Merge "Revert "Enforce that RuntimeShader is only hardware accelerated""
diff --git a/Android.bp b/Android.bp
index 3b8ef61..acd3b34 100644
--- a/Android.bp
+++ b/Android.bp
@@ -25,6 +25,18 @@
 //
 // READ ME: ########################################################
 
+// TODO(b/21090328): Remove filter after we are ready to.
+soong_config_module_type {
+    name: "java_library_with_nonpublic_deps",
+    module_type: "java_library",
+    config_namespace: "ANDROID",
+    bool_variables: ["include_nonpublic_framework_api"],
+    properties: [
+        "static_libs",
+        "libs",
+    ],
+}
+
 package {
     default_applicable_licenses: ["frameworks_base_license"],
 }
@@ -142,7 +154,7 @@
     ],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "framework-updatable-stubs-module_libs_api",
     static_libs: [
         "android.net.ipsec.ike.stubs.module_lib",
@@ -161,11 +173,18 @@
         "framework-uwb.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs.module_lib",
+            ],
+        },
+    },
     sdk_version: "module_current",
     visibility: ["//visibility:private"],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "framework-all",
     installable: false,
     static_libs: [
@@ -186,6 +205,13 @@
         "framework-wifi.impl",
         "updatable-media",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs.module_lib",
+            ],
+        },
+    },
     apex_available: ["//apex_available:platform"],
     sdk_version: "core_platform",
     visibility: [
@@ -362,6 +388,7 @@
         "tv_tuner_resource_manager_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
         "modules-utils-preconditions",
+        "modules-utils-synchronous-result-receiver",
         "modules-utils-os",
         "framework-permission-aidl-java",
         "spatializer-aidl-java",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 3b11036..9543fbd 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -23,6 +23,14 @@
 // and comparing them against the checked in API signature, and also checking compatibility
 // with the latest frozen API signature.
 
+// TODO(b/21090328): Remove filter after we are ready to.
+soong_config_module_type_import {
+    from: "frameworks/base/Android.bp",
+    module_types: [
+        "java_library_with_nonpublic_deps",
+    ],
+}
+
 /////////////////////////////////////////////////////////////////////
 // Common metalava configs
 /////////////////////////////////////////////////////////////////////
@@ -299,21 +307,35 @@
     visibility: ["//visibility:private"],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android-non-updatable.stubs",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":api-stubs-docs-non-updatable"],
     libs: modules_public_stubs,
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     dist: {
         dir: "apistubs/android/public",
     },
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android-non-updatable.stubs.system",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":system-api-stubs-docs-non-updatable"],
     libs: modules_system_stubs,
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     dist: {
         dir: "apistubs/android/system",
     },
@@ -334,11 +356,18 @@
     },
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android-non-updatable.stubs.test",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":test-api-stubs-docs-non-updatable"],
     libs: modules_system_stubs,
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     dist: {
         dir: "apistubs/android/test",
     },
@@ -357,21 +386,35 @@
     defaults_visibility: ["//frameworks/base/services"],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android_stubs_current",
     static_libs: modules_public_stubs + [
         "android-non-updatable.stubs",
         "private-stub-annotations-jar",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     defaults: ["android.jar_defaults"],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android_system_stubs_current",
     static_libs: modules_system_stubs + [
         "android-non-updatable.stubs.system",
         "private-stub-annotations-jar",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -392,7 +435,7 @@
     ],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android_test_stubs_current",
     // Modules do not have test APIs, but we want to include their SystemApis, like we include
     // the SystemApi of framework-non-updatable-sources.
@@ -400,6 +443,13 @@
         "android-non-updatable.stubs.test",
         "private-stub-annotations-jar",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
diff --git a/core/api/current.txt b/core/api/current.txt
index 49689e4..f84322e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1420,6 +1420,7 @@
     field public static final int supportsMultipleDisplays = 16844182; // 0x1010596
     field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
     field public static final int supportsRtl = 16843695; // 0x10103af
+    field public static final int supportsStylusHandwriting;
     field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
     field public static final int supportsUploading = 16843419; // 0x101029b
     field public static final int suppressesSpellChecker = 16844355; // 0x1010643
@@ -10249,6 +10250,7 @@
     method @Nullable public String getPackageName();
     method public int getUid();
     method public boolean isTrusted(@NonNull android.content.Context);
+    method @NonNull public static android.content.AttributionSource myAttributionSource();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.AttributionSource> CREATOR;
   }
@@ -11566,6 +11568,7 @@
     field public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
     field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
     field public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
+    field public static final String EXTRA_PREVIOUS_UID = "android.intent.extra.PREVIOUS_UID";
     field public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
     field public static final String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
     field public static final String EXTRA_QUICK_VIEW_FEATURES = "android.intent.extra.QUICK_VIEW_FEATURES";
@@ -11597,6 +11600,7 @@
     field public static final String EXTRA_TIMEZONE = "time-zone";
     field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
     field public static final String EXTRA_UID = "android.intent.extra.UID";
+    field public static final String EXTRA_UID_CHANGING = "android.intent.extra.UID_CHANGING";
     field public static final String EXTRA_USER = "android.intent.extra.USER";
     field public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
     field public static final int FILL_IN_ACTION = 1; // 0x1
@@ -26146,11 +26150,14 @@
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
     field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
     field public static final String COLUMN_REVIEW_RATING = "review_rating";
     field public static final String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
+    field public static final String COLUMN_SCRAMBLED = "scrambled";
     field public static final String COLUMN_SEARCHABLE = "searchable";
     field public static final String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
     field @Deprecated public static final String COLUMN_SEASON_NUMBER = "season_number";
@@ -26210,7 +26217,9 @@
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
     field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes";
     field public static final String COLUMN_RECORDING_DATA_URI = "recording_data_uri";
@@ -41469,6 +41478,7 @@
     field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
     field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+    field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int";
     field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
@@ -43085,6 +43095,7 @@
     method public int getSimSlotIndex();
     method public int getSubscriptionId();
     method public int getSubscriptionType();
+    method public int getUsageSetting();
     method public boolean isEmbedded();
     method public boolean isOpportunistic();
     method public void writeToParcel(android.os.Parcel, int);
@@ -43159,6 +43170,10 @@
     field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1
     field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
     field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
+    field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2
+    field public static final int USAGE_SETTING_DEFAULT = 0; // 0x0
+    field public static final int USAGE_SETTING_UNKNOWN = -1; // 0xffffffff
+    field public static final int USAGE_SETTING_VOICE_CENTRIC = 1; // 0x1
   }
 
   public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
@@ -52852,6 +52867,7 @@
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public CharSequence loadLabel(android.content.pm.PackageManager);
     method public boolean shouldShowInInputMethodPicker();
+    method public boolean supportsStylusHandwriting();
     method public boolean suppressesSpellChecker();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c78e50f..99e47b1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -311,6 +311,7 @@
     field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
     field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
     field public static final String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
+    field public static final String UPDATE_DEVICE_MANAGEMENT_RESOURCES = "android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES";
     field public static final String UPDATE_DOMAIN_VERIFICATION_USER_SELECTION = "android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION";
     field public static final String UPDATE_FONTS = "android.permission.UPDATE_FONTS";
     field public static final String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
@@ -9302,6 +9303,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String);
+    field @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public static final String ACTION_CREATE_SUPERVISED_USER = "android.os.action.CREATE_SUPERVISED_USER";
     field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
     field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
     field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
@@ -12616,6 +12618,7 @@
   }
 
   public class TelephonyManager {
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addCarrierPrivilegesListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) @WorkerThread public void bootstrapAuthenticationRequest(int, @NonNull android.net.Uri, @NonNull android.telephony.gba.UaSecurityProtocolIdentifier, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.BootstrapAuthenticationCallback);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
@@ -12717,6 +12720,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
     method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeCarrierPrivilegesListener(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
@@ -12893,6 +12897,10 @@
     field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
+  public static interface TelephonyManager.CarrierPrivilegesListener {
+    method public void onCarrierPrivilegesChanged(@NonNull java.util.List<java.lang.String>, @NonNull int[]);
+  }
+
   public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
     method public int getErrorCode();
     field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2
@@ -13140,6 +13148,7 @@
     method public final int getSlotIndex();
     method public final void notifyApnUnthrottled(@NonNull String);
     method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
+    method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
     method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
     method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
     method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback);
@@ -13150,6 +13159,7 @@
   public class DataServiceCallback {
     method public void onApnUnthrottled(@NonNull String);
     method public void onDataCallListChanged(@NonNull java.util.List<android.telephony.data.DataCallResponse>);
+    method public void onDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
     method public void onDeactivateDataCallComplete(int);
     method public void onRequestDataCallListComplete(int, @NonNull java.util.List<android.telephony.data.DataCallResponse>);
     method public void onSetDataProfileComplete(int);
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index db7ab1a..eb4a355 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PersistableBundle;
@@ -498,6 +499,28 @@
         }
     }
 
+    /**
+     * Shows or hides a Camera app compat toggle for stretched issues with the requested state.
+     *
+     * @param token The token for the window that needs a control.
+     * @param showControl Whether the control should be shown or hidden.
+     * @param transformationApplied Whether the treatment is already applied.
+     * @param callback The callback executed when the user clicks on a control.
+     */
+    void requestCompatCameraControl(Resources res, IBinder token, boolean showControl,
+            boolean transformationApplied, ICompatCameraControlCallback callback) {
+        if (!res.getBoolean(com.android.internal.R.bool
+                .config_isCameraCompatControlForStretchedIssuesEnabled)) {
+            return;
+        }
+        try {
+            getActivityClientController().requestCompatCameraControl(
+                    token, showControl, transformationApplied, callback);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     public static ActivityClient getInstance() {
         return sInstance.get();
     }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c0a8c1e..a8894dc 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -561,8 +561,8 @@
         private Configuration mPendingOverrideConfig;
         // Used for consolidating configs before sending on to Activity.
         private Configuration tmpConfig = new Configuration();
-        // Callback used for updating activity override config.
-        ViewRootImpl.ActivityConfigCallback configCallback;
+        // Callback used for updating activity override config and camera compat control state.
+        ViewRootImpl.ActivityConfigCallback activityConfigCallback;
         ActivityClientRecord nextIdle;
 
         // Indicates whether this activity is currently the topmost resumed one in the system.
@@ -660,13 +660,30 @@
             stopped = false;
             hideForNow = false;
             nextIdle = null;
-            configCallback = (Configuration overrideConfig, int newDisplayId) -> {
-                if (activity == null) {
-                    throw new IllegalStateException(
-                            "Received config update for non-existing activity");
+            activityConfigCallback = new ViewRootImpl.ActivityConfigCallback() {
+                @Override
+                public void onConfigurationChanged(Configuration overrideConfig,
+                        int newDisplayId) {
+                    if (activity == null) {
+                        throw new IllegalStateException(
+                                "Received config update for non-existing activity");
+                    }
+                    activity.mMainThread.handleActivityConfigurationChanged(
+                            ActivityClientRecord.this, overrideConfig, newDisplayId);
                 }
-                activity.mMainThread.handleActivityConfigurationChanged(this, overrideConfig,
-                        newDisplayId);
+
+                @Override
+                public void requestCompatCameraControl(boolean showControl,
+                        boolean transformationApplied, ICompatCameraControlCallback callback) {
+                    if (activity == null) {
+                        throw new IllegalStateException(
+                                "Received camera compat control update for non-existing activity");
+                    }
+                    ActivityClient.getInstance().requestCompatCameraControl(
+                            activity.getResources(), token, showControl, transformationApplied,
+                            callback);
+                }
+
             };
         }
 
@@ -3672,7 +3689,7 @@
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstances, config,
-                        r.referrer, r.voiceInteractor, window, r.configCallback,
+                        r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                         r.assistToken, r.shareableActivityToken);
 
                 if (customIntent != null) {
@@ -4982,7 +4999,8 @@
                 Slog.w(TAG, "Activity top position already set to onTop=" + onTop);
                 return;
             }
-            throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
+            // TODO(b/209744518): Remove this short-term workaround while fixing the binder failure.
+            Slog.e(TAG, "Activity top position already set to onTop=" + onTop);
         }
 
         r.isTopResumedActivity = onTop;
@@ -5512,8 +5530,8 @@
                 } else {
                     final ViewRootImpl viewRoot = v.getViewRootImpl();
                     if (viewRoot != null) {
-                        // Clear the callback to avoid the destroyed activity from receiving
-                        // configuration changes that are no longer effective.
+                        // Clear callbacks to avoid the destroyed activity from receiving
+                        // configuration or camera compat changes that are no longer effective.
                         viewRoot.setActivityConfigCallback(null);
                     }
                     wm.removeViewImmediate(v);
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index aba6eb9..83c57c5 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.app.ActivityManager;
+import android.app.ICompatCameraControlCallback;
 import android.app.IRequestFinishCallback;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
@@ -143,4 +144,15 @@
 
     /** Reports that the splash screen view has attached to activity.  */
     oneway void splashScreenAttached(in IBinder token);
+
+    /**
+     * Shows or hides a Camera app compat toggle for stretched issues with the requested state.
+     *
+     * @param token The token for the window that needs a control.
+     * @param showControl Whether the control should be shown or hidden.
+     * @param transformationApplied Whether the treatment is already applied.
+     * @param callback The callback executed when the user clicks on a control.
+     */
+    oneway void requestCompatCameraControl(in IBinder token, boolean showControl,
+            boolean transformationApplied, in ICompatCameraControlCallback callback);
 }
diff --git a/core/java/android/app/ICompatCameraControlCallback.aidl b/core/java/android/app/ICompatCameraControlCallback.aidl
new file mode 100644
index 0000000..1a7f210
--- /dev/null
+++ b/core/java/android/app/ICompatCameraControlCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/**
+ * This callback allows ActivityRecord to ask the calling View to apply the treatment for stretched
+ * issues affecting camera viewfinders when the user clicks on the camera compat control.
+ *
+ * {@hide}
+ */
+oneway interface ICompatCameraControlCallback {
+
+    void applyCameraCompatTreatment();
+
+    void revertCameraCompatTreatment();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 370031a..eb4585d 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1259,7 +1259,7 @@
                 info, title, parent, id,
                 (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
                 new Configuration(), null /* referrer */, null /* voiceInteractor */,
-                null /* window */, null /* activityConfigCallback */, null /*assistToken*/,
+                null /* window */, null /* activityCallback */, null /*assistToken*/,
                 null /*shareableActivityToken*/);
         return activity;
     }
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 95b00c1..18f9379 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -39,6 +40,8 @@
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -274,6 +277,51 @@
      */
     public boolean isSleeping;
 
+    /**
+     * Camera compat control isn't shown because it's not requested by heuristics.
+     * @hide
+     */
+    public static final int CAMERA_COMPAT_CONTROL_HIDDEN = 0;
+
+    /**
+     * Camera compat control is shown with the treatment suggested.
+     * @hide
+     */
+    public static final int CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED = 1;
+
+    /**
+     * Camera compat control is shown to allow reverting the applied treatment.
+     * @hide
+     */
+    public static final int CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED = 2;
+
+    /**
+     * Camera compat control is dismissed by user.
+     * @hide
+     */
+    public static final int CAMERA_COMPAT_CONTROL_DISMISSED = 3;
+
+    /**
+     * Enum for the Camera app compat control states.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "CAMERA_COMPAT_CONTROL_" }, value = {
+            CAMERA_COMPAT_CONTROL_HIDDEN,
+            CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED,
+            CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED,
+            CAMERA_COMPAT_CONTROL_DISMISSED,
+    })
+    public @interface CameraCompatControlState {};
+
+    /**
+     * State of the Camera app compat control which is used to correct stretched viewfinder
+     * in apps that don't handle all possible configurations and changes between them correctly.
+     * @hide
+     */
+    @CameraCompatControlState
+    public int cameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
+
     TaskInfo() {
         // Do nothing
     }
@@ -342,6 +390,17 @@
         launchCookies.add(cookie);
     }
 
+    /** @hide */
+    public boolean hasCameraCompatControl() {
+        return cameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
+                && cameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
+    }
+
+    /** @hide */
+    public boolean hasCompatUI() {
+        return hasCameraCompatControl() || topActivityInSizeCompat;
+    }
+
     /**
      * @return {@code true} if this task contains the launch cookie.
      * @hide
@@ -394,19 +453,20 @@
      * @return {@code true} if parameters that are important for size compat have changed.
      * @hide
      */
-    public boolean equalsForSizeCompat(@Nullable TaskInfo that) {
+    public boolean equalsForCompatUi(@Nullable TaskInfo that) {
         if (that == null) {
             return false;
         }
         return displayId == that.displayId
                 && taskId == that.taskId
                 && topActivityInSizeCompat == that.topActivityInSizeCompat
-                // Bounds are important if top activity is in size compat
-                && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
+                && cameraCompatControlState == that.cameraCompatControlState
+                // Bounds are important if top activity has compat controls.
+                && (!hasCompatUI() || configuration.windowConfiguration.getBounds()
                     .equals(that.configuration.windowConfiguration.getBounds()))
-                && (!topActivityInSizeCompat || configuration.getLayoutDirection()
+                && (!hasCompatUI() || configuration.getLayoutDirection()
                     == that.configuration.getLayoutDirection())
-                && (!topActivityInSizeCompat || isVisible == that.isVisible);
+                && (!hasCompatUI() || isVisible == that.isVisible);
     }
 
     /**
@@ -449,6 +509,7 @@
         topActivityInSizeCompat = source.readBoolean();
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
         displayAreaFeatureId = source.readInt();
+        cameraCompatControlState = source.readInt();
     }
 
     /**
@@ -492,6 +553,7 @@
         dest.writeBoolean(topActivityInSizeCompat);
         dest.writeTypedObject(mTopActivityLocusId, flags);
         dest.writeInt(displayAreaFeatureId);
+        dest.writeInt(cameraCompatControlState);
     }
 
     @Override
@@ -525,6 +587,22 @@
                 + " topActivityInSizeCompat=" + topActivityInSizeCompat
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
+                + " cameraCompatControlState="
+                        + cameraCompatControlStateToString(cameraCompatControlState)
                 + "}";
     }
+
+    /** @hide */
+    public static String cameraCompatControlStateToString(
+            @CameraCompatControlState int cameraCompatControlState) {
+        switch (cameraCompatControlState) {
+            case CAMERA_COMPAT_CONTROL_HIDDEN: return "hidden";
+            case CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED: return "treatment-suggested";
+            case CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED: return "treatment-applied";
+            case CAMERA_COMPAT_CONTROL_DISMISSED: return "dismissed";
+            default:
+                throw new AssertionError(
+                    "Unexpected camera compat control state: " + cameraCompatControlState);
+        }
+    }
 }
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 567eb4a..9bb048d 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -25,6 +25,11 @@
 import android.os.RemoteException;
 
 import com.android.internal.backup.IBackupTransport;
+import com.android.internal.backup.ITransportStatusCallback;
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Concrete class that provides a stable-API bridge between IBackupTransport
@@ -659,141 +664,185 @@
     class TransportImpl extends IBackupTransport.Stub {
 
         @Override
-        public String name() throws RemoteException {
-            return BackupTransport.this.name();
+        public void name(AndroidFuture<String> resultFuture) throws RemoteException {
+            String result = BackupTransport.this.name();
+            resultFuture.complete(result);
         }
 
         @Override
-        public Intent configurationIntent() throws RemoteException {
-            return BackupTransport.this.configurationIntent();
-        }
-
-        @Override
-        public String currentDestinationString() throws RemoteException {
-            return BackupTransport.this.currentDestinationString();
-        }
-
-        @Override
-        public Intent dataManagementIntent() {
-            return BackupTransport.this.dataManagementIntent();
-        }
-
-        @Override
-        public CharSequence dataManagementIntentLabel() {
-            return BackupTransport.this.dataManagementIntentLabel();
-        }
-
-        @Override
-        public String transportDirName() throws RemoteException {
-            return BackupTransport.this.transportDirName();
-        }
-
-        @Override
-        public long requestBackupTime() throws RemoteException {
-            return BackupTransport.this.requestBackupTime();
-        }
-
-        @Override
-        public int initializeDevice() throws RemoteException {
-            return BackupTransport.this.initializeDevice();
-        }
-
-        @Override
-        public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
+        public void configurationIntent(AndroidFuture<Intent> resultFuture)
                 throws RemoteException {
-            return BackupTransport.this.performBackup(packageInfo, inFd, flags);
+            Intent result = BackupTransport.this.configurationIntent();
+            resultFuture.complete(result);
         }
 
         @Override
-        public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
-            return BackupTransport.this.clearBackupData(packageInfo);
+        public void currentDestinationString(AndroidFuture<String> resultFuture)
+                throws RemoteException {
+            String result = BackupTransport.this.currentDestinationString();
+            resultFuture.complete(result);
         }
 
         @Override
-        public int finishBackup() throws RemoteException {
-            return BackupTransport.this.finishBackup();
+        public void dataManagementIntent(AndroidFuture<Intent> resultFuture)
+                throws RemoteException {
+            Intent result = BackupTransport.this.dataManagementIntent();
+            resultFuture.complete(result);
         }
 
         @Override
-        public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
-            return BackupTransport.this.getAvailableRestoreSets();
+        public void dataManagementIntentLabel(AndroidFuture<CharSequence> resultFuture)
+                throws RemoteException {
+            CharSequence result = BackupTransport.this.dataManagementIntentLabel();
+            resultFuture.complete(result);
         }
 
         @Override
-        public long getCurrentRestoreSet() throws RemoteException {
-            return BackupTransport.this.getCurrentRestoreSet();
+        public void transportDirName(AndroidFuture<String> resultFuture) throws RemoteException {
+            String result = BackupTransport.this.transportDirName();
+            resultFuture.complete(result);
         }
 
         @Override
-        public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
-            return BackupTransport.this.startRestore(token, packages);
+        public void requestBackupTime(AndroidFuture<Long> resultFuture) throws RemoteException {
+            long result = BackupTransport.this.requestBackupTime();
+            resultFuture.complete(result);
         }
 
         @Override
-        public RestoreDescription nextRestorePackage() throws RemoteException {
-            return BackupTransport.this.nextRestorePackage();
+        public void initializeDevice(ITransportStatusCallback callback) throws RemoteException {
+            int result = BackupTransport.this.initializeDevice();
+            callback.onOperationCompleteWithStatus(result);
         }
 
         @Override
-        public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
-            return BackupTransport.this.getRestoreData(outFd);
+        public void performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags,
+                ITransportStatusCallback callback) throws RemoteException {
+            int result = BackupTransport.this.performBackup(packageInfo, inFd, flags);
+            callback.onOperationCompleteWithStatus(result);
         }
 
         @Override
-        public void finishRestore() throws RemoteException {
+        public void clearBackupData(PackageInfo packageInfo, ITransportStatusCallback callback)
+                throws RemoteException {
+            int result = BackupTransport.this.clearBackupData(packageInfo);
+            callback.onOperationCompleteWithStatus(result);
+        }
+
+        @Override
+        public void finishBackup(ITransportStatusCallback callback) throws RemoteException {
+            int result = BackupTransport.this.finishBackup();
+            callback.onOperationCompleteWithStatus(result);
+        }
+
+        @Override
+        public void getAvailableRestoreSets(AndroidFuture<List<RestoreSet>> resultFuture)
+                throws RemoteException {
+            RestoreSet[] result = BackupTransport.this.getAvailableRestoreSets();
+            resultFuture.complete(Arrays.asList(result));
+        }
+
+        @Override
+        public void getCurrentRestoreSet(AndroidFuture<Long> resultFuture)
+                throws RemoteException {
+            long result = BackupTransport.this.getCurrentRestoreSet();
+            resultFuture.complete(result);
+        }
+
+        @Override
+        public void startRestore(long token, PackageInfo[] packages,
+                ITransportStatusCallback callback)  throws RemoteException {
+            int result = BackupTransport.this.startRestore(token, packages);
+            callback.onOperationCompleteWithStatus(result);
+        }
+
+        @Override
+        public void nextRestorePackage(AndroidFuture<RestoreDescription> resultFuture)
+                throws RemoteException {
+            RestoreDescription result = BackupTransport.this.nextRestorePackage();
+            resultFuture.complete(result);
+        }
+
+        @Override
+        public void getRestoreData(ParcelFileDescriptor outFd,
+                ITransportStatusCallback callback) throws RemoteException {
+            int result = BackupTransport.this.getRestoreData(outFd);
+            callback.onOperationCompleteWithStatus(result);
+        }
+
+        @Override
+        public void finishRestore(ITransportStatusCallback callback)
+                throws RemoteException {
             BackupTransport.this.finishRestore();
+            callback.onOperationComplete();
         }
 
         @Override
-        public long requestFullBackupTime() throws RemoteException {
-            return BackupTransport.this.requestFullBackupTime();
-        }
-
-        @Override
-        public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket,
-                int flags) throws RemoteException {
-            return BackupTransport.this.performFullBackup(targetPackage, socket, flags);
-        }
-
-        @Override
-        public int checkFullBackupSize(long size) {
-            return BackupTransport.this.checkFullBackupSize(size);
-        }
-
-        @Override
-        public int sendBackupData(int numBytes) throws RemoteException {
-            return BackupTransport.this.sendBackupData(numBytes);
-        }
-
-        @Override
-        public void cancelFullBackup() throws RemoteException {
-            BackupTransport.this.cancelFullBackup();
-        }
-
-        @Override
-        public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup)
+        public void requestFullBackupTime(AndroidFuture<Long> resultFuture)
                 throws RemoteException {
-            return BackupTransport.this.isAppEligibleForBackup(targetPackage, isFullBackup);
+            long result = BackupTransport.this.requestFullBackupTime();
+            resultFuture.complete(result);
         }
 
         @Override
-        public long getBackupQuota(String packageName, boolean isFullBackup) {
-            return BackupTransport.this.getBackupQuota(packageName, isFullBackup);
+        public void performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket,
+                int flags, ITransportStatusCallback callback) throws RemoteException {
+            int result = BackupTransport.this.performFullBackup(targetPackage, socket, flags);
+            callback.onOperationCompleteWithStatus(result);
         }
 
         @Override
-        public int getTransportFlags() {
-            return BackupTransport.this.getTransportFlags();
+        public void checkFullBackupSize(long size, ITransportStatusCallback callback)
+                throws RemoteException {
+            int result = BackupTransport.this.checkFullBackupSize(size);
+            callback.onOperationCompleteWithStatus(result);
         }
 
         @Override
-        public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
-            return BackupTransport.this.getNextFullRestoreDataChunk(socket);
+        public void sendBackupData(int numBytes, ITransportStatusCallback callback)
+                throws RemoteException {
+            int result = BackupTransport.this.sendBackupData(numBytes);
+            callback.onOperationCompleteWithStatus(result);
         }
 
         @Override
-        public int abortFullRestore() {
-            return BackupTransport.this.abortFullRestore();
+        public void cancelFullBackup(ITransportStatusCallback callback) throws RemoteException {
+            BackupTransport.this.cancelFullBackup();
+            callback.onOperationComplete();
+        }
+
+        @Override
+        public void isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup,
+                AndroidFuture<Boolean> resultFuture) throws RemoteException {
+            boolean result = BackupTransport.this.isAppEligibleForBackup(targetPackage,
+                    isFullBackup);
+            resultFuture.complete(result);
+        }
+
+        @Override
+        public void getBackupQuota(String packageName, boolean isFullBackup,
+                AndroidFuture<Long> resultFuture) throws RemoteException {
+            long result = BackupTransport.this.getBackupQuota(packageName, isFullBackup);
+            resultFuture.complete(result);
+        }
+
+        @Override
+        public void getTransportFlags(AndroidFuture<Integer> resultFuture) throws RemoteException {
+            int result = BackupTransport.this.getTransportFlags();
+            resultFuture.complete(result);
+        }
+
+        @Override
+        public void getNextFullRestoreDataChunk(ParcelFileDescriptor socket,
+                ITransportStatusCallback callback) throws RemoteException {
+            int result = BackupTransport.this.getNextFullRestoreDataChunk(socket);
+            callback.onOperationCompleteWithStatus(result);
+        }
+
+        @Override
+        public void abortFullRestore(ITransportStatusCallback callback) throws RemoteException {
+            int result = BackupTransport.this.abortFullRestore();
+            callback.onOperationCompleteWithStatus(result);
         }
     }
 }
diff --git a/core/java/android/app/cloudsearch/OWNERS b/core/java/android/app/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/core/java/android/app/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index d66dc63..8b9cec1 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,17 +32,19 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 
 /**
@@ -271,7 +275,7 @@
                     IBluetoothA2dp.class.getName()) {
                 @Override
                 public IBluetoothA2dp getServiceInterface(IBinder service) {
-                    return IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothA2dp.Stub.asInterface(service);
                 }
     };
 
@@ -322,17 +326,21 @@
     @UnsupportedAppUsage
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.connect(device);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connectWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -364,17 +372,21 @@
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.disconnect(device);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnectWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -385,19 +397,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevicesWithAttribution(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevicesWithAttribution(mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -408,20 +425,25 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStatesWithAttribution(states,
+                        mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStatesWithAttribution(states,
-                                mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -432,18 +454,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @BtProfileState int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionState(device);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionStateWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.STATE_DISCONNECTED;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.STATE_DISCONNECTED;
         }
+        return defaultValue;
     }
 
     /**
@@ -471,18 +496,21 @@
     @UnsupportedAppUsage(trackingBug = 171933273)
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && ((device == null) || isValidDevice(device))) {
-                return service.setActiveDevice(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && ((device == null) || isValidDevice(device))) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setActiveDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -499,18 +527,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothDevice getActiveDevice() {
         if (VDBG) log("getActiveDevice()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        final BluetoothDevice defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BluetoothDevice> recv =
+                        new SynchronousResultReceiver();
+                service.getActiveDevice(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getActiveDevice(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return null;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return null;
         }
+        return defaultValue;
     }
 
     /**
@@ -555,22 +589,23 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                    && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -589,19 +624,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return BluetoothAdapter.connectionPolicyToPriority(
-                        service.getPriority(device, mAttributionSource));
-            }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.PRIORITY_OFF;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.PRIORITY_OFF;
-        }
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
     }
 
     /**
@@ -623,18 +646,21 @@
     })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionPolicy(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         }
+        return defaultValue;
     }
 
     /**
@@ -646,17 +672,21 @@
     @RequiresNoPermission
     public boolean isAvrcpAbsoluteVolumeSupported() {
         if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.isAvrcpAbsoluteVolumeSupported();
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isAvrcpAbsoluteVolumeSupported(recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -669,14 +699,16 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setAvrcpAbsoluteVolume(int volume) {
         if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
                 service.setAvrcpAbsoluteVolume(volume, mAttributionSource);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
         }
     }
 
@@ -689,18 +721,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isA2dpPlaying(BluetoothDevice device) {
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.isA2dpPlaying(device, mAttributionSource);
+        if (DBG) log("isA2dpPlaying(" + device + ")");
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isA2dpPlaying(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -729,8 +765,7 @@
     /**
      * Gets the current codec status (configuration and capability).
      *
-     * @param device the remote Bluetooth device. If null, use the current
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @return the current codec status
      * @hide
      */
@@ -742,26 +777,28 @@
     public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
         verifyDeviceNotNull(device, "getCodecStatus");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.getCodecStatus(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final BluetoothCodecStatus defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<BluetoothCodecStatus> recv =
+                        new SynchronousResultReceiver();
+                service.getCodecStatus(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return null;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in getCodecStatus()", e);
-            return null;
         }
+        return defaultValue;
     }
 
     /**
      * Sets the codec configuration preference.
      *
-     * @param device the remote Bluetooth device. If null, use the current
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @param codecConfig the codec configuration preference
      * @hide
      */
@@ -777,24 +814,23 @@
             Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
             throw new IllegalArgumentException("codecConfig cannot be null");
         }
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
                 service.setCodecConfigPreference(device, codecConfig, mAttributionSource);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e);
-            return;
         }
     }
 
     /**
      * Enables the optional codecs.
      *
-     * @param device the remote Bluetooth device. If null, use the currect
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -810,8 +846,7 @@
     /**
      * Disables the optional codecs.
      *
-     * @param device the remote Bluetooth device. If null, use the currect
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -827,26 +862,25 @@
     /**
      * Enables or disables the optional codecs.
      *
-     * @param device the remote Bluetooth device. If null, use the currect
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @param enable if true, enable the optional codecs, other disable them
      */
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
                 if (enable) {
                     service.enableOptionalCodecs(device, mAttributionSource);
                 } else {
                     service.disableOptionalCodecs(device, mAttributionSource);
                 }
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e);
-            return;
         }
     }
 
@@ -864,18 +898,23 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @OptionalCodecsSupportStatus
     public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
+        if (DBG) log("isOptionalCodecsSupported(" + device + ")");
         verifyDeviceNotNull(device, "isOptionalCodecsSupported");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.supportsOptionalCodecs(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.supportsOptionalCodecs(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in supportsOptionalCodecs()", e);
-            return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
         }
+        return defaultValue;
     }
 
     /**
@@ -892,18 +931,23 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @OptionalCodecsPreferenceStatus
     public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
+        if (DBG) log("isOptionalCodecsEnabled(" + device + ")");
         verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.getOptionalCodecsEnabled(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = OPTIONAL_CODECS_PREF_UNKNOWN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getOptionalCodecsEnabled(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return OPTIONAL_CODECS_PREF_UNKNOWN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in getOptionalCodecsEnabled()", e);
-            return OPTIONAL_CODECS_PREF_UNKNOWN;
         }
+        return defaultValue;
     }
 
     /**
@@ -921,24 +965,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
             @OptionalCodecsPreferenceStatus int value) {
+        if (DBG) log("setOptionalCodecsEnabled(" + device + ")");
         verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
-        try {
-            if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
-                    && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
-                    && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
-                Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
-                return;
-            }
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
+        if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
+                && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
+                && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+            Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
+            return;
+        }
+        final IBluetoothA2dp service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
                 service.setOptionalCodecsEnabled(device, value, mAttributionSource);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return;
         }
     }
 
@@ -961,17 +1005,21 @@
     })
     public @Type int getDynamicBufferSupport() {
         if (VDBG) log("getDynamicBufferSupport()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.getDynamicBufferSupport(mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = DYNAMIC_BUFFER_SUPPORT_NONE;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getDynamicBufferSupport(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return DYNAMIC_BUFFER_SUPPORT_NONE;
-        } catch (RemoteException e) {
-            Log.e(TAG, "failed to get getDynamicBufferSupport, error: ", e);
-            return DYNAMIC_BUFFER_SUPPORT_NONE;
         }
+        return defaultValue;
     }
 
     /**
@@ -992,17 +1040,22 @@
     })
     public @Nullable BufferConstraints getBufferConstraints() {
         if (VDBG) log("getBufferConstraints()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.getBufferConstraints(mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final BufferConstraints defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BufferConstraints> recv =
+                        new SynchronousResultReceiver();
+                service.getBufferConstraints(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return null;
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return null;
         }
+        return defaultValue;
     }
 
     /**
@@ -1027,17 +1080,21 @@
             Log.e(TAG, "Trying to set audio buffer length to a negative value: " + value);
             return false;
         }
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.setBufferLengthMillis(codec, value, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setBufferLengthMillis(codec, value, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
         }
+        return defaultValue;
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index 924dc55..5941681 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -29,14 +31,16 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Bluetooth A2DP Sink
@@ -86,7 +90,7 @@
                     "BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) {
                 @Override
                 public IBluetoothA2dpSink getServiceInterface(IBinder service) {
-                    return IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothA2dpSink.Stub.asInterface(service);
                 }
     };
 
@@ -140,16 +144,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -181,16 +189,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -204,17 +216,23 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -228,18 +246,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -251,18 +274,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
-        if (VDBG) log("getState(" + device + ")");
+        if (VDBG) log("getConnectionState(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -282,16 +309,21 @@
     public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
         if (VDBG) log("getAudioConfig(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final BluetoothAudioConfig defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getAudioConfig(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return null;
+                final SynchronousResultReceiver<BluetoothAudioConfig> recv =
+                        new SynchronousResultReceiver();
+                service.getAudioConfig(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -337,20 +369,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -393,16 +427,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     /**
@@ -420,17 +458,22 @@
             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
     })
     public boolean isAudioPlaying(@NonNull BluetoothDevice device) {
+        if (VDBG) log("isAudioPlaying(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.isA2dpPlaying(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isA2dpPlaying(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 4297512..2d1ecfb 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -797,7 +797,7 @@
     @RequiresNoPermission
     public static synchronized BluetoothAdapter getDefaultAdapter() {
         if (sAdapter == null) {
-            sAdapter = createAdapter(BluetoothManager.resolveAttributionSource(null));
+            sAdapter = createAdapter(AttributionSource.myAttributionSource());
         }
         return sAdapter;
     }
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index 536dfb0..81fc3e1 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -23,13 +25,15 @@
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
@@ -93,8 +97,7 @@
                     "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) {
                 @Override
                 public IBluetoothAvrcpController getServiceInterface(IBinder service) {
-                    return IBluetoothAvrcpController.Stub.asInterface(
-                            Binder.allowBlocking(service));
+                    return IBluetoothAvrcpController.Stub.asInterface(service);
                 }
     };
 
@@ -130,19 +133,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -153,20 +161,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -177,18 +189,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothAvrcpController service = getService();
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -201,17 +216,22 @@
     public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getPlayerSettings");
         BluetoothAvrcpPlayerSettings settings = null;
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        final BluetoothAvrcpPlayerSettings defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                settings = service.getPlayerSettings(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
-                return null;
+                final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv =
+                        new SynchronousResultReceiver();
+                service.getPlayerSettings(device, mAttributionSource, recv);
+                settings = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return settings;
+        return defaultValue;
     }
 
     /**
@@ -222,18 +242,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
         if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                return service.setPlayerApplicationSetting(plAppSetting, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -245,18 +268,20 @@
     public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
         Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = "
                 + keyState);
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource);
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                 return;
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
-                return;
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
index f0a8df0..ba57ec4 100644
--- a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
+++ b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
@@ -17,6 +17,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
@@ -27,13 +29,14 @@
 import android.annotation.SystemApi;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -41,6 +44,7 @@
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Bluetooth CSIP set coordinator.
@@ -229,8 +233,7 @@
                     IBluetoothCsipSetCoordinator.class.getName()) {
                 @Override
                 public IBluetoothCsipSetCoordinator getServiceInterface(IBinder service) {
-                    return IBluetoothCsipSetCoordinator.Stub.asInterface(
-                            Binder.allowBlocking(service));
+                    return IBluetoothCsipSetCoordinator.Stub.asInterface(service);
                 }
             };
 
@@ -283,26 +286,27 @@
     public
     @Nullable UUID groupLock(int groupId, @Nullable @CallbackExecutor Executor executor,
             @Nullable ClientLockCallback cb) {
-        if (VDBG) {
-            log("groupLockSet()");
-        }
+        if (VDBG) log("groupLockSet()");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                IBluetoothCsipSetCoordinatorLockCallback delegate = null;
-                if ((executor != null) && (cb != null)) {
-                    delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb);
-                }
-                return service.groupLock(groupId, delegate, mAttributionSource).getUuid();
+        final UUID defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            IBluetoothCsipSetCoordinatorLockCallback delegate = null;
+            if ((executor != null) && (cb != null)) {
+                delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb);
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
+            try {
+                final SynchronousResultReceiver<ParcelUuid> recv = new SynchronousResultReceiver();
+                service.groupLock(groupId, delegate, mAttributionSource, recv);
+                final ParcelUuid ret = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+                return ret == null ? defaultValue : ret.getUuid();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            return null;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return null;
         }
+        return defaultValue;
     }
 
     /**
@@ -315,27 +319,26 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean groupUnlock(@NonNull UUID lockUuid) {
-        if (VDBG) {
-            log("groupLockSet()");
-        }
+        if (VDBG) log("groupLockSet()");
         if (lockUuid == null) {
             return false;
         }
-
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                 return true;
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -348,22 +351,22 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public @NonNull Map getGroupUuidMapByDevice(@Nullable BluetoothDevice device) {
-        if (VDBG) {
-            log("getGroupUuidMapByDevice()");
-        }
+        if (VDBG) log("getGroupUuidMapByDevice()");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                return service.getGroupUuidMapByDevice(device, mAttributionSource);
+        final Map defaultValue = new HashMap<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Map> recv = new SynchronousResultReceiver();
+                service.getGroupUuidMapByDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return new HashMap<>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new HashMap<>();
         }
+        return defaultValue;
     }
 
     /**
@@ -376,22 +379,23 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public @NonNull List<Integer> getAllGroupIds(@Nullable ParcelUuid uuid) {
-        if (VDBG) {
-            log("getAllGroupIds()");
-        }
+        if (VDBG) log("getAllGroupIds()");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                return service.getAllGroupIds(uuid, mAttributionSource);
+        final List<Integer> defaultValue = new ArrayList<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<Integer>> recv =
+                        new SynchronousResultReceiver();
+                service.getAllGroupIds(uuid, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return new ArrayList<Integer>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<Integer>();
         }
+        return defaultValue;
     }
 
     /**
@@ -399,22 +403,23 @@
      */
     @Override
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
-        if (VDBG) {
-            log("getConnectedDevices()");
-        }
+        if (VDBG) log("getConnectedDevices()");
         final IBluetoothCsipSetCoordinator service = getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.getConnectedDevices(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -422,24 +427,24 @@
      */
     @Override
     public
-    @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
-            @NonNull int[] states) {
-        if (VDBG) {
-            log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")");
-        }
+    @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
+        if (VDBG) log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")");
         final IBluetoothCsipSetCoordinator service = getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.getDevicesMatchingConnectionStates(states, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -447,24 +452,23 @@
      */
     @Override
     public
-    @BluetoothProfile.BtProfileState int getConnectionState(
-            @Nullable BluetoothDevice device) {
-        if (VDBG) {
-            log("getState(" + device + ")");
-        }
+    @BluetoothProfile.BtProfileState int getConnectionState(@Nullable BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
         final IBluetoothCsipSetCoordinator service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-        }
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -484,26 +488,24 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(
             @Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
-        if (DBG) {
-            log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        }
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -521,22 +523,22 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
-        if (VDBG) {
-            log("getConnectionPolicy(" + device + ")");
-        }
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.getConnectionPolicy(device, mAttributionSource);
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         }
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 9ff4dc3..93f0268 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1177,7 +1177,7 @@
 
         mAddress = address;
         mAddressType = ADDRESS_TYPE_PUBLIC;
-        mAttributionSource = BluetoothManager.resolveAttributionSource(null);
+        mAttributionSource = AttributionSource.myAttributionSource();
     }
 
     /** {@hide} */
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 17c02cd..f2a6276 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -31,7 +33,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -41,8 +42,11 @@
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Public API for controlling the Bluetooth Headset Service. This includes both
@@ -479,16 +483,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connectWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -520,16 +528,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnectWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -541,18 +553,23 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevicesWithAttribution(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevicesWithAttribution(mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -564,18 +581,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -587,16 +609,20 @@
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionStateWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -622,20 +648,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -655,18 +683,7 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return BluetoothAdapter.connectionPolicyToPriority(
-                        service.getPriority(device, mAttributionSource));
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.PRIORITY_OFF;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.PRIORITY_OFF;
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
     }
 
     /**
@@ -689,16 +706,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     /**
@@ -713,15 +734,20 @@
     public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
         if (DBG) log("isNoiseReductionSupported()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.isNoiseReductionSupported(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isNoiseReductionSupported(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -736,15 +762,20 @@
     public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
         if (DBG) log("isVoiceRecognitionSupported()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.isVoiceRecognitionSupported(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isVoiceRecognitionSupported(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -775,15 +806,20 @@
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.startVoiceRecognition(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.startVoiceRecognition(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -804,15 +840,20 @@
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.stopVoiceRecognition(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.stopVoiceRecognition(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -827,15 +868,20 @@
     public boolean isAudioConnected(BluetoothDevice device) {
         if (VDBG) log("isAudioConnected()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.isAudioConnected(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isAudioConnected(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -861,17 +907,20 @@
     public int getAudioState(BluetoothDevice device) {
         if (VDBG) log("getAudioState");
         final IBluetoothHeadset service = mService;
-        if (service != null && !isDisabled()) {
-            try {
-                return service.getAudioState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final int defaultValue = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (!isDisabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getAudioState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -889,15 +938,17 @@
     public void setAudioRouteAllowed(boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                service.setAudioRouteAllowed(allowed, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setAudioRouteAllowed(allowed, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 
@@ -912,17 +963,20 @@
     public boolean getAudioRouteAllowed() {
         if (VDBG) log("getAudioRouteAllowed");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.getAudioRouteAllowed(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getAudioRouteAllowed(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -937,15 +991,17 @@
     public void setForceScoAudio(boolean forced) {
         if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                service.setForceScoAudio(forced, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setForceScoAudio(forced, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 
@@ -962,16 +1018,20 @@
     public boolean isAudioOn() {
         if (VDBG) log("isAudioOn()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                return service.isAudioOn(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isAudioOn(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-
+        return defaultValue;
     }
 
     /**
@@ -996,18 +1056,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connectAudio() {
+        if (VDBG) log("connectAudio()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.connectAudio(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connectAudio(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1025,18 +1089,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnectAudio() {
+        if (VDBG) log("disconnectAudio()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.disconnectAudio(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnectAudio(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1070,17 +1138,20 @@
     public boolean startScoUsingVirtualVoiceCall() {
         if (DBG) log("startScoUsingVirtualVoiceCall()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.startScoUsingVirtualVoiceCall(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.startScoUsingVirtualVoiceCall(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1105,17 +1176,20 @@
     public boolean stopScoUsingVirtualVoiceCall() {
         if (DBG) log("stopScoUsingVirtualVoiceCall()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.stopScoUsingVirtualVoiceCall(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.stopScoUsingVirtualVoiceCall(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1135,16 +1209,16 @@
     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
             int type, String name) {
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
                 service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
+            } catch (RemoteException  e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
         }
     }
 
@@ -1161,16 +1235,18 @@
     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
             String number, int type) {
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                service.clccResponse(index, direction, status, mode, mpty, number, type,
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.clccResponse(index, direction, status, mode, mpty, number, type,
+                        mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 
@@ -1202,18 +1278,21 @@
             throw new IllegalArgumentException("command is null");
         }
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.sendVendorSpecificResultCode(device, command, arg,
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendVendorSpecificResultCode(device, command, arg,
+                        mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1247,17 +1326,20 @@
             Log.d(TAG, "setActiveDevice: " + device);
         }
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
-            try {
-                return service.setActiveDevice(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && (device == null || isValidDevice(device))) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setActiveDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1273,22 +1355,25 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothDevice getActiveDevice() {
-        if (VDBG) {
-            Log.d(TAG, "getActiveDevice");
-        }
+        if (VDBG) Log.d(TAG, "getActiveDevice");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getActiveDevice(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+        final BluetoothDevice defaultValue = null;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BluetoothDevice> recv =
+                        new SynchronousResultReceiver();
+                service.getActiveDevice(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -1303,21 +1388,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean isInbandRingingEnabled() {
-        if (DBG) {
-            log("isInbandRingingEnabled()");
-        }
+        if (DBG) log("isInbandRingingEnabled()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.isInbandRingingEnabled(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isInbandRingingEnabled(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1337,7 +1423,7 @@
         @Override
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
+            mService = IBluetoothHeadset.Stub.asInterface(service);
             mHandler.sendMessage(mHandler.obtainMessage(
                     MESSAGE_HEADSET_SERVICE_CONNECTED));
         }
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index 2ef3710..7d7a7f7 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -25,15 +27,17 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Public API to control Hands Free Profile (HFP role only).
@@ -432,7 +436,7 @@
                     "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) {
                 @Override
                 public IBluetoothHeadsetClient getServiceInterface(IBinder service) {
-                    return IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothHeadsetClient.Stub.asInterface(service);
                 }
     };
 
@@ -479,18 +483,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -507,18 +514,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -531,19 +541,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -558,20 +573,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -585,18 +604,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -634,22 +656,23 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -686,18 +709,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final @ConnectionPolicy int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     /**
@@ -715,17 +741,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.startVoiceRecognition(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.startVoiceRecognition(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -739,20 +769,23 @@
      */
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
-    public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId,
-                                             String atCommand) {
+    public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
         if (DBG) log("sendVendorSpecificCommand()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -770,17 +803,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.stopVoiceRecognition(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.stopVoiceRecognition(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -793,18 +830,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
         if (DBG) log("getCurrentCalls()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final List<BluetoothHeadsetClientCall> defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
+                final SynchronousResultReceiver<List<BluetoothHeadsetClientCall>> recv =
+                        new SynchronousResultReceiver();
+                service.getCurrentCalls(device, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getCurrentCalls(device, mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -817,17 +860,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public Bundle getCurrentAgEvents(BluetoothDevice device) {
         if (DBG) log("getCurrentAgEvents()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final Bundle defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getCurrentAgEvents(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver();
+                service.getCurrentAgEvents(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -844,17 +891,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean acceptCall(BluetoothDevice device, int flag) {
         if (DBG) log("acceptCall()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.acceptCall(device, flag, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.acceptCall(device, flag, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -868,17 +919,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean holdCall(BluetoothDevice device) {
         if (DBG) log("holdCall()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.holdCall(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.holdCall(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -897,17 +952,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean rejectCall(BluetoothDevice device) {
         if (DBG) log("rejectCall()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.rejectCall(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.rejectCall(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -930,17 +989,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
         if (DBG) log("terminateCall()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.terminateCall(device, call, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.terminateCall(device, call, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -961,17 +1024,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean enterPrivateMode(BluetoothDevice device, int index) {
         if (DBG) log("enterPrivateMode()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.enterPrivateMode(device, index, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.enterPrivateMode(device, index, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -991,17 +1058,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean explicitCallTransfer(BluetoothDevice device) {
         if (DBG) log("explicitCallTransfer()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.explicitCallTransfer(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.explicitCallTransfer(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1017,18 +1088,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
         if (DBG) log("dial()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final BluetoothHeadsetClientCall defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
+                final SynchronousResultReceiver<BluetoothHeadsetClientCall> recv =
+                        new SynchronousResultReceiver();
+                service.dial(device, number, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.dial(device, number, mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -1045,17 +1122,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendDTMF(BluetoothDevice device, byte code) {
         if (DBG) log("sendDTMF()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendDTMF(device, code, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendDTMF(device, code, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1074,17 +1155,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean getLastVoiceTagNumber(BluetoothDevice device) {
         if (DBG) log("getLastVoiceTagNumber()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getLastVoiceTagNumber(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getLastVoiceTagNumber(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1097,17 +1182,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getAudioState(BluetoothDevice device) {
         if (VDBG) log("getAudioState");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = getService();
+        final int defaultValue = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                return service.getAudioState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getAudioState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            return defaultValue;
         }
         return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
     }
@@ -1123,17 +1212,18 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                service.setAudioRouteAllowed(device, allowed, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final IBluetoothHeadsetClient service = getService();
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setAudioRouteAllowed(device, allowed, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 
@@ -1148,19 +1238,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean getAudioRouteAllowed(BluetoothDevice device) {
         if (VDBG) log("getAudioRouteAllowed");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.getAudioRouteAllowed(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getAudioRouteAllowed(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1175,19 +1267,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connectAudio(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.connectAudio(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (VDBG) log("connectAudio");
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connectAudio(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1202,19 +1297,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnectAudio(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.disconnectAudio(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (VDBG) log("disconnectAudio");
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnectAudio(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1226,19 +1324,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.getCurrentAgFeatures(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (VDBG) log("getCurrentAgFeatures");
+        final IBluetoothHeadsetClient service = getService();
+        final Bundle defaultValue = null;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver();
+                service.getCurrentAgFeatures(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return null;
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index a00b20d..339a75f 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -28,14 +30,16 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Hearing Aid profile.
@@ -136,7 +140,7 @@
                     "BluetoothHearingAid", IBluetoothHearingAid.class.getName()) {
                 @Override
                 public IBluetoothHearingAid getServiceInterface(IBinder service) {
-                    return IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothHearingAid.Stub.asInterface(service);
                 }
     };
 
@@ -181,16 +185,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.connect(device, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -223,16 +231,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.disconnect(device, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -244,17 +256,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -267,18 +285,23 @@
     @NonNull int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -291,17 +314,20 @@
     @NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionState(device, mAttributionSource);
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.STATE_DISCONNECTED;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.STATE_DISCONNECTED;
         }
+        return defaultValue;
     }
 
     /**
@@ -330,18 +356,20 @@
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && ((device == null) || isValidDevice(device))) {
-                service.setActiveDevice(device, mAttributionSource);
-                return true;
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && ((device == null) || isValidDevice(device))) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setActiveDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -359,17 +387,23 @@
     public @NonNull List<BluetoothDevice> getActiveDevices() {
         if (VDBG) log("getActiveDevices()");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getActiveDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getActiveDevices(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<>();
         }
+        return defaultValue;
     }
 
     /**
@@ -416,21 +450,22 @@
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         verifyDeviceNotNull(device, "setConnectionPolicy");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                    && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -474,17 +509,20 @@
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         verifyDeviceNotNull(device, "getConnectionPolicy");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionPolicy(device, mAttributionSource);
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         }
+        return defaultValue;
     }
 
     /**
@@ -519,19 +557,18 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setVolume(int volume) {
         if (DBG) Log.d(TAG, "setVolume(" + volume + ")");
-
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-                return;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setVolume(volume, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-
-            if (!isEnabled()) return;
-
-            service.setVolume(volume, mAttributionSource);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
         }
     }
 
@@ -552,24 +589,23 @@
             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
     })
     public long getHiSyncId(@NonNull BluetoothDevice device) {
-        if (VDBG) {
-            log("getHiSyncId(" + device + ")");
-        }
+        if (VDBG) log("getHiSyncId(" + device + ")");
         verifyDeviceNotNull(device, "getConnectionPolicy");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-                return HI_SYNC_ID_INVALID;
+        final long defaultValue = HI_SYNC_ID_INVALID;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Long> recv = new SynchronousResultReceiver();
+                service.getHiSyncId(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-
-            if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID;
-
-            return service.getHiSyncId(device, mAttributionSource);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return HI_SYNC_ID_INVALID;
         }
+        return defaultValue;
     }
 
     /**
@@ -583,21 +619,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getDeviceSide(BluetoothDevice device) {
-        if (VDBG) {
-            log("getDeviceSide(" + device + ")");
-        }
+        if (VDBG) log("getDeviceSide(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getDeviceSide(device, mAttributionSource);
+        final int defaultValue = SIDE_LEFT;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getDeviceSide(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return SIDE_LEFT;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return SIDE_LEFT;
         }
+        return defaultValue;
     }
 
     /**
@@ -611,21 +648,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getDeviceMode(BluetoothDevice device) {
-        if (VDBG) {
-            log("getDeviceMode(" + device + ")");
-        }
+        if (VDBG) log("getDeviceMode(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getDeviceMode(device, mAttributionSource);
+        final int defaultValue = MODE_MONAURAL;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getDeviceMode(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return MODE_MONAURAL;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return MODE_MONAURAL;
         }
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index f5b444f..44a355b 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -26,14 +28,16 @@
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Provides the public APIs to control the Bluetooth HID Device profile.
@@ -431,7 +435,7 @@
                     "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) {
                 @Override
                 public IBluetoothHidDevice getServiceInterface(IBinder service) {
-                    return IBluetoothHidDevice.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothHidDevice.Stub.asInterface(service);
                 }
     };
 
@@ -455,18 +459,23 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return new ArrayList<>();
+        return defaultValue;
     }
 
     /** {@inheritDoc} */
@@ -475,19 +484,23 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return new ArrayList<>();
+        return defaultValue;
     }
 
     /** {@inheritDoc} */
@@ -496,17 +509,20 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final int defaultValue = STATE_DISCONNECTED;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -555,18 +571,21 @@
         }
 
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource);
-                result = service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = result;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource);
+                service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource, recv);
+                result = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -582,20 +601,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean unregisterApp() {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.unregisterApp(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.unregisterApp(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -609,20 +629,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.sendReport(device, id, data, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendReport(device, id, data, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -637,20 +658,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.replyReport(device, type, id, data, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.replyReport(device, type, id, data, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -663,20 +685,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean reportError(BluetoothDevice device, byte error) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.reportError(device, error, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.reportError(device, error, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -689,18 +712,20 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public String getUserAppName() {
         final IBluetoothHidDevice service = getService();
-
-        if (service != null) {
-            try {
-                return service.getUserAppName(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final String defaultValue = "";
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<String> recv = new SynchronousResultReceiver();
+                service.getUserAppName(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return "";
+        return defaultValue;
     }
 
     /**
@@ -714,20 +739,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(BluetoothDevice device) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -740,20 +766,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -781,23 +808,24 @@
     })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
-        log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        try {
-            final IBluetoothHidDevice service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothHidDevice service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index 121aa16..ecbeddf 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -28,13 +30,15 @@
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 
 /**
@@ -244,7 +248,7 @@
                     "BluetoothHidHost", IBluetoothHidHost.class.getName()) {
                 @Override
                 public IBluetoothHidHost getServiceInterface(IBinder service) {
-                    return IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothHidHost.Stub.asInterface(service);
                 }
     };
 
@@ -292,16 +296,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -334,16 +342,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -358,17 +370,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -382,18 +400,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -411,16 +434,20 @@
             throw new IllegalArgumentException("device must not be null");
         }
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -469,20 +496,22 @@
             throw new IllegalArgumentException("device must not be null");
         }
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -528,16 +557,20 @@
             throw new IllegalArgumentException("device must not be null");
         }
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     private boolean isEnabled() {
@@ -561,18 +594,20 @@
     public boolean virtualUnplug(BluetoothDevice device) {
         if (DBG) log("virtualUnplug(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.virtualUnplug(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.virtualUnplug(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-
+        return defaultValue;
     }
 
     /**
@@ -588,16 +623,20 @@
     public boolean getProtocolMode(BluetoothDevice device) {
         if (VDBG) log("getProtocolMode(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getProtocolMode(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getProtocolMode(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -613,16 +652,20 @@
     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
         if (DBG) log("setProtocolMode(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.setProtocolMode(device, protocolMode, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setProtocolMode(device, protocolMode, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -645,17 +688,21 @@
                     + "bufferSize=" + bufferSize);
         }
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getReport(device, reportType, reportId, bufferSize,
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getReport(device, reportType, reportId, bufferSize, mAttributionSource,
+                        recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -673,16 +720,20 @@
     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.setReport(device, reportType, report, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setReport(device, reportType, report, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -699,16 +750,20 @@
     public boolean sendData(BluetoothDevice device, String report) {
         if (DBG) log("sendData(" + device + "), report=" + report);
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendData(device, report, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendData(device, report, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -724,16 +779,20 @@
     public boolean getIdleTime(BluetoothDevice device) {
         if (DBG) log("getIdletime(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getIdleTime(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getIdleTime(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -750,16 +809,20 @@
     public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
         if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.setIdleTime(device, idleTime, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setIdleTime(device, idleTime, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java
index 34398eb..15db686 100644
--- a/core/java/android/bluetooth/BluetoothLeAudio.java
+++ b/core/java/android/bluetooth/BluetoothLeAudio.java
@@ -17,6 +17,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,14 +29,16 @@
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the LeAudio profile.
@@ -331,7 +335,7 @@
                     IBluetoothLeAudio.class.getName()) {
                 @Override
                 public IBluetoothLeAudio getServiceInterface(IBinder service) {
-                    return IBluetoothLeAudio.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothLeAudio.Stub.asInterface(service);
                 }
     };
 
@@ -385,17 +389,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(@Nullable BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled() && isValidDevice(device)) {
-                return service.connect(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -425,17 +433,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(@Nullable BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled() && isValidDevice(device)) {
-                return service.disconnect(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -446,18 +458,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
+        final IBluetoothLeAudio service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -469,19 +487,24 @@
     public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
             @NonNull int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
+        final IBluetoothLeAudio service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -493,18 +516,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionState(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.STATE_DISCONNECTED;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.STATE_DISCONNECTED;
         }
+        return defaultValue;
     }
 
     /**
@@ -531,19 +557,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()
-                    && ((device == null) || isValidDevice(device))) {
-                service.setActiveDevice(device, mAttributionSource);
-                return true;
+        final IBluetoothLeAudio service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && ((device == null) || isValidDevice(device))) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setActiveDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -557,19 +585,25 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getActiveDevices() {
-        if (VDBG) log("getActiveDevices()");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
+        if (VDBG) log("getActiveDevice()");
+        final IBluetoothLeAudio service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getActiveDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getActiveDevices(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<>();
         }
+        return defaultValue;
     }
 
     /**
@@ -583,17 +617,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getGroupId(@NonNull BluetoothDevice device) {
         if (VDBG) log("getGroupId()");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
-                return service.getGroupId(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final int defaultValue = GROUP_ID_INVALID;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getGroupId(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return GROUP_ID_INVALID;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return GROUP_ID_INVALID;
         }
+        return defaultValue;
     }
 
     /**
@@ -606,17 +644,18 @@
     @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED})
     public void setVolume(int volume) {
         if (VDBG) log("setVolume(vol: " + volume + " )");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
-                service.setVolume(volume, mAttributionSource);
-                return;
+        final IBluetoothLeAudio service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setVolume(volume, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return;
         }
     }
 
@@ -635,16 +674,20 @@
     public boolean groupAddNode(int group_id, @NonNull BluetoothDevice device) {
         if (VDBG) log("groupAddNode()");
         final IBluetoothLeAudio service = getService();
-        try {
-            if (service != null && mAdapter.isEnabled()) {
-                return service.groupAddNode(group_id, device, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.groupAddNode(group_id, device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -663,16 +706,20 @@
     public boolean groupRemoveNode(int group_id, @NonNull BluetoothDevice device) {
         if (VDBG) log("groupRemoveNode()");
         final IBluetoothLeAudio service = getService();
-        try {
-            if (service != null && mAdapter.isEnabled()) {
-                return service.groupRemoveNode(group_id, device, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.groupRemoveNode(group_id, device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -695,22 +742,23 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)
+                    && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -728,18 +776,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionPolicy(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         }
+        return defaultValue;
     }
 
 
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index c93de41..fef6f22 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -16,14 +16,10 @@
 
 package android.bluetooth;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresNoPermission;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
-import android.app.ActivityThread;
-import android.app.AppGlobals;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.content.AttributionSource;
@@ -68,37 +64,11 @@
      * @hide
      */
     public BluetoothManager(Context context) {
-        mAttributionSource = resolveAttributionSource(context);
+        mAttributionSource = (context != null) ? context.getAttributionSource() :
+                AttributionSource.myAttributionSource();
         mAdapter = BluetoothAdapter.createAdapter(mAttributionSource);
     }
 
-    /** {@hide} */
-    public static @NonNull AttributionSource resolveAttributionSource(@Nullable Context context) {
-        AttributionSource res = null;
-        if (context != null) {
-            res = context.getAttributionSource();
-        }
-        if (res == null) {
-            res = ActivityThread.currentAttributionSource();
-        }
-        if (res == null) {
-            int uid = android.os.Process.myUid();
-            if (uid == android.os.Process.ROOT_UID) {
-                uid = android.os.Process.SYSTEM_UID;
-            }
-            try {
-                res = new AttributionSource.Builder(uid)
-                    .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
-                    .build();
-            } catch (RemoteException ignored) {
-            }
-        }
-        if (res == null) {
-            throw new IllegalStateException("Failed to resolve AttributionSource");
-        }
-        return res;
-    }
-
     /**
      * Get the BLUETOOTH Adapter for this device.
      *
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 474e41f..56e4972 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
@@ -28,15 +30,17 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth MAP
@@ -87,7 +91,7 @@
                     "BluetoothMap", IBluetoothMap.class.getName()) {
                 @Override
                 public IBluetoothMap getServiceInterface(IBinder service) {
-                    return IBluetoothMap.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothMap.Stub.asInterface(service);
                 }
     };
 
@@ -142,17 +146,20 @@
     public int getState() {
         if (VDBG) log("getState()");
         final IBluetoothMap service = getService();
-        if (service != null) {
-            try {
-                return service.getState(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final int defaultValue = BluetoothMap.STATE_ERROR;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getState(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothMap.STATE_ERROR;
+        return defaultValue;
     }
 
     /**
@@ -168,18 +175,23 @@
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
         final IBluetoothMap service = getService();
-        if (service != null) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getClient(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final BluetoothDevice defaultValue = null;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BluetoothDevice> recv =
+                        new SynchronousResultReceiver();
+                service.getClient(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -194,17 +206,20 @@
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
         final IBluetoothMap service = getService();
-        if (service != null) {
-            try {
-                return service.isConnected(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isConnected(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -233,16 +248,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -284,17 +303,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -309,18 +334,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -335,16 +365,21 @@
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -390,20 +425,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                    && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -446,16 +483,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 8a3f801..03536f9a 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,15 +31,17 @@
 import android.content.AttributionSource;
 import android.content.Context;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth MAP MCE Profile.
@@ -180,7 +184,7 @@
                     "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
                 @Override
                 public IBluetoothMapClient getServiceInterface(IBinder service) {
-                    return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothMapClient.Stub.asInterface(service);
                 }
     };
 
@@ -221,17 +225,20 @@
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null) {
-            try {
-                return service.isConnected(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isConnected(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -248,17 +255,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
         final IBluetoothMapClient service = getService();
-        if (service != null) {
-            try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -277,15 +287,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "disconnect(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -300,17 +315,23 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) Log.d(TAG, "getConnectedDevices()");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<>();
+        return defaultValue;
     }
 
     /**
@@ -325,18 +346,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<>();
+        return defaultValue;
     }
 
     /**
@@ -351,16 +377,20 @@
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue =  BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver<>();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -405,20 +435,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -460,16 +492,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     /**
@@ -494,18 +530,8 @@
     public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts,
             @NonNull String message, @Nullable PendingIntent sentIntent,
             @Nullable PendingIntent deliveredIntent) {
-        if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
-        final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.sendMessage(device, contacts.toArray(new Uri[contacts.size()]),
-                        message, sentIntent, deliveredIntent, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        return false;
+        return sendMessage(device, contacts.toArray(new Uri[contacts.size()]), message, sentIntent,
+                deliveredIntent);
     }
 
      /**
@@ -531,16 +557,21 @@
             PendingIntent sentIntent, PendingIntent deliveredIntent) {
         if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent,
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendMessage(device, contacts, message, sentIntent, deliveredIntent,
+                        mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -558,15 +589,20 @@
     public boolean getUnreadMessages(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getUnreadMessages(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getUnreadMessages(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -580,13 +616,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isUploadingSupported(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "isUploadingSupported(" + device + ")");
         final IBluetoothMapClient service = getService();
-        try {
-            return (service != null && isEnabled() && isValidDevice(device))
-                    && ((service.getSupportedFeatures(device, mAttributionSource)
-                            & UPLOADING_FEATURE_BITMASK) > 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, e.getMessage());
+        final int defaultValue = 0;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getSupportedFeatures(device, mAttributionSource, recv);
+                return (recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue)
+                        & UPLOADING_FEATURE_BITMASK) > 0;
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
         return false;
     }
@@ -615,16 +659,21 @@
     public boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
         if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device) && handle != null &&
-            (status == READ || status == UNREAD || status == UNDELETED  || status == DELETED)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device) && handle != null && (status == READ
+                    || status == UNREAD || status == UNDELETED  || status == DELETED)) {
             try {
-                return service.setMessageStatus(device, handle, status, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setMessageStatus(device, handle, status, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return false;
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index ac7a52d..d4ad4ef4 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -28,16 +30,18 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth Pan
@@ -188,7 +192,7 @@
                     "BluetoothPan", IBluetoothPan.class.getName()) {
                 @Override
                 public IBluetoothPan getServiceInterface(IBinder service) {
-                    return IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothPan.Stub.asInterface(service);
                 }
     };
 
@@ -249,16 +253,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -289,16 +297,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -322,22 +334,23 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        try {
-            final IBluetoothPan service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final IBluetoothPan service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -354,17 +367,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -381,18 +400,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -409,16 +433,20 @@
     public int getConnectionState(@NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -438,11 +466,16 @@
         String pkgName = mContext.getOpPackageName();
         if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled()) {
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                service.setBluetoothTethering(value, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setBluetoothTethering(value, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
     }
@@ -459,14 +492,20 @@
     public boolean isTetheringOn() {
         if (VDBG) log("isTetheringOn()");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled()) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                return service.isTetheringOn(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isTetheringOn(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return false;
+        return defaultValue;
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index cc91ad2..e096de8 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -24,13 +26,15 @@
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth PBAP Client Profile.
@@ -64,7 +68,7 @@
                     "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) {
                 @Override
                 public IBluetoothPbapClient getServiceInterface(IBinder service) {
-                    return IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothPbapClient.Stub.asInterface(service);
                 }
     };
 
@@ -123,18 +127,20 @@
             log("connect(" + device + ") for PBAP Client.");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -155,19 +161,21 @@
             log("disconnect(" + device + ")" + new Exception());
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                service.disconnect(device, mAttributionSource);
-                return true;
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
+        final boolean defaultValue = true;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+                return true;
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -184,19 +192,23 @@
             log("getConnectedDevices()");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled()) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -212,20 +224,23 @@
             log("getDevicesMatchingStates()");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled()) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -241,18 +256,20 @@
             log("getConnectionState(" + device + ")");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-        }
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     private static void log(String msg) {
@@ -311,22 +328,22 @@
             log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
-            try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -370,17 +387,19 @@
             log("getConnectionPolicy(" + device + ")");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-            }
-        }
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index ab2b8ea..808fa39 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.RequiresNoPermission;
 import android.annotation.RequiresPermission;
@@ -26,14 +28,16 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth SIM
@@ -104,7 +108,7 @@
                     "BluetoothSap", IBluetoothSap.class.getName()) {
                 @Override
                 public IBluetoothSap getServiceInterface(IBinder service) {
-                    return IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothSap.Stub.asInterface(service);
                 }
     };
 
@@ -155,17 +159,20 @@
     public int getState() {
         if (VDBG) log("getState()");
         final IBluetoothSap service = getService();
-        if (service != null) {
-            try {
-                return service.getState(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final int defaultValue = BluetoothSap.STATE_ERROR;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getState(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothSap.STATE_ERROR;
+        return defaultValue;
     }
 
     /**
@@ -180,18 +187,23 @@
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
         final IBluetoothSap service = getService();
-        if (service != null) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getClient(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final BluetoothDevice defaultValue = null;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BluetoothDevice> recv =
+                        new SynchronousResultReceiver();
+                service.getClient(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -206,17 +218,20 @@
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
         final IBluetoothSap service = getService();
-        if (service != null) {
-            try {
-                return service.isConnected(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isConnected(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -244,16 +259,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -267,17 +286,23 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -291,18 +316,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -316,16 +346,20 @@
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -370,20 +404,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -425,16 +461,20 @@
     public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothUtils.java b/core/java/android/bluetooth/BluetoothUtils.java
new file mode 100644
index 0000000..8674692
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothUtils.java
@@ -0,0 +1,41 @@
+/*
+ * 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.bluetooth;
+
+import java.time.Duration;
+
+/**
+ * {@hide}
+ */
+public final class BluetoothUtils {
+    /**
+     * This utility class cannot be instantiated
+     */
+    private BluetoothUtils() {}
+
+    /**
+     * Timeout value for synchronous binder call
+     */
+    private static final Duration SYNC_CALLS_TIMEOUT = Duration.ofSeconds(5);
+
+    /**
+     * @return timeout value for synchronous binder call
+     */
+    static Duration getSyncTimeout() {
+        return SYNC_CALLS_TIMEOUT;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothVolumeControl.java b/core/java/android/bluetooth/BluetoothVolumeControl.java
index ba83eca..27532aa 100644
--- a/core/java/android/bluetooth/BluetoothVolumeControl.java
+++ b/core/java/android/bluetooth/BluetoothVolumeControl.java
@@ -17,6 +17,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -29,14 +31,16 @@
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Bluetooth Volume Control service.
@@ -86,7 +90,7 @@
                     IBluetoothVolumeControl.class.getName()) {
                 @Override
                 public IBluetoothVolumeControl getServiceInterface(IBinder service) {
-                    return IBluetoothVolumeControl.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothVolumeControl.Stub.asInterface(service);
                 }
             };
 
@@ -134,17 +138,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -159,18 +169,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -185,16 +200,20 @@
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -212,18 +231,19 @@
     })
     public void setVolume(@Nullable BluetoothDevice device,
             @IntRange(from = 0, to = 255) int volume) {
-        if (DBG)
-            log("setVolume(" + volume + ")");
+        if (DBG) log("setVolume(" + volume + ")");
         final IBluetoothVolumeControl service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                service.setVolume(device, volume, mAttributionSource);
-                return;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setVolume(device, volume, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null)
-                Log.w(TAG, "Proxy not attached to service");
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
         }
     }
 
@@ -249,20 +269,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -285,16 +307,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 6ae2bb5..157e709 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
+import android.app.AppGlobals;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -191,10 +192,42 @@
         return new ScopedParcelState(this);
     }
 
-    /** @hide */
-    public static AttributionSource myAttributionSource() {
-        return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(),
-                /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null);
+    /**
+     * Returns a generic {@link AttributionSource} that represents the entire
+     * calling process.
+     *
+     * <p>Callers are <em>strongly</em> encouraged to use a more specific
+     * attribution source whenever possible, such as from
+     * {@link Context#getAttributionSource()}, since that enables developers to
+     * have more detailed and scoped control over attribution within
+     * sub-components of their app.
+     *
+     * @see Context#createAttributionContext(String)
+     * @see Context#getAttributionTag()
+     * @return a generic {@link AttributionSource} representing the entire
+     *         calling process
+     * @throws IllegalStateException when no accurate {@link AttributionSource}
+     *         can be determined
+     */
+    public static @NonNull AttributionSource myAttributionSource() {
+
+        final AttributionSource globalSource = ActivityThread.currentAttributionSource();
+        if (globalSource != null) {
+            return globalSource;
+        }
+
+        int uid = Process.myUid();
+        if (uid == Process.ROOT_UID) {
+            uid = Process.SYSTEM_UID;
+        }
+        try {
+            return new AttributionSource.Builder(uid)
+                .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
+                .build();
+        } catch (Exception ignored) {
+        }
+
+        throw new IllegalStateException("Failed to resolve AttributionSource");
     }
 
     /**
@@ -247,7 +280,7 @@
      * whether the attribution source is one for the calling app to prevent the caller
      * to pass you a source from another app without including themselves in the
      * attribution chain.
-     *f
+     *
      * @return if the attribution source cannot be trusted to be from the caller.
      */
     public boolean checkCallingUid() {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 983d0cc..1e6029f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5915,6 +5915,14 @@
     public static final String EXTRA_UID = "android.intent.extra.UID";
 
     /**
+     * Used as an optional int extra field in {@link android.content.Intent#ACTION_PACKAGE_ADDED}
+     * intents to supply the previous uid the package had been assigned.
+     * This would only be set when a package is leaving sharedUserId in an upgrade, or when a
+     * system app upgrade that had left sharedUserId is getting uninstalled.
+     */
+    public static final String EXTRA_PREVIOUS_UID = "android.intent.extra.PREVIOUS_UID";
+
+    /**
      * @hide String array of package names.
      */
     @SystemApi
@@ -5946,6 +5954,16 @@
     public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
 
     /**
+     * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED},
+     * {@link android.content.Intent#ACTION_UID_REMOVED}, and
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED}
+     * intents to indicate that this package is changing its UID.
+     * This would only be set when a package is leaving sharedUserId in an upgrade, or when a
+     * system app upgrade that had left sharedUserId is getting uninstalled.
+     */
+    public static final String EXTRA_UID_CHANGING = "android.intent.extra.UID_CHANGING";
+
+    /**
      * Used as an int extra field in {@link android.app.AlarmManager} pending intents
      * to tell the application being invoked how many pending alarms are being
      * delivered with the intent.  For one-shot alarms this will always be 1.
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 8ebb8ec..01bf49e 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2432,27 +2432,10 @@
                 break;
         }
 
-        switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) {
-            case Configuration.UI_MODE_TYPE_APPLIANCE:
-                parts.add("appliance");
-                break;
-            case Configuration.UI_MODE_TYPE_DESK:
-                parts.add("desk");
-                break;
-            case Configuration.UI_MODE_TYPE_TELEVISION:
-                parts.add("television");
-                break;
-            case Configuration.UI_MODE_TYPE_CAR:
-                parts.add("car");
-                break;
-            case Configuration.UI_MODE_TYPE_WATCH:
-                parts.add("watch");
-                break;
-            case Configuration.UI_MODE_TYPE_VR_HEADSET:
-                parts.add("vrheadset");
-                break;
-            default:
-                break;
+        final String uiModeTypeString =
+                getUiModeTypeString(config.uiMode & Configuration.UI_MODE_TYPE_MASK);
+        if (uiModeTypeString != null) {
+            parts.add(uiModeTypeString);
         }
 
         switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) {
@@ -2587,6 +2570,28 @@
     }
 
     /**
+     * @hide
+     */
+    public static String getUiModeTypeString(int uiModeType) {
+        switch (uiModeType) {
+            case Configuration.UI_MODE_TYPE_APPLIANCE:
+                return "appliance";
+            case Configuration.UI_MODE_TYPE_DESK:
+                return "desk";
+            case Configuration.UI_MODE_TYPE_TELEVISION:
+                return "television";
+            case Configuration.UI_MODE_TYPE_CAR:
+                return "car";
+            case Configuration.UI_MODE_TYPE_WATCH:
+                return "watch";
+            case Configuration.UI_MODE_TYPE_VR_HEADSET:
+                return "vrheadset";
+            default:
+                return null;
+        }
+    }
+
+    /**
      * Generate a delta Configuration between <code>base</code> and <code>change</code>. The
      * resulting delta can be used with {@link #updateFrom(Configuration)}.
      * <p />
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index ba9332d..c607195 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -161,6 +161,7 @@
      */
     @IntDef(prefix = {"PROCESS_STATE_"}, value = {
             PROCESS_STATE_ANY,
+            PROCESS_STATE_UNSPECIFIED,
             PROCESS_STATE_FOREGROUND,
             PROCESS_STATE_BACKGROUND,
             PROCESS_STATE_FOREGROUND_SERVICE,
@@ -169,7 +170,8 @@
     public @interface ProcessState {
     }
 
-    public static final int PROCESS_STATE_ANY = 0;
+    public static final int PROCESS_STATE_UNSPECIFIED = 0;
+    public static final int PROCESS_STATE_ANY = PROCESS_STATE_UNSPECIFIED;
     public static final int PROCESS_STATE_FOREGROUND = 1;
     public static final int PROCESS_STATE_BACKGROUND = 2;
     public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
@@ -180,7 +182,7 @@
 
     static {
         // Assign individually to avoid future mismatch
-        sProcessStateNames[PROCESS_STATE_ANY] = "any";
+        sProcessStateNames[PROCESS_STATE_UNSPECIFIED] = "unspecified";
         sProcessStateNames[PROCESS_STATE_FOREGROUND] = "fg";
         sProcessStateNames[PROCESS_STATE_BACKGROUND] = "bg";
         sProcessStateNames[PROCESS_STATE_FOREGROUND_SERVICE] = "fgs";
@@ -188,6 +190,7 @@
 
     private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = {
             POWER_COMPONENT_CPU,
+            POWER_COMPONENT_MOBILE_RADIO,
     };
 
     static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
@@ -213,7 +216,7 @@
                 sb.append("powerComponent=").append(sPowerComponentNames[powerComponent]);
                 dimensionSpecified = true;
             }
-            if (processState != PROCESS_STATE_ANY) {
+            if (processState != PROCESS_STATE_UNSPECIFIED) {
                 if (dimensionSpecified) {
                     sb.append(", ");
                 }
@@ -283,7 +286,7 @@
             if (mShortString == null) {
                 StringBuilder sb = new StringBuilder();
                 sb.append(powerComponentIdToString(powerComponent));
-                if (processState != PROCESS_STATE_ANY) {
+                if (processState != PROCESS_STATE_UNSPECIFIED) {
                     sb.append(':');
                     sb.append(processStateToString(processState));
                 }
@@ -333,7 +336,7 @@
      * for all values of other dimensions such as process state.
      */
     public Key getKey(@PowerComponent int componentId) {
-        return mData.getKey(componentId, PROCESS_STATE_ANY);
+        return mData.getKey(componentId, PROCESS_STATE_UNSPECIFIED);
     }
 
     /**
@@ -352,7 +355,7 @@
      */
     public double getConsumedPower(@PowerComponent int componentId) {
         return mPowerComponents.getConsumedPower(
-                mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY));
+                mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
     }
 
     /**
@@ -374,7 +377,7 @@
      */
     public @PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int componentId) {
         return mPowerComponents.getPowerModel(
-                mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY));
+                mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
     }
 
     /**
@@ -706,7 +709,7 @@
                     if (isSupported) {
                         for (int processState = 0; processState < PROCESS_STATE_COUNT;
                                 processState++) {
-                            if (processState == PROCESS_STATE_ANY) {
+                            if (processState == PROCESS_STATE_UNSPECIFIED) {
                                 continue;
                             }
 
@@ -789,7 +792,7 @@
         @NonNull
         public T setConsumedPower(@PowerComponent int componentId, double componentPower,
                 @PowerModel int powerModel) {
-            mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_ANY),
+            mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
                     componentPower, powerModel);
             return (T) this;
         }
@@ -825,8 +828,9 @@
         @NonNull
         public T setUsageDurationMillis(@UidBatteryConsumer.PowerComponent int componentId,
                 long componentUsageTimeMillis) {
-            mPowerComponentsBuilder.setUsageDurationMillis(getKey(componentId, PROCESS_STATE_ANY),
-                    componentUsageTimeMillis);
+            mPowerComponentsBuilder
+                    .setUsageDurationMillis(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
+                            componentUsageTimeMillis);
             return (T) this;
         }
 
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 584f3c4..fa209cc 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -669,7 +669,7 @@
             case BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE:
                 return BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
             default:
-                return BatteryConsumer.PROCESS_STATE_ANY;
+                return BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
         }
     }
 
@@ -963,6 +963,13 @@
         public abstract long getNetworkActivityPackets(int type, int which);
         @UnsupportedAppUsage
         public abstract long getMobileRadioActiveTime(int which);
+
+        /**
+         * Returns the amount of time (in microseconds) this UID was in the specified processState.
+         */
+        public abstract long getMobileRadioActiveTimeInProcessState(
+                @BatteryConsumer.ProcessState int processState);
+
         public abstract int getMobileRadioActiveCount(int which);
 
         /**
@@ -1061,6 +1068,16 @@
         public abstract long getMobileRadioMeasuredBatteryConsumptionUC();
 
         /**
+         * Returns the battery consumption (in microcoulombs) of the uid's radio usage when in the
+         * specified process state.
+         * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+         *
+         * {@hide}
+         */
+        public abstract long getMobileRadioMeasuredBatteryConsumptionUC(
+                @BatteryConsumer.ProcessState int processState);
+
+        /**
          * Returns the battery consumption (in microcoulombs) of the screen while on and uid active,
          * derived from on device power measurement data.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index ed44fb6..a23dae8 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -556,7 +556,7 @@
                 }
 
                 String label = BatteryConsumer.powerComponentIdToString(componentId);
-                if (key.processState != BatteryConsumer.PROCESS_STATE_ANY) {
+                if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
                     label = label
                             + "(" + BatteryConsumer.processStateToString(key.processState) + ")";
                 }
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index e863111..590494c 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -18,6 +18,7 @@
 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
 import static android.os.BatteryConsumer.POWER_COMPONENT_COUNT;
 import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
+import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
 import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
 
 import android.annotation.NonNull;
@@ -339,7 +340,7 @@
 
                 serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
                 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
-                if (key.processState != PROCESS_STATE_ANY) {
+                if (key.processState != PROCESS_STATE_UNSPECIFIED) {
                     serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
                             key.processState);
                 }
@@ -398,7 +399,7 @@
                 switch (parser.getName()) {
                     case BatteryUsageStats.XML_TAG_COMPONENT: {
                         int componentId = -1;
-                        int processState = PROCESS_STATE_ANY;
+                        int processState = PROCESS_STATE_UNSPECIFIED;
                         double powerMah = 0;
                         long durationMs = 0;
                         int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 563d6cc3..b3639e4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1546,6 +1546,17 @@
     private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
 
     /**
+     * Action to start an activity to create a supervised user.
+     * Only devices with non-empty config_supervisedUserCreationPackage support this.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USERS)
+    public static final String ACTION_CREATE_SUPERVISED_USER =
+            "android.os.action.CREATE_SUPERVISED_USER";
+
+    /**
      * Extra containing a name for the user being created. Optional parameter passed to
      * ACTION_CREATE_USER activity.
      * @hide
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a00dd51..e3c3969 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5453,5 +5453,12 @@
          */
         public static final String COLUMN_PHONE_NUMBER_SOURCE_IMS =
                 "phone_number_source_ims";
+
+        /**
+         * TelephonyProvider column name for the device's preferred usage setting.
+         *
+         * @hide
+         */
+        public static final String COLUMN_USAGE_SETTING = "usage_setting";
     }
 }
diff --git a/core/java/android/service/cloudsearch/OWNERS b/core/java/android/service/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/core/java/android/service/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
new file mode 100644
index 0000000..2bd99ac
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.service.selectiontoolbar;
+
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ShowInfo;
+
+/**
+ * The service to render the selection toolbar menus.
+ *
+ * @hide
+ */
+oneway interface ISelectionToolbarRenderService {
+    void onShow(in ShowInfo showInfo, in ISelectionToolbarCallback callback);
+    void onHide(long widgetToken);
+    void onDismiss(long widgetToken);
+}
diff --git a/core/java/android/service/selectiontoolbar/OWNERS b/core/java/android/service/selectiontoolbar/OWNERS
new file mode 100644
index 0000000..5500b92
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 709498
+
+augale@google.com
+joannechung@google.com
+licha@google.com
+lpeter@google.com
+svetoslavganov@google.com
+toki@google.com
+tonymak@google.com
+tymtsai@google.com
\ No newline at end of file
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
new file mode 100644
index 0000000..6468183
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
@@ -0,0 +1,53 @@
+/*
+ * 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.service.selectiontoolbar;
+
+import android.view.selectiontoolbar.ToolbarMenuItem;
+import android.view.selectiontoolbar.WidgetInfo;
+
+/**
+ * The callback that the render service uses to communicate with the host of the selection toolbar
+ * container.
+ *
+ * @hide
+ */
+public interface SelectionToolbarRenderCallback {
+    /**
+     * The selection toolbar is shown.
+     */
+    void onShown(WidgetInfo widgetInfo);
+    /**
+     * The selection toolbar is hidden.
+     */
+    void onHidden(long widgetToken);
+    /**
+     * The selection toolbar is dismissed.
+     */
+    void onDismissed(long widgetToken);
+    /**
+     * The selection toolbar has changed.
+     */
+    void onWidgetUpdated(WidgetInfo info);
+    /**
+     * The menu item on the selection toolbar has been clicked.
+     */
+    void onMenuItemClicked(ToolbarMenuItem item);
+    /**
+     * The error occurred when operating on the selection toolbar.
+     */
+    void onError(int errorCode);
+}
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
new file mode 100644
index 0000000..6f66c9f
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
@@ -0,0 +1,182 @@
+/*
+ * 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.service.selectiontoolbar;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ShowInfo;
+import android.view.selectiontoolbar.ToolbarMenuItem;
+import android.view.selectiontoolbar.WidgetInfo;
+
+/**
+ * Service for rendering selection toolbar.
+ *
+ * @hide
+ */
+public abstract class SelectionToolbarRenderService extends Service {
+
+    private static final String TAG = "SelectionToolbarRenderService";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     *
+     * <p>To be supported, the service must also require the
+     * {@link android.Manifest.permission#BIND_SELECTION_TOOLBAR_RENDER_SERVICE} permission so
+     * that other applications can not abuse it.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.selectiontoolbar.SelectionToolbarRenderService";
+
+    private Handler mHandler;
+
+    /**
+     * Binder to receive calls from system server.
+     */
+    private final ISelectionToolbarRenderService mInterface =
+            new ISelectionToolbarRenderService.Stub() {
+
+        @Override
+        public void onShow(ShowInfo showInfo, ISelectionToolbarCallback callback) {
+            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onShow,
+                    SelectionToolbarRenderService.this, showInfo,
+                    new RemoteCallbackWrapper(callback)));
+        }
+
+        @Override
+        public void onHide(long widgetToken) {
+            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onHide,
+                    SelectionToolbarRenderService.this, widgetToken));
+        }
+
+        @Override
+        public void onDismiss(long widgetToken) {
+            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onDismiss,
+                    SelectionToolbarRenderService.this, widgetToken));
+        }
+    };
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+    }
+
+    @Override
+    @Nullable
+    public final IBinder onBind(@NonNull Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+
+    /**
+     * Called when showing the selection toolbar.
+     */
+    public abstract void onShow(ShowInfo showInfo, RemoteCallbackWrapper callbackWrapper);
+
+    /**
+     * Called when hiding the selection toolbar.
+     */
+    public abstract void onHide(long widgetToken);
+
+
+    /**
+     * Called when dismissing the selection toolbar.
+     */
+    public abstract void onDismiss(long widgetToken);
+
+    /**
+     * Add avadoc.
+     */
+    public static final class RemoteCallbackWrapper implements SelectionToolbarRenderCallback {
+
+        private final ISelectionToolbarCallback mRemoteCallback;
+
+        RemoteCallbackWrapper(ISelectionToolbarCallback remoteCallback) {
+            mRemoteCallback = remoteCallback;
+        }
+
+        @Override
+        public void onShown(WidgetInfo widgetInfo) {
+            try {
+                mRemoteCallback.onShown(widgetInfo);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onHidden(long widgetToken) {
+            try {
+                mRemoteCallback.onHidden(widgetToken);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onDismissed(long widgetToken) {
+            try {
+                mRemoteCallback.onDismissed(widgetToken);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onWidgetUpdated(WidgetInfo widgetInfo) {
+            try {
+                mRemoteCallback.onWidgetUpdated(widgetInfo);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onMenuItemClicked(ToolbarMenuItem item) {
+            try {
+                mRemoteCallback.onMenuItemClicked(item);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onError(int errorCode) {
+            try {
+                mRemoteCallback.onError(errorCode);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+    }
+}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index e7f8920..9eaaa91 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -36,18 +36,24 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
+import android.telephony.TelephonyManager.CarrierPrivilegesListener;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.listeners.ListenerExecutor;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 
+import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
 
 /**
@@ -1214,4 +1220,117 @@
         listenFromCallback(false, false, subId,
                 pkgName, attributionTag, callback, new int[0], notifyNow);
     }
+
+    private static class CarrierPrivilegesListenerWrapper extends ICarrierPrivilegesListener.Stub
+            implements ListenerExecutor {
+        private final WeakReference<CarrierPrivilegesListener> mListener;
+        private final Executor mExecutor;
+
+        CarrierPrivilegesListenerWrapper(CarrierPrivilegesListener listener, Executor executor) {
+            mListener = new WeakReference<>(listener);
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCarrierPrivilegesChanged(
+                List<String> privilegedPackageNames, int[] privilegedUids) {
+            Binder.withCleanCallingIdentity(
+                    () ->
+                            executeSafely(
+                                    mExecutor,
+                                    mListener::get,
+                                    cpl ->
+                                            cpl.onCarrierPrivilegesChanged(
+                                                    privilegedPackageNames, privilegedUids)));
+        }
+    }
+
+    @GuardedBy("sCarrierPrivilegeListeners")
+    private static final WeakHashMap<
+                    CarrierPrivilegesListener, WeakReference<CarrierPrivilegesListenerWrapper>>
+            sCarrierPrivilegeListeners = new WeakHashMap<>();
+
+    /**
+     * Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to
+     * receive callbacks when the set of packages with carrier privileges changes. The callback will
+     * immediately be called with the latest state.
+     *
+     * @param logicalSlotIndex The SIM slot to listen on
+     * @param executor The executor where {@code listener} will be invoked
+     * @param listener The callback to register
+     */
+    public void addCarrierPrivilegesListener(
+            int logicalSlotIndex,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CarrierPrivilegesListener listener) {
+        if (listener == null || executor == null) {
+            throw new IllegalArgumentException("listener and executor must be non-null");
+        }
+        synchronized (sCarrierPrivilegeListeners) {
+            WeakReference<CarrierPrivilegesListenerWrapper> existing =
+                    sCarrierPrivilegeListeners.get(listener);
+            if (existing != null && existing.get() != null) {
+                Log.d(TAG, "addCarrierPrivilegesListener: listener already registered");
+                return;
+            }
+            CarrierPrivilegesListenerWrapper wrapper =
+                    new CarrierPrivilegesListenerWrapper(listener, executor);
+            sCarrierPrivilegeListeners.put(listener, new WeakReference<>(wrapper));
+            try {
+                sRegistry.addCarrierPrivilegesListener(
+                        logicalSlotIndex,
+                        wrapper,
+                        mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters a {@link CarrierPrivilegesListener}.
+     *
+     * @param listener The callback to unregister
+     */
+    public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must be non-null");
+        }
+        synchronized (sCarrierPrivilegeListeners) {
+            WeakReference<CarrierPrivilegesListenerWrapper> ref =
+                    sCarrierPrivilegeListeners.remove(listener);
+            if (ref == null) return;
+            CarrierPrivilegesListenerWrapper wrapper = ref.get();
+            if (wrapper == null) return;
+            try {
+                sRegistry.removeCarrierPrivilegesListener(wrapper, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Notify listeners that the set of packages with carrier privileges has changed.
+     *
+     * @param logicalSlotIndex The SIM slot the change occurred on
+     * @param privilegedPackageNames The updated set of packages names with carrier privileges
+     * @param privilegedUids The updated set of UIDs with carrier privileges
+     */
+    public void notifyCarrierPrivilegesChanged(
+            int logicalSlotIndex,
+            @NonNull List<String> privilegedPackageNames,
+            @NonNull int[] privilegedUids) {
+        if (privilegedPackageNames == null || privilegedUids == null) {
+            throw new IllegalArgumentException(
+                    "privilegedPackageNames and privilegedUids must be non-null");
+        }
+        try {
+            sRegistry.notifyCarrierPrivilegesChanged(
+                    logicalSlotIndex, privilegedPackageNames, privilegedUids);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index adb8b86..8db62f6 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1872,7 +1872,7 @@
             float x, float y, float pressure, float size, int metaState,
             float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
         return obtain(downTime, eventTime, action, x, y, pressure, size, metaState,
-                xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_UNKNOWN,
+                xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_CLASS_POINTER,
                 DEFAULT_DISPLAY);
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 70505fc..c371202 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -93,6 +93,7 @@
 import android.annotation.UiContext;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
+import android.app.ICompatCameraControlCallback;
 import android.app.ResourcesManager;
 import android.app.WindowConfiguration;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -317,7 +318,7 @@
     private static final ArrayList<ConfigChangedCallback> sConfigCallbacks = new ArrayList<>();
 
     /**
-     * Callback for notifying activities about override configuration changes.
+     * Callback for notifying activities.
      */
     public interface ActivityConfigCallback {
 
@@ -327,11 +328,23 @@
          * @param newDisplayId New display id, {@link Display#INVALID_DISPLAY} if not changed.
          */
         void onConfigurationChanged(Configuration overrideConfig, int newDisplayId);
+
+        /**
+         * Notify the corresponding activity about the request to show or hide a camera compat
+         * control for stretched issues in the viewfinder.
+         *
+         * @param showControl Whether the control should be shown or hidden.
+         * @param transformationApplied Whether the treatment is already applied.
+         * @param callback The callback executed when the user clicks on a control.
+         */
+        void requestCompatCameraControl(boolean showControl, boolean transformationApplied,
+                ICompatCameraControlCallback callback);
     }
 
     /**
-     * Callback used to notify corresponding activity about override configuration change and make
-     * sure that all resources are set correctly before updating the ViewRootImpl's internal state.
+     * Callback used to notify corresponding activity about camera compat control changes, override
+     * configuration change and make sure that all resources are set correctly before updating the
+     * ViewRootImpl's internal state.
      */
     private ActivityConfigCallback mActivityConfigCallback;
 
@@ -865,7 +878,10 @@
         }
     }
 
-    /** Add activity config callback to be notified about override config changes. */
+    /**
+     * Add activity config callback to be notified about override config changes and camera
+     * compat control state updates.
+     */
     public void setActivityConfigCallback(ActivityConfigCallback callback) {
         mActivityConfigCallback = callback;
     }
@@ -10498,6 +10514,20 @@
     }
 
     /**
+     * Shows or hides a Camera app compat toggle for stretched issues with the requested state
+     * for the corresponding activity.
+     *
+     * @param showControl Whether the control should be shown or hidden.
+     * @param transformationApplied Whether the treatment is already applied.
+     * @param callback The callback executed when the user clicks on a control.
+    */
+    public void requestCompatCameraControl(boolean showControl, boolean transformationApplied,
+                ICompatCameraControlCallback callback) {
+        mActivityConfigCallback.requestCompatCameraControl(
+                showControl, transformationApplied, callback);
+    }
+
+    /**
      * Redirect the next draw of this ViewRoot (from the UI thread perspective)
      * to the passed in consumer. This can be used to create P2P synchronization
      * between ViewRoot's however it comes with many caveats.
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 96198c6..7e6e6fd 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -141,6 +141,12 @@
     private final int mHandledConfigChanges;
 
     /**
+     * The flag whether this IME supports Handwriting using stylus input.
+     */
+    private final boolean mSupportsStylusHandwriting;
+
+
+    /**
      * @param service the {@link ResolveInfo} corresponds in which the IME is implemented.
      * @return a unique ID to be returned by {@link #getId()}. We have used
      *         {@link ComponentName#flattenToShortString()} for this purpose (and it is already
@@ -234,6 +240,8 @@
                     com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true);
             mHandledConfigChanges = sa.getInt(
                     com.android.internal.R.styleable.InputMethod_configChanges, 0);
+            mSupportsStylusHandwriting = sa.getBoolean(
+                    com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false);
             sa.recycle();
 
             final int depth = parser.getDepth();
@@ -323,6 +331,7 @@
         mService = ResolveInfo.CREATOR.createFromParcel(source);
         mSubtypes = new InputMethodSubtypeArray(source);
         mHandledConfigChanges = source.readInt();
+        mSupportsStylusHandwriting = source.readBoolean();
         mForceDefault = false;
     }
 
@@ -335,7 +344,7 @@
                 settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
                 false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
-                0 /* handledConfigChanges */);
+                0 /* handledConfigChanges */, false /* supportsStylusHandwriting */);
     }
 
     /**
@@ -349,7 +358,8 @@
         this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
                 settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
                 false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
-                false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges);
+                false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges,
+                false /* supportsStylusHandwriting */);
     }
 
     /**
@@ -361,7 +371,8 @@
             boolean forceDefault) {
         this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
                 true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
-                false /* isVrOnly */, 0 /* handledconfigChanges */);
+                false /* isVrOnly */, 0 /* handledconfigChanges */,
+                false /* supportsStylusHandwriting */);
     }
 
     /**
@@ -373,7 +384,7 @@
             boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) {
         this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
                 supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
-                0 /* handledConfigChanges */);
+                0 /* handledConfigChanges */, false /* supportsStylusHandwriting */);
     }
 
     /**
@@ -383,7 +394,7 @@
     public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
             List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
             boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
-            boolean isVrOnly, int handledConfigChanges) {
+            boolean isVrOnly, int handledConfigChanges, boolean supportsStylusHandwriting) {
         final ServiceInfo si = ri.serviceInfo;
         mService = ri;
         mId = new ComponentName(si.packageName, si.name).flattenToShortString();
@@ -398,6 +409,7 @@
         mShowInInputMethodPicker = true;
         mIsVrOnly = isVrOnly;
         mHandledConfigChanges = handledConfigChanges;
+        mSupportsStylusHandwriting = supportsStylusHandwriting;
     }
 
     private static ResolveInfo buildFakeResolveInfo(String packageName, String className,
@@ -556,6 +568,14 @@
         return mHandledConfigChanges;
     }
 
+    /**
+     * Returns if IME supports handwriting using stylus input.
+     * @attr ref android.R.styleable#InputMethod_supportsStylusHandwriting
+     */
+    public boolean supportsStylusHandwriting() {
+        return mSupportsStylusHandwriting;
+    }
+
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "mId=" + mId
                 + " mSettingsActivityName=" + mSettingsActivityName
@@ -563,7 +583,8 @@
                 + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
                 + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled
                 + " mSuppressesSpellChecker=" + mSuppressesSpellChecker
-                + " mShowInInputMethodPicker=" + mShowInInputMethodPicker);
+                + " mShowInInputMethodPicker=" + mShowInInputMethodPicker
+                + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting);
         pw.println(prefix + "mIsDefaultResId=0x"
                 + Integer.toHexString(mIsDefaultResId));
         pw.println(prefix + "Service:");
@@ -667,6 +688,7 @@
         mService.writeToParcel(dest, flags);
         mSubtypes.writeToParcel(dest);
         dest.writeInt(mHandledConfigChanges);
+        dest.writeBoolean(mSupportsStylusHandwriting);
     }
 
     /**
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
index 0e8e57b..48af7b9 100644
--- a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
+++ b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
@@ -25,8 +25,8 @@
  */
 oneway interface ISelectionToolbarCallback {
     void onShown(in WidgetInfo info);
-    void onHidden();
-    void onDismissed();
+    void onHidden(long widgetToken);
+    void onDismissed(long widgetToken);
     void onWidgetUpdated(in WidgetInfo info);
     void onMenuItemClicked(in ToolbarMenuItem item);
     void onError(int errorCode);
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index a833600..022d05d 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -66,4 +66,7 @@
      * Restarts the top activity in the given task by killing its process if it is visible.
      */
     void restartTaskTopActivityProcessIfVisible(in WindowContainerToken task);
+
+    /** Updates a state of camera compat control for stretched issues in the viewfinder. */
+    void updateCameraCompatControlState(in WindowContainerToken task, int state);
 }
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 27c7d315..3ec18db 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -24,6 +24,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.SurfaceControl;
@@ -238,6 +239,20 @@
     }
 
     /**
+     * Updates a state of camera compat control for stretched issues in the viewfinder.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+    public void updateCameraCompatControlState(@NonNull WindowContainerToken task,
+            @CameraCompatControlState int state) {
+        try {
+            mTaskOrganizerController.updateCameraCompatControlState(task, state);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the executor to run callbacks on.
      * @hide
      */
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index c9baf00..f09e176 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -22,39 +22,46 @@
 import android.content.pm.PackageInfo;
 import android.os.ParcelFileDescriptor;
 
+import com.android.internal.backup.ITransportStatusCallback;
+import com.android.internal.infra.AndroidFuture;
+
 /** {@hide} */
-interface IBackupTransport {
+oneway interface IBackupTransport {
     /**
-     * Ask the transport for the name under which it should be registered.  This will
+     * Ask the transport for the String name under which it should be registered.  This will
      * typically be its host service's component name, but need not be.
+     *
+     * @param resultFuture an {@link AndroidFuture} that is completed with the {@code String} name
+     * of the transport.
      */
-    String name();
+    void name(in AndroidFuture<String> result);
 
-	/**
-	 * Ask the transport for an Intent that can be used to launch any internal
-	 * configuration Activity that it wishes to present.  For example, the transport
-	 * may offer a UI for allowing the user to supply login credentials for the
-	 * transport's off-device backend.
-	 *
-	 * If the transport does not supply any user-facing configuration UI, it should
-	 * return null from this method.
-	 *
-	 * @return An Intent that can be passed to Context.startActivity() in order to
-	 *         launch the transport's configuration UI.  This method will return null
-	 *         if the transport does not offer any user-facing configuration UI.
-	 */
-	Intent configurationIntent();
+    /**
+     * Ask the transport for an Intent that can be used to launch any internal
+     * configuration Activity that it wishes to present.  For example, the transport
+     * may offer a UI for allowing the user to supply login credentials for the
+     * transport's off-device backend.
+     *
+     * If the transport does not supply any user-facing configuration UI, it should
+     * return null from this method.
+     *
+     * @param resultFuture an {@link AndroidFuture} that is completed with an {@code Intent} that
+     *        can be passed to Context.startActivity() in order to launch the transport's
+     *        configuration UI.  This future will complete with null if the transport does not
+     *        offer any user-facing configuration UI.
+     */
+    void configurationIntent(in AndroidFuture<Intent> resultFuture);
 
-	/**
-	 * On demand, supply a one-line string that can be shown to the user that
-	 * describes the current backend destination.  For example, a transport that
-	 * can potentially associate backup data with arbitrary user accounts should
-	 * include the name of the currently-active account here.
-	 *
-	 * @return A string describing the destination to which the transport is currently
-	 *         sending data.  This method should not return null.
-	 */
-	String currentDestinationString();
+    /**
+     * Ask the transport for a one-line string that can be shown to the user that
+     * describes the current backend destination.  For example, a transport that
+     * can potentially associate backup data with arbitrary user accounts should
+     * include the name of the currently-active account here.
+     *
+     * @param resultFuture an {@link AndroidFuture} that is completed with a {@code String}
+     *        describing the destination to which the transport is currently sending data.
+     */
+    void currentDestinationString(in AndroidFuture<String> resultFuture);
 
     /**
      * Ask the transport for an Intent that can be used to launch a more detailed
@@ -71,22 +78,23 @@
      * <p>If the transport does not supply any user-facing data management
      * UI, then it should return {@code null} from this method.
      *
-     * @return An intent that can be passed to Context.startActivity() in order to
-     *         launch the transport's data-management UI.  This method will return
-     *         {@code null} if the transport does not offer any user-facing data
-     *         management UI.
+     * @param resultFuture an {@link AndroidFuture} that is completed with an {@code Intent} that
+     *        can be passed to Context.startActivity() in order to launch the transport's
+     *        data-management UI. The callback will supply {@code null} if the transport does not
+     *        offer any user-facing data management UI.
      */
-    Intent dataManagementIntent();
+    void dataManagementIntent(in AndroidFuture<Intent> resultFuture);
 
     /**
-     * On demand, supply a short {@link CharSequence} that can be shown to the user as the label on
-     * an overflow menu item used to invoke the data management UI.
+     * Ask the transport for a short {@link CharSequence} that can be shown to the user as the label
+     * on an overflow menu item used to invoke the data management UI.
      *
-     * @return A {@link CharSequence} to be used as the label for the transport's data management
-     *         affordance.  If the transport supplies a data management intent, this
-     *         method must not return {@code null}.
+     * @param resultFuture an {@link AndroidFuture} that is completed with a {@code CharSequence}
+     *        to be used as the label for the transport's data management affordance.  If the
+     *        transport supplies a data management Intent via {@link #dataManagementIntent},
+     *        this method must not return {@code null}.
      */
-    CharSequence dataManagementIntentLabel();
+    void dataManagementIntentLabel(in AndroidFuture<CharSequence> resultFuture);
 
     /**
      * Ask the transport where, on local device storage, to keep backup state blobs.
@@ -96,11 +104,11 @@
      * available backup transports; the name of the class implementing the transport
      * is a good choice.  This MUST be constant.
      *
-     * @return A unique name, suitable for use as a file or directory name, that the
-     *         Backup Manager could use to disambiguate state files associated with
-     *         different backup transports.
+     * @param resultFuture an {@link AndroidFuture} that is completed with a unique {@code String}
+     *        name, suitable for use as a file or directory name, that the Backup Manager could use
+     *        to disambiguate state files associated with different backup transports.
      */
-    String transportDirName();
+    void transportDirName(in AndroidFuture<String> resultFuture);
 
     /**
      * Verify that this is a suitable time for a backup pass.  This should return zero
@@ -110,10 +118,11 @@
      * <p>If this is not a suitable time for a backup, the transport should return a
      * backoff delay, in milliseconds, after which the Backup Manager should try again.
      *
-     * @return Zero if this is a suitable time for a backup pass, or a positive time delay
-     *   in milliseconds to suggest deferring the backup pass for a while.
+     * @param resultFuture an {@link AndroidFuture} that is completed with {@code int}: zero if
+     *        this is a suitable time for a backup pass, or a positive time delay in milliseconds
+     *        to suggest deferring the backup pass for a while.
      */
-    long requestBackupTime();
+    void requestBackupTime(in AndroidFuture<long> resultFuture);
 
     /**
      * Initialize the server side storage for this device, erasing all stored data.
@@ -121,10 +130,11 @@
      * this is called, {@link #finishBackup} must be called to ensure the request
      * is sent and received successfully.
      *
-     * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or
-     *   {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure).
+     * @param callback a callback that is completed with a {@code int} which is one
+     *        of {@link BackupConstants#TRANSPORT_OK} (OK so far) or
+     *        {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure).
      */
-    int initializeDevice();
+    void initializeDevice(in ITransportStatusCallback callback);
 
     /**
      * Send one application's data to the backup destination.  The transport may send
@@ -137,12 +147,14 @@
      *   BackupService.doBackup() method.  This may be a pipe rather than a file on
      *   persistent media, so it may not be seekable.
      * @param flags Some of {@link BackupTransport#FLAG_USER_INITIATED}.
-     * @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
-     *  {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
-     *  {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
-     *  become lost due to inactive expiry or some other reason and needs re-initializing)
+     * @param callback a callback that is completed with a {@code int} which is one
+     *  of {@link BackupConstants#TRANSPORT_OK}(OK so far), {@link BackupConstants#TRANSPORT_ERROR}
+     *  (on network error or other failure), or {@link BackupConstants#TRANSPORT_NOT_INITIALIZED}
+     *  (if the backend dataset has become lost due to inactive expiry or some other reason and
+     *  needs re-initializing).
      */
-    int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd, int flags);
+    void performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd, int flags,
+            in ITransportStatusCallback callback);
 
     /**
      * Erase the give application's data from the backup destination.  This clears
@@ -150,9 +162,10 @@
      * the app had never yet been backed up.  After this is called, {@link finishBackup}
      * must be called to ensure that the operation is recorded successfully.
      *
-     * @return the same error codes as {@link #performBackup}.
+     * @param callback a callback that is completed with the same error codes as
+     *        {@link #performBackup}.
      */
-    int clearBackupData(in PackageInfo packageInfo);
+    void clearBackupData(in PackageInfo packageInfo, in ITransportStatusCallback callback);
 
     /**
      * Finish sending application data to the backup destination.  This must be
@@ -160,27 +173,30 @@
      * all data is sent.  Only when this method returns true can a backup be assumed
      * to have succeeded.
      *
-     * @return the same error codes as {@link #performBackup}.
+     * @param callback a callback that is completed with the same error codes as
+     *        {@link #performBackup}.
      */
-    int finishBackup();
+    void finishBackup(in ITransportStatusCallback callback);
 
     /**
      * Get the set of all backups currently available over this transport.
      *
-     * @return Descriptions of the set of restore images available for this device,
-     *   or null if an error occurred (the attempt should be rescheduled).
+     * @param resultFuture an {@link AndroidFuture} that is completed with {@code List<RestoreSet>}:
+     *        the descriptions of a set of restore images available for this device, or null if an
+     *        error occurred (the attempt should be rescheduled).
      **/
-    RestoreSet[] getAvailableRestoreSets();
+    void getAvailableRestoreSets(in AndroidFuture<List<RestoreSet>> resultFuture);
 
     /**
      * Get the identifying token of the backup set currently being stored from
      * this device.  This is used in the case of applications wishing to restore
      * their last-known-good data.
      *
-     * @return A token that can be passed to {@link #startRestore}, or 0 if there
-     *   is no backup set available corresponding to the current device state.
+     * @param resultFuture an {@link AndroidFuture} that is completed with a {@code long}: a token
+     *        that can be passed to {@link #startRestore}, or 0 if there is no backup set available
+     *        corresponding to the current device state.
      */
-    long getCurrentRestoreSet();
+    void getCurrentRestoreSet(in AndroidFuture<long> resultFuture);
 
     /**
      * Start restoring application data from backup.  After calling this function,
@@ -191,11 +207,12 @@
      *   or {@link #getCurrentRestoreSet}.
      * @param packages List of applications to restore (if data is available).
      *   Application data will be restored in the order given.
-     * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call
-     *   {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR}
-     *   (an error occurred, the restore should be aborted and rescheduled).
+     * @param callback a callback that is completed with one of
+     *   {@link BackupConstants#TRANSPORT_OK} (OK so far, call {@link #nextRestorePackage}) or
+     *   {@link BackupConstants#TRANSPORT_ERROR} (an error occurred, the restore should be aborted
+     *   and rescheduled).
      */
-    int startRestore(long token, in PackageInfo[] packages);
+    void startRestore(long token, in PackageInfo[] packages, in ITransportStatusCallback callback);
 
     /**
      * Get the package name of the next application with data in the backup store, plus
@@ -210,34 +227,95 @@
      * <p>If this method returns {@code null}, it means that a transport-level error has
      * occurred and the entire restore operation should be abandoned.
      *
-     * @return A RestoreDescription object containing the name of one of the packages
-     *   supplied to {@link #startRestore} plus an indicator of the data type of that
-     *   restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that
-     *   no more packages can be restored in this session; or {@code null} to indicate
-     *   a transport-level error.
+     * @param resultFuture an {@link AndroidFuture} that is completed with a
+     *   {@link RestoreDescription} object containing the name of one of the packages supplied to
+     *   {@link #startRestore} plus an indicator of the data type of that restore data; or
+     *   {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that no more packages can be
+     *   restored in this session; or {@code null} to indicate a transport-level error.
      */
-    RestoreDescription nextRestorePackage();
+    void nextRestorePackage(in AndroidFuture<RestoreDescription> resultFuture);
 
     /**
      * Get the data for the application returned by {@link #nextRestorePackage}.
      * @param data An open, writable file into which the backup data should be stored.
-     * @return the same error codes as {@link #startRestore}.
+     *
+     * @param callback a callback that is completed with the same error codes as
+     *        {@link #startRestore}.
      */
-    int getRestoreData(in ParcelFileDescriptor outFd);
+    void getRestoreData(in ParcelFileDescriptor outFd, in ITransportStatusCallback callback);
 
     /**
      * End a restore session (aborting any in-process data transfer as necessary),
      * freeing any resources and connections used during the restore process.
+     *
+     * @param callback a callback to signal that restore has been finished on transport side.
      */
-    void finishRestore();
+    void finishRestore(in ITransportStatusCallback callback);
 
     // full backup stuff
 
-    long requestFullBackupTime();
-    int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket, int flags);
-    int checkFullBackupSize(long size);
-    int sendBackupData(int numBytes);
-    void cancelFullBackup();
+    /**
+     * Verify that this is a suitable time for a full-data backup pass.
+     *
+     * @param resultFuture an {@link AndroidFuture} that is completed with {@code long}: 0 if this
+     *        is a suitable time for a backup pass, or a positive time delay in milliseconds to
+     *        suggest deferring the backup pass for a while.
+     */
+    void requestFullBackupTime(in AndroidFuture<long> resultFuture);
+
+    /**
+     * Begin the process of sending an application's full-data archive to the backend.
+     *
+     * @param targetPackage The package whose data is to follow.
+     * @param socket The socket file descriptor through which the data will be provided.
+     * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
+     * @param callback callback to return a {@code int} which is one of:
+     *        {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} to indicate that the stated
+     *        application is not to be backed up; {@link BackupTransport#TRANSPORT_OK} to indicate
+     *        that the OS may proceed with delivering backup data;
+     *        {@link BackupTransport#TRANSPORT_ERROR to indicate a fatal error condition that
+     *        precludes performing a backup at this time.
+     */
+    void performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket, int flags,
+            in ITransportStatusCallback callback);
+
+    /**
+     * Called after {@link #performFullBackup} to make sure that the transport is willing to
+     * handle a full-data backup operation of the specified size on the current package.
+     *
+     * @param size The estimated size of the full-data payload for this app.  This includes
+     *         manifest and archive format overhead, but is not guaranteed to be precise.
+     * @param callback a callback that is completed with a {@code int} which is
+     *        one of: {@link BackupTransport#TRANSPORT_OK} if the platform is to proceed with the
+     *        full-data {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} if the proposed payload
+     *        size is backup, too large for the transport to handle, or
+     *        {@link BackupTransport#TRANSPORT_ERROR} to indicate a fatal error condition that
+     *        means the platform cannot perform a backup at this time.
+     */
+    void checkFullBackupSize(long size, in ITransportStatusCallback callback);
+
+    /**
+     * Tells the transport to read {@code numBytes} bytes of data from the socket file
+     * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}
+     * call, and deliver those bytes to the datastore.
+     *
+     * @param numBytes The number of bytes of tarball data available to be read from the
+     *    socket.
+     * @param callback a callback that is completed with a {@code int} which is
+     *        one of: {@link BackupTransport#TRANSPORT_OK} on successful processing of the data,
+     *        {@link BackupTransport#TRANSPORT_ERROR} to indicate a fatal error situation.  If an
+     *        error is returned, the system will call finishBackup() and stop attempting backups
+     *        until after a backoff and retry interval.
+     */
+    void sendBackupData(int numBytes, in ITransportStatusCallback callback);
+
+    /**
+     * Tells the transport to cancel the currently-ongoing full backup operation.
+     *
+     * @param callback a callback to indicate that transport has cancelled the operation,
+     *        does not return any value (see {@link ITransportCallback#onVoidReceived}).
+     */
+    void cancelFullBackup(in ITransportStatusCallback callback);
 
     /**
      * Ask the transport whether this app is eligible for backup.
@@ -245,9 +323,11 @@
      * @param targetPackage The identity of the application.
      * @param isFullBackup If set, transport should check if app is eligible for full data backup,
      *   otherwise to check if eligible for key-value backup.
-     * @return Whether this app is eligible for backup.
+     * @param resultFuture an {@link AndroidFuture} that is completed with a {@code boolean}
+     *        indicating whether this app is eligible for backup.
      */
-    boolean isAppEligibleForBackup(in PackageInfo targetPackage, boolean isFullBackup);
+    void isAppEligibleForBackup(in PackageInfo targetPackage, boolean isFullBackup,
+            in AndroidFuture<boolean> resultFuture);
 
     /**
      * Ask the transport about current quota for backup size of the package.
@@ -255,9 +335,11 @@
      * @param packageName ID of package to provide the quota.
      * @param isFullBackup If set, transport should return limit for full data backup, otherwise
      *                     for key-value backup.
-     * @return Current limit on full data backup size in bytes.
+     * @param resultFuture an {@link AndroidFuture} that is completed with a {@code long}: current
+     *        limit on full data backup size in bytes.
      */
-    long getBackupQuota(String packageName, boolean isFullBackup);
+    void getBackupQuota(String packageName, boolean isFullBackup,
+            in AndroidFuture<long> resultFuture);
 
     // full restore stuff
 
@@ -284,13 +366,14 @@
      * @param socket The file descriptor that the transport will use for delivering the
      *    streamed archive.  The transport must close this socket in all cases when returning
      *    from this method.
-     * @return 0 when no more data for the current package is available.  A positive value
-     *    indicates the presence of that many bytes to be delivered to the app.  Any negative
-     *    return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
-     *    indicating a fatal error condition that precludes further restore operations
-     *    on the current dataset.
+     * @param callback a callback that is completed with an {@code int}: 0 when
+     *    no more data for the current package is available.  A positive value indicates the
+     *    presence of that many bytes to be delivered to the app.  Any negative return value is
+     *    treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, indicating a fatal error
+     *    condition that precludes further restore operations on the current dataset.
      */
-    int getNextFullRestoreDataChunk(in ParcelFileDescriptor socket);
+    void getNextFullRestoreDataChunk(in ParcelFileDescriptor socket,
+            in ITransportStatusCallback callback);
 
     /**
      * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
@@ -300,19 +383,21 @@
      * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
      * operation.
      *
-     * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
-     *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
-     *    transport-level failure.  If the transport reports an error here, the entire restore
-     *    operation will immediately be finished with no further attempts to restore app data.
+     * @param callback a callback that is completed with {@code int}, which is
+     *    one of: {@link #TRANSPORT_OK} if the transport was successful in shutting down the current
+     *    stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious transport-level failure.
+     *    If the transport reports an error here, the entire restore operation will immediately be
+     *    finished with no further attempts to restore app data.
      */
-    int abortFullRestore();
+    void abortFullRestore(in ITransportStatusCallback callback);
 
     /**
-     * Returns flags with additional information about the transport, which is accessible to the
+     * @param resultFuture an {@link AndroidFuture} that is completed with an {@code int}: flags
+     * with additional information about the transport, which is accessible to the
      * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to backup or
      * restore based on properties of the transport.
      *
      * <p>For supported flags see {@link android.app.backup.BackupAgent}.
      */
-    int getTransportFlags();
+    void getTransportFlags(in AndroidFuture<int> resultFuture);
 }
diff --git a/core/java/com/android/internal/backup/ITransportStatusCallback.aidl b/core/java/com/android/internal/backup/ITransportStatusCallback.aidl
new file mode 100644
index 0000000..a731480
--- /dev/null
+++ b/core/java/com/android/internal/backup/ITransportStatusCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.backup;
+
+/**
+* A callback class for {@link IBackupTransport}
+*
+* {@hide}
+*/
+oneway interface ITransportStatusCallback {
+    /**
+    * Callback for methods that complete with an {@code int} status.
+    */
+    void onOperationCompleteWithStatus(int status);
+
+    /**
+    * Callback for methods that complete without a value.
+    */
+    void onOperationComplete();
+}
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 6c2b330..662ed6b 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -25,6 +25,7 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
@@ -69,6 +70,82 @@
     private static final String TAG = "RemoteInputConnectionImpl";
     private static final boolean DEBUG = false;
 
+    /**
+     * An upper limit of calling {@link InputConnection#endBatchEdit()}.
+     *
+     * <p>This is a safeguard against broken {@link InputConnection#endBatchEdit()} implementations,
+     * which are real as we've seen in Bug 208941904.  If the retry count reaches to the number
+     * defined here, we fall back into {@link InputMethodManager#restartInput(View)} as a
+     * workaround.</p>
+     */
+    private static final int MAX_END_BATCH_EDIT_RETRY = 16;
+
+    /**
+     * A lightweight per-process type cache to remember classes that never returns {@code false}
+     * from {@link InputConnection#endBatchEdit()}.  The implementation is optimized for simplicity
+     * and speed with accepting false-negatives in {@link #contains(Class)}.
+     */
+    private static final class KnownAlwaysTrueEndBatchEditCache {
+        @Nullable
+        private static volatile Class<?> sElement;
+        @Nullable
+        private static volatile Class<?>[] sArray;
+
+        /**
+         * Query if the specified {@link InputConnection} implementation is known to be broken, with
+         * allowing false-negative results.
+         *
+         * @param klass An implementation class of {@link InputConnection} to be tested.
+         * @return {@code true} if the specified type was passed to {@link #add(Class)}.
+         *         Note that there is a chance that you still receive {@code false} even if you
+         *         called {@link #add(Class)} (false-negative).
+         */
+        @AnyThread
+        static boolean contains(@NonNull Class<? extends InputConnection> klass) {
+            if (klass == sElement) {
+                return true;
+            }
+            final Class<?>[] array = sArray;
+            if (array == null) {
+                return false;
+            }
+            for (Class<?> item : array) {
+                if (item == klass) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Try to remember the specified {@link InputConnection} implementation as a known bad.
+         *
+         * <p>There is a chance that calling this method can accidentally overwrite existing
+         * cache entries. See the document of {@link #contains(Class)} for details.</p>
+         *
+         * @param klass The implementation class of {@link InputConnection} to be remembered.
+         */
+        @AnyThread
+        static void add(@NonNull Class<? extends InputConnection> klass) {
+            if (sElement == null) {
+                // OK to accidentally overwrite an existing element that was set by another thread.
+                sElement = klass;
+                return;
+            }
+
+            final Class<?>[] array = sArray;
+            final int arraySize = array != null ? array.length : 0;
+            final Class<?>[] newArray = new Class<?>[arraySize + 1];
+            for (int i = 0; i < arraySize; ++i) {
+                newArray[i] = array[i];
+            }
+            newArray[arraySize] = klass;
+
+            // OK to accidentally overwrite an existing array that was set by another thread.
+            sArray = newArray;
+        }
+    }
+
     @Retention(SOURCE)
     private @interface Dispatching {
         boolean cancellable();
@@ -155,32 +232,63 @@
             mH.post(() -> {
                 try {
                     if (isFinished()) {
+                        // This is a stale request, which can happen.  No need to show a warning
+                        // because this situation itself is not an error.
                         return;
                     }
                     final InputConnection ic = getInputConnection();
                     if (ic == null) {
+                        // This is a stale request, which can happen.  No need to show a warning
+                        // because this situation itself is not an error.
+                        return;
+                    }
+                    final View view = getServedView();
+                    if (view == null) {
+                        // This is a stale request, which can happen.  No need to show a warning
+                        // because this situation itself is not an error.
                         return;
                     }
 
-                    // Clean up composing text and batch edit.
-                    ic.finishComposingText();
-                    // Also clean up batch edit.
-                    while (true) {
-                        if (!ic.endBatchEdit()) {
-                            break;
+                    final Class<? extends InputConnection> icClass = ic.getClass();
+
+                    boolean alwaysTrueEndBatchEditDetected =
+                            KnownAlwaysTrueEndBatchEditCache.contains(icClass);
+
+                    if (!alwaysTrueEndBatchEditDetected) {
+                        // Clean up composing text and batch edit.
+                        final boolean supportsBatchEdit = ic.beginBatchEdit();
+                        ic.finishComposingText();
+                        if (supportsBatchEdit) {
+                            // Also clean up batch edit.
+                            int retryCount = 0;
+                            while (true) {
+                                if (!ic.endBatchEdit()) {
+                                    break;
+                                }
+                                ++retryCount;
+                                if (retryCount > MAX_END_BATCH_EDIT_RETRY) {
+                                    Log.e(TAG, icClass.getTypeName() + "#endBatchEdit() still"
+                                            + " returns true even after retrying "
+                                            + MAX_END_BATCH_EDIT_RETRY + " times.  Falling back to"
+                                            + " InputMethodManager#restartInput(View)");
+                                    alwaysTrueEndBatchEditDetected = true;
+                                    KnownAlwaysTrueEndBatchEditCache.add(icClass);
+                                    break;
+                                }
+                            }
                         }
                     }
 
-                    final TextSnapshot textSnapshot = ic.takeSnapshot();
-                    if (textSnapshot == null) {
-                        final View view = getServedView();
-                        if (view == null) {
+                    if (!alwaysTrueEndBatchEditDetected) {
+                        final TextSnapshot textSnapshot = ic.takeSnapshot();
+                        if (textSnapshot != null) {
+                            mParentInputMethodManager.doInvalidateInput(this, textSnapshot,
+                                    nextSessionId);
                             return;
                         }
-                        mParentInputMethodManager.restartInput(view);
-                        return;
                     }
-                    mParentInputMethodManager.doInvalidateInput(this, textSnapshot, nextSessionId);
+
+                    mParentInputMethodManager.restartInput(view);
                 } finally {
                     mHasPendingInvalidation.set(false);
                 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index c2224b4..209c64a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -161,7 +161,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 204;
+    static final int VERSION = 205;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -240,6 +240,7 @@
 
     private static final int[] SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS = {
             MeasuredEnergyStats.POWER_BUCKET_CPU,
+            MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
     };
 
     // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate
@@ -582,6 +583,8 @@
         int UPDATE_ALL =
                 UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT | UPDATE_RPM | UPDATE_DISPLAY;
 
+        int UPDATE_ON_PROC_STATE_CHANGE = UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
+
         @IntDef(flag = true, prefix = "UPDATE_", value = {
                 UPDATE_CPU,
                 UPDATE_WIFI,
@@ -608,6 +611,8 @@
         Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis);
         /** Schedule removal of UIDs corresponding to a removed user */
         Future<?> scheduleCleanupDueToRemovedUser(int userId);
+        /** Schedule a sync because of a process state change */
+        Future<?> scheduleSyncDueToProcessStateChange(long delayMillis);
     }
 
     public Handler mHandler;
@@ -1703,7 +1708,6 @@
         }
     }
 
-
     private static class TimeMultiStateCounter implements TimeBaseObs {
         private final TimeBase mTimeBase;
         private final LongMultiStateCounter mCounter;
@@ -1736,10 +1740,6 @@
             mCounter.setEnabled(false, elapsedRealtimeUs / 1000);
         }
 
-        public LongMultiStateCounter getCounter() {
-            return mCounter;
-        }
-
         public int getStateCount() {
             return mCounter.getStateCount();
         }
@@ -1753,8 +1753,8 @@
             return mCounter.updateValue(value, timestampMs);
         }
 
-        public void addCount(long delta) {
-            mCounter.addCount(delta);
+        private void increment(long increment, long timestampMs) {
+            mCounter.incrementValue(increment, timestampMs);
         }
 
         /**
@@ -1764,6 +1764,10 @@
             return mCounter.getCount(procState);
         }
 
+        public long getTotalCountLocked() {
+            return mCounter.getTotalCount();
+        }
+
         public void logState(Printer pw, String prefix) {
             pw.println(prefix + "mCounter=" + mCounter);
         }
@@ -7985,7 +7989,7 @@
 
         LongSamplingCounter[] mNetworkByteActivityCounters;
         LongSamplingCounter[] mNetworkPacketActivityCounters;
-        LongSamplingCounter mMobileRadioActiveTime;
+        TimeMultiStateCounter mMobileRadioActiveTime;
         LongSamplingCounter mMobileRadioActiveCount;
 
         /**
@@ -8195,6 +8199,7 @@
             final int batteryConsumerProcessState =
                     mapUidProcessStateToBatteryConsumerProcessState(procState);
             getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedTimeMs);
+            getMobileRadioActiveTimeCounter().setState(batteryConsumerProcessState, elapsedTimeMs);
             final MeasuredEnergyStats energyStats =
                     getOrCreateMeasuredEnergyStatsIfSupportedLocked();
             if (energyStats != null) {
@@ -8594,11 +8599,10 @@
         /** Adds the given charge to the given standard power bucket for this uid. */
         @GuardedBy("mBsi")
         private void addChargeToStandardBucketLocked(long chargeDeltaUC,
-                @StandardPowerBucket int powerBucket) {
+                @StandardPowerBucket int powerBucket, long timestampMs) {
             final MeasuredEnergyStats measuredEnergyStats =
                     getOrCreateMeasuredEnergyStatsLocked();
-            measuredEnergyStats.updateStandardBucket(powerBucket, chargeDeltaUC,
-                    mBsi.mClock.elapsedRealtime());
+            measuredEnergyStats.updateStandardBucket(powerBucket, chargeDeltaUC, timestampMs);
         }
 
         /** Adds the given charge to the given custom power bucket for this uid. */
@@ -8690,6 +8694,13 @@
 
         @GuardedBy("mBsi")
         @Override
+        public long getMobileRadioMeasuredBatteryConsumptionUC(int processState) {
+            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
+                    processState);
+        }
+
+        @GuardedBy("mBsi")
+        @Override
         public long getScreenOnMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
         }
@@ -9228,9 +9239,7 @@
         }
 
         void noteNetworkActivityLocked(int type, long deltaBytes, long deltaPackets) {
-            if (mNetworkByteActivityCounters == null) {
-                initNetworkActivityLocked();
-            }
+            ensureNetworkActivityLocked();
             if (type >= 0 && type < NUM_NETWORK_ACTIVITY_TYPES) {
                 mNetworkByteActivityCounters[type].addCountLocked(deltaBytes);
                 mNetworkPacketActivityCounters[type].addCountLocked(deltaPackets);
@@ -9240,14 +9249,25 @@
             }
         }
 
-        void noteMobileRadioActiveTimeLocked(long batteryUptime) {
-            if (mNetworkByteActivityCounters == null) {
-                initNetworkActivityLocked();
-            }
-            mMobileRadioActiveTime.addCountLocked(batteryUptime);
+        void noteMobileRadioActiveTimeLocked(long batteryUptimeDeltaUs, long elapsedTimeMs) {
+            ensureNetworkActivityLocked();
+            getMobileRadioActiveTimeCounter().increment(batteryUptimeDeltaUs, elapsedTimeMs);
             mMobileRadioActiveCount.addCountLocked(1);
         }
 
+        private TimeMultiStateCounter getMobileRadioActiveTimeCounter() {
+            if (mMobileRadioActiveTime == null) {
+                final long timestampMs = mBsi.mClock.elapsedRealtime();
+                mMobileRadioActiveTime = new TimeMultiStateCounter(
+                        mBsi.mOnBatteryTimeBase, BatteryConsumer.PROCESS_STATE_COUNT, timestampMs);
+                mMobileRadioActiveTime.setState(
+                        mapUidProcessStateToBatteryConsumerProcessState(mProcessState),
+                        timestampMs);
+                mMobileRadioActiveTime.update(0, timestampMs);
+            }
+            return mMobileRadioActiveTime;
+        }
+
         @Override
         public boolean hasNetworkActivity() {
             return mNetworkByteActivityCounters != null;
@@ -9275,8 +9295,20 @@
 
         @Override
         public long getMobileRadioActiveTime(int which) {
-            return mMobileRadioActiveTime != null
-                    ? mMobileRadioActiveTime.getCountLocked(which) : 0;
+            return getMobileRadioActiveTimeInProcessState(BatteryConsumer.PROCESS_STATE_ANY);
+        }
+
+        @Override
+        public long getMobileRadioActiveTimeInProcessState(
+                @BatteryConsumer.ProcessState int processState) {
+            if (mMobileRadioActiveTime == null) {
+                return 0;
+            }
+            if (processState == BatteryConsumer.PROCESS_STATE_ANY) {
+                return mMobileRadioActiveTime.getTotalCountLocked();
+            } else {
+                return mMobileRadioActiveTime.getCountLocked(processState);
+            }
         }
 
         @Override
@@ -9388,18 +9420,17 @@
             }
         }
 
-        void initNetworkActivityLocked() {
-            detachIfNotNull(mNetworkByteActivityCounters);
+        void ensureNetworkActivityLocked() {
+            if (mNetworkByteActivityCounters != null) {
+                return;
+            }
+
             mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
-            detachIfNotNull(mNetworkPacketActivityCounters);
             mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
             for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
                 mNetworkByteActivityCounters[i] = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
                 mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
             }
-            detachIfNotNull(mMobileRadioActiveTime);
-            mMobileRadioActiveTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
-            detachIfNotNull(mMobileRadioActiveCount);
             mMobileRadioActiveCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
         }
 
@@ -9896,7 +9927,12 @@
                     mNetworkByteActivityCounters[i].writeToParcel(out);
                     mNetworkPacketActivityCounters[i].writeToParcel(out);
                 }
-                mMobileRadioActiveTime.writeToParcel(out);
+                if (mMobileRadioActiveTime != null) {
+                    out.writeBoolean(true);
+                    mMobileRadioActiveTime.writeToParcel(out);
+                } else {
+                    out.writeBoolean(false);
+                }
                 mMobileRadioActiveCount.writeToParcel(out);
             } else {
                 out.writeInt(0);
@@ -9996,6 +10032,7 @@
 
         @GuardedBy("mBsi")
         void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
+            final long timestampMs = mBsi.mClock.elapsedRealtime();
             mOnBatteryBackgroundTimeBase.readFromParcel(in);
             mOnBatteryScreenOffBackgroundTimeBase.readFromParcel(in);
 
@@ -10205,7 +10242,14 @@
                     mNetworkPacketActivityCounters[i]
                             = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
                 }
-                mMobileRadioActiveTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+                if (in.readBoolean()) {
+                    final TimeMultiStateCounter counter =
+                            new TimeMultiStateCounter(mBsi.mOnBatteryTimeBase, in, timestampMs);
+                    if (counter.getStateCount() == BatteryConsumer.PROCESS_STATE_COUNT) {
+                        mMobileRadioActiveTime = counter;
+                    }
+                }
+
                 mMobileRadioActiveCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
             } else {
                 mNetworkByteActivityCounters = null;
@@ -10247,7 +10291,6 @@
             mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
                     in, mBsi.mOnBatteryScreenOffTimeBase);
 
-            final long timestampMs = mBsi.mClock.elapsedRealtime();
             int stateCount = in.readInt();
             if (stateCount != 0) {
                 final TimeMultiStateCounter counter = new TimeMultiStateCounter(
@@ -11182,6 +11225,9 @@
                     onBatteryScreenOffCounter.setState(uidRunningState, elapsedRealtimeMs);
                 }
 
+                final int prevBatteryConsumerProcessState =
+                        mapUidProcessStateToBatteryConsumerProcessState(mProcessState);
+
                 mProcessState = uidRunningState;
 
                 updateOnBatteryBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
@@ -11191,11 +11237,15 @@
                         mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
                 getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedRealtimeMs);
 
+                getMobileRadioActiveTimeCounter()
+                        .setState(batteryConsumerProcessState, elapsedRealtimeMs);
                 final MeasuredEnergyStats energyStats =
                         getOrCreateMeasuredEnergyStatsIfSupportedLocked();
                 if (energyStats != null) {
                     energyStats.setState(batteryConsumerProcessState, elapsedRealtimeMs);
                 }
+                maybeScheduleExternalStatsSync(prevBatteryConsumerProcessState,
+                        batteryConsumerProcessState);
             }
 
             if (userAwareService != mInForegroundService) {
@@ -11208,6 +11258,27 @@
             }
         }
 
+        @GuardedBy("mBsi")
+        private void maybeScheduleExternalStatsSync(
+                @BatteryConsumer.ProcessState int oldProcessState,
+                @BatteryConsumer.ProcessState int newProcessState) {
+            if (oldProcessState == newProcessState) {
+                return;
+            }
+            // Transitions between BACKGROUND and such non-foreground states like cached
+            // or nonexistent do not warrant doing a sync.  If some of the stats for those
+            // proc states bleed into the PROCESS_STATE_BACKGROUND, that's ok.
+            if ((oldProcessState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED
+                    && newProcessState == BatteryConsumer.PROCESS_STATE_BACKGROUND)
+                    || (oldProcessState == BatteryConsumer.PROCESS_STATE_BACKGROUND
+                    && newProcessState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED)) {
+                return;
+            }
+
+            mBsi.mExternalSync.scheduleSyncDueToProcessStateChange(
+                    mBsi.mConstants.PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
+        }
+
         /** Whether to consider Uid to be in the background for background timebase purposes. */
         public boolean isInBackground() {
             // Note that PROCESS_STATE_CACHED and Uid.PROCESS_STATE_NONEXISTENT is
@@ -12805,7 +12876,8 @@
                             .calcGlobalPowerWithoutControllerDataMah(globalTimeMs);
                 }
                 distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_WIFI,
-                        consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedConsumptionMah);
+                        consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedConsumptionMah,
+                        elapsedRealtimeMs);
             }
         }
     }
@@ -12963,7 +13035,7 @@
                         final long appPackets = entry.rxPackets + entry.txPackets;
                         final long appRadioTimeUs =
                                 (totalAppRadioTimeUs * appPackets) / totalPackets;
-                        u.noteMobileRadioActiveTimeLocked(appRadioTimeUs);
+                        u.noteMobileRadioActiveTimeLocked(appRadioTimeUs, elapsedRealtimeMs);
 
                         // Distribute measured mobile radio charge consumption based on app radio
                         // active time
@@ -13042,7 +13114,7 @@
 
                     distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
                             consumedChargeUC, uidEstimatedConsumptionMah,
-                            totalEstimatedConsumptionMah);
+                            totalEstimatedConsumptionMah, elapsedRealtimeMs);
                 }
 
                 mNetworkStatsPool.release(delta);
@@ -13330,7 +13402,8 @@
                     = mBluetoothPowerCalculator.calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
             totalEstimatedMah = Math.max(totalEstimatedMah, controllerMaMs / MILLISECONDS_IN_HOUR);
             distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH,
-                    consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedMah);
+                    consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedMah,
+                    elapsedRealtimeMs);
         }
 
         mLastBluetoothActivityInfo.set(info);
@@ -13433,8 +13506,10 @@
         }
         if (totalCpuChargeUC <= 0) return;
 
+        final long timestampMs = mClock.elapsedRealtime();
+
         mGlobalMeasuredEnergyStats.updateStandardBucket(MeasuredEnergyStats.POWER_BUCKET_CPU,
-                totalCpuChargeUC, mClock.elapsedRealtime());
+                totalCpuChargeUC, timestampMs);
 
         // Calculate the measured microcoulombs/calculated milliamp-hour charge ratio for each
         // cluster to normalize  each uid's estimated power usage against actual power usage for
@@ -13483,7 +13558,7 @@
             }
 
             uid.addChargeToStandardBucketLocked(uidCpuChargeUC,
-                    MeasuredEnergyStats.POWER_BUCKET_CPU);
+                    MeasuredEnergyStats.POWER_BUCKET_CPU, timestampMs);
         }
     }
 
@@ -13583,7 +13658,7 @@
             fgTimeUsArray.put(uid.getUid(), (double) fgTimeUs);
         }
         distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON,
-                totalScreenOnChargeUC, fgTimeUsArray, 0);
+                totalScreenOnChargeUC, fgTimeUsArray, 0, elapsedRealtimeMs);
     }
 
     /**
@@ -13627,7 +13702,7 @@
             gnssTimeUsArray.put(uid.getUid(), (double) gnssTimeUs);
         }
         distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_GNSS, chargeUC,
-                gnssTimeUsArray, 0);
+                gnssTimeUsArray, 0, elapsedRealtimeMs);
     }
 
     /**
@@ -13699,7 +13774,7 @@
     @SuppressWarnings("GuardedBy") // errorprone false positive on u.addChargeToStandardBucketLocked
     private void distributeEnergyToUidsLocked(@StandardPowerBucket int bucket,
             long totalConsumedChargeUC, SparseDoubleArray ratioNumerators,
-            double minRatioDenominator) {
+            double minRatioDenominator, long timestampMs) {
 
         // If the sum of all app usage was greater than the total, use that instead:
         double sumRatioNumerators = 0;
@@ -13714,7 +13789,7 @@
             final double ratioNumerator = ratioNumerators.valueAt(i);
             final long uidActualUC
                     = (long) (totalConsumedChargeUC * ratioNumerator / ratioDenominator + 0.5);
-            uid.addChargeToStandardBucketLocked(uidActualUC, bucket);
+            uid.addChargeToStandardBucketLocked(uidActualUC, bucket, timestampMs);
         }
     }
 
@@ -14433,7 +14508,7 @@
                 if (childUid != null) {
                     final long delta =
                             childUid.cpuActiveCounter.update(cpuActiveTimesMs, elapsedRealtimeMs);
-                    u.getCpuActiveTimeCounter().addCount(delta);
+                    u.getCpuActiveTimeCounter().increment(delta, elapsedRealtimeMs);
                 }
             }
         });
@@ -15627,7 +15702,6 @@
         for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) {
             procStateNames[procState] = BatteryConsumer.processStateToString(procState);
         }
-        procStateNames[BatteryConsumer.PROCESS_STATE_ANY] = "untracked";
         return procStateNames;
     }
 
@@ -15651,6 +15725,8 @@
                 = "external_stats_collection_rate_limit_ms";
         public static final String KEY_BATTERY_LEVEL_COLLECTION_DELAY_MS
                 = "battery_level_collection_delay_ms";
+        public static final String KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS =
+                "procstate_change_collection_delay_ms";
         public static final String KEY_MAX_HISTORY_FILES = "max_history_files";
         public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb";
         public static final String KEY_BATTERY_CHARGED_DELAY_MS =
@@ -15661,6 +15737,7 @@
         private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L;
         private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
         private static final long DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS = 300_000;
+        private static final long DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS = 60_000;
         private static final int DEFAULT_MAX_HISTORY_FILES = 32;
         private static final int DEFAULT_MAX_HISTORY_BUFFER_KB = 128; /*Kilo Bytes*/
         private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64;
@@ -15676,6 +15753,8 @@
                 = DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS;
         public long BATTERY_LEVEL_COLLECTION_DELAY_MS
                 = DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS;
+        public long PROC_STATE_CHANGE_COLLECTION_DELAY_MS =
+                DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS;
         public int MAX_HISTORY_FILES;
         public int MAX_HISTORY_BUFFER; /*Bytes*/
         public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
@@ -15742,6 +15821,9 @@
                 BATTERY_LEVEL_COLLECTION_DELAY_MS = mParser.getLong(
                         KEY_BATTERY_LEVEL_COLLECTION_DELAY_MS,
                         DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS);
+                PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong(
+                        KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS,
+                        DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
 
                 MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES,
                         ActivityManager.isLowRamDeviceStatic() ?
@@ -15793,6 +15875,8 @@
             pw.println(EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS);
             pw.print(KEY_BATTERY_LEVEL_COLLECTION_DELAY_MS); pw.print("=");
             pw.println(BATTERY_LEVEL_COLLECTION_DELAY_MS);
+            pw.print(KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS); pw.print("=");
+            pw.println(PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
             pw.print(KEY_MAX_HISTORY_FILES); pw.print("=");
             pw.println(MAX_HISTORY_FILES);
             pw.print(KEY_MAX_HISTORY_BUFFER_KB); pw.print("=");
@@ -16476,14 +16560,18 @@
             }
 
             if (in.readInt() != 0) {
-                if (u.mNetworkByteActivityCounters == null) {
-                    u.initNetworkActivityLocked();
-                }
+                u.ensureNetworkActivityLocked();
                 for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
                     u.mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
                     u.mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
                 }
-                u.mMobileRadioActiveTime.readSummaryFromParcelLocked(in);
+                if (in.readBoolean()) {
+                    TimeMultiStateCounter counter = new TimeMultiStateCounter(
+                            mOnBatteryTimeBase, in, elapsedRealtimeMs);
+                    if (counter.getStateCount() == BatteryConsumer.PROCESS_STATE_COUNT) {
+                        u.mMobileRadioActiveTime = counter;
+                    }
+                }
                 u.mMobileRadioActiveCount.readSummaryFromParcelLocked(in);
             }
 
@@ -17033,7 +17121,12 @@
                     u.mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
                     u.mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
                 }
-                u.mMobileRadioActiveTime.writeSummaryFromParcelLocked(out);
+                if (u.mMobileRadioActiveTime != null) {
+                    out.writeBoolean(true);
+                    u.mMobileRadioActiveTime.writeToParcel(out);
+                } else {
+                    out.writeBoolean(false);
+                }
                 u.mMobileRadioActiveCount.writeSummaryFromParcelLocked(out);
             }
 
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 175f28f..ee614cd 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -156,11 +156,11 @@
     private void calculateMeasuredPowerPerProcessState(UidBatteryConsumer.Builder app,
             BatteryStats.Uid u, BatteryConsumer.Key[] keys) {
         for (BatteryConsumer.Key key : keys) {
-            // The key for "PROCESS_STATE_ANY" has already been populated with the
-            // full energy across all states.  We don't want to override it with
+            // The key for PROCESS_STATE_UNSPECIFIED aka PROCESS_STATE_ANY has already been
+            // populated with the full energy across all states.  We don't want to override it with
             // the energy for "other" states, which excludes the tracked states like
             // foreground, background etc.
-            if (key.processState == BatteryConsumer.PROCESS_STATE_ANY) {
+            if (key.processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
                 continue;
             }
 
@@ -184,7 +184,7 @@
                 uidProcState++) {
             @BatteryConsumer.ProcessState int procState =
                     BatteryStats.mapUidProcessStateToBatteryConsumerProcessState(uidProcState);
-            if (procState == BatteryConsumer.PROCESS_STATE_ANY) {
+            if (procState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
                 continue;
             }
 
@@ -199,7 +199,7 @@
         }
 
         for (BatteryConsumer.Key key : keys) {
-            if (key.processState == BatteryConsumer.PROCESS_STATE_ANY) {
+            if (key.processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
                 continue;
             }
 
diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java
index ad8e0ed..33a9d54 100644
--- a/core/java/com/android/internal/os/LongMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongMultiStateCounter.java
@@ -122,6 +122,13 @@
     /**
      * Adds the supplied values to the current accumulated values in the counter.
      */
+    public void incrementValue(long count, long timestampMs) {
+        native_incrementValue(mNativeObject, count, timestampMs);
+    }
+
+    /**
+     * Adds the supplied values to the current accumulated values in the counter.
+     */
     public void addCount(long count) {
         native_addCount(mNativeObject, count);
     }
@@ -144,6 +151,17 @@
         return native_getCount(mNativeObject, state);
     }
 
+    /**
+     * Returns the total accumulated count across all states.
+     */
+    public long getTotalCount() {
+        long total = 0;
+        for (int state = 0; state < mStateCount; state++) {
+            total += native_getCount(mNativeObject, state);
+        }
+        return total;
+    }
+
     @Override
     public String toString() {
         return native_toString(mNativeObject);
@@ -190,6 +208,10 @@
     private static native long native_updateValue(long nativeObject, long value, long timestampMs);
 
     @CriticalNative
+    private static native void native_incrementValue(long nativeObject, long increment,
+            long timestampMs);
+
+    @CriticalNative
     private static native void native_addCount(long nativeObject, long count);
 
     @CriticalNative
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index eb5993d..28cc836 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -34,6 +34,8 @@
     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
             CellSignalStrength.getNumSignalStrengthLevels();
 
+    private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+
     private final UsageBasedPowerEstimator mActivePowerEstimator;
     private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
             new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
@@ -89,14 +91,22 @@
 
         PowerAndDuration total = new PowerAndDuration();
 
-        final double powerPerPacketMah = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED);
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
+        BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
+
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
             final BatteryStats.Uid uid = app.getBatteryStatsUid();
-            calculateApp(app, uid, powerPerPacketMah, total, query);
+            if (keys == UNINITIALIZED_KEYS) {
+                if (query.isProcessStateDataNeeded()) {
+                    keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
+                } else {
+                    keys = null;
+                }
+            }
+
+            calculateApp(app, uid, total, query, keys);
         }
 
         final long consumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
@@ -121,34 +131,49 @@
     }
 
     private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
-            double powerPerPacketMah, PowerAndDuration total,
-            BatteryUsageStatsQuery query) {
+            PowerAndDuration total,
+            BatteryUsageStatsQuery query, BatteryConsumer.Key[] keys) {
         final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
         total.totalAppDurationMs += radioActiveDurationMs;
 
         final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC, query);
-        final double powerMah = calculatePower(u, powerModel, powerPerPacketMah,
-                radioActiveDurationMs, consumptionUC);
+        final double powerMah = calculatePower(u, powerModel, radioActiveDurationMs, consumptionUC);
         total.totalAppPowerMah += powerMah;
 
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                         radioActiveDurationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah,
                         powerModel);
+
+        if (query.isProcessStateDataNeeded() && keys != null) {
+            for (BatteryConsumer.Key key: keys) {
+                final int processState = key.processState;
+                if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                    // Already populated with the total across all process states
+                    continue;
+                }
+
+                final long durationInStateMs =
+                        u.getMobileRadioActiveTimeInProcessState(processState) / 1000;
+                final long consumptionInStateUc =
+                        u.getMobileRadioMeasuredBatteryConsumptionUC(processState);
+                final double powerInStateMah = calculatePower(u, powerModel, durationInStateMs,
+                        consumptionInStateUc);
+                app.setConsumedPower(key, powerInStateMah, powerModel);
+            }
+        }
     }
 
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        final double mobilePowerPerPacket = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
-                statsType);
         PowerAndDuration total = new PowerAndDuration();
         for (int i = sippers.size() - 1; i >= 0; i--) {
             final BatterySipper app = sippers.get(i);
             if (app.drainType == BatterySipper.DrainType.APP) {
                 final BatteryStats.Uid u = app.uidObj;
-                calculateApp(app, u, statsType, mobilePowerPerPacket, total);
+                calculateApp(app, u, statsType, total);
             }
         }
 
@@ -172,13 +197,12 @@
     }
 
     private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
-            double powerPerPacketMah, PowerAndDuration total) {
+            PowerAndDuration total) {
         app.mobileActive = calculateDuration(u, statsType);
 
         final long consumptionUC =  u.getMobileRadioMeasuredBatteryConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC);
-        app.mobileRadioPowerMah = calculatePower(u, powerModel, powerPerPacketMah, app.mobileActive,
-                consumptionUC);
+        app.mobileRadioPowerMah = calculatePower(u, powerModel, app.mobileActive, consumptionUC);
         total.totalAppDurationMs += app.mobileActive;
 
         // Add cost of mobile traffic.
@@ -205,26 +229,15 @@
     }
 
     private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
-            double powerPerPacketMah, long radioActiveDurationMs, long measuredChargeUC) {
+            long radioActiveDurationMs, long measuredChargeUC) {
         if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
             return uCtoMah(measuredChargeUC);
         }
 
         if (radioActiveDurationMs > 0) {
-            // We are tracking when the radio is up, so can use the active time to
-            // determine power use.
             return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
-        } else {
-            // We are not tracking when the radio is up, so must approximate power use
-            // based on the number of packets.
-            final long mobileRxPackets = u.getNetworkActivityPackets(
-                    BatteryStats.NETWORK_MOBILE_RX_DATA,
-                    BatteryStats.STATS_SINCE_CHARGED);
-            final long mobileTxPackets = u.getNetworkActivityPackets(
-                    BatteryStats.NETWORK_MOBILE_TX_DATA,
-                    BatteryStats.STATS_SINCE_CHARGED);
-            return (mobileRxPackets + mobileTxPackets) * powerPerPacketMah;
         }
+        return 0;
     }
 
     private void calculateRemaining(PowerAndDuration total,
@@ -299,21 +312,4 @@
     public double calcScanTimePowerMah(long scanningTimeMs) {
         return mScanPowerEstimator.calculatePower(scanningTimeMs);
     }
-
-    /**
-     * Return estimated power (in mAh) of sending or receiving a packet with the mobile radio.
-     */
-    private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) {
-        final long radioDataUptimeMs =
-                stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
-        final double mobilePower = calcPowerFromRadioActiveDurationMah(radioDataUptimeMs);
-
-        final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
-                statsType);
-        final long mobileTx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
-                statsType);
-        final long mobilePackets = mobileRx + mobileTx;
-
-        return mobilePackets != 0 ? mobilePower / mobilePackets : 0;
-    }
 }
diff --git a/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl b/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
new file mode 100644
index 0000000..6ca8cec
--- /dev/null
+++ b/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+oneway interface ICarrierPrivilegesListener {
+    void onCarrierPrivilegesChanged(
+            in List<String> privilegedPackageNames, in int[] privilegedUids);
+}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 15d4246..9712d7e 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 
@@ -100,4 +101,10 @@
     void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
     void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
             in List<LinkCapacityEstimate> linkCapacityEstimateList);
+
+    void addCarrierPrivilegesListener(
+            int phoneId, ICarrierPrivilegesListener callback, String pkg, String featureId);
+    void removeCarrierPrivilegesListener(ICarrierPrivilegesListener callback, String pkg);
+    void notifyCarrierPrivilegesChanged(
+            int phoneId, in List<String> privilegedPackageNames, in int[] privilegedUids);
 }
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 45652a7..69c4f3d 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -84,6 +84,10 @@
     return (jlong)asLongMultiStateCounter(nativePtr)->updateValue((int64_t)value, timestamp);
 }
 
+static void native_incrementValue(jlong nativePtr, jlong count, jlong timestamp) {
+    asLongMultiStateCounter(nativePtr)->incrementValue(count, timestamp);
+}
+
 static void native_addCount(jlong nativePtr, jlong count) {
     asLongMultiStateCounter(nativePtr)->addValue(count);
 }
@@ -172,6 +176,8 @@
         // @CriticalNative
         {"native_updateValue", "(JJJ)J", (void *)native_updateValue},
         // @CriticalNative
+        {"native_incrementValue", "(JJJ)V", (void *)native_incrementValue},
+        // @CriticalNative
         {"native_addCount", "(JJ)V", (void *)native_addCount},
         // @CriticalNative
         {"native_reset", "(J)V", (void *)native_reset},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4f35f2c..e2a2ac6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6065,6 +6065,12 @@
     <permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"
         android:protectionLevel="signature|role" />
 
+    <!-- @SystemApi Allows an application to update certain device management related system
+         resources.
+         @hide -->
+    <permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES"
+                android:protectionLevel="signature|role" />
+    
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
@@ -6566,6 +6572,16 @@
             </intent-filter>
         </service>
 
+        <!-- TODO: Move to ExtServices or relevant component. -->
+        <service android:name="com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService"
+                 android:permission="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
+                 android:process=":ui"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.service.selectiontoolbar.SelectionToolbarRenderService"/>
+            </intent-filter>
+        </service>
+
         <provider
             android:name="com.android.server.textclassifier.IconsContentProvider"
             android:authorities="com.android.textclassifier.icons"
diff --git a/core/res/res/drawable/ic_add_supervised_user.xml b/core/res/res/drawable/ic_add_supervised_user.xml
new file mode 100644
index 0000000..a493775
--- /dev/null
+++ b/core/res/res/drawable/ic_add_supervised_user.xml
@@ -0,0 +1,34 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="20"
+        android:viewportHeight="20"
+        android:tint="?android:attr/colorControlNormal">
+
+    <group
+        android:scaleX="0.5"
+        android:scaleY="0.5">
+
+        <path
+            android:fillColor="@android:color/white"
+            android:pathData="M15.625,22.5q-2.375,0 -4.063,-1.688 -1.687,-1.687 -1.687,-4.104 0,-2.375 1.688,-4.062 1.687,-1.688 4.062,-1.688 2.417,0 4.105,1.688 1.687,1.687 1.687,4.062 0,2.417 -1.688,4.105 -1.687,1.687 -4.104,1.687zM15.625,19.708q1.292,0 2.146,-0.875 0.854,-0.875 0.854,-2.125t-0.854,-2.125q-0.854,-0.875 -2.146,-0.875 -1.208,0 -2.104,0.875 -0.896,0.875 -0.896,2.125t0.896,2.125q0.896,0.875 2.104,0.875zM27.875,24.333q-1.792,0 -3.063,-1.27 -1.27,-1.271 -1.27,-3.063 0,-1.792 1.27,-3.063 1.271,-1.27 3.063,-1.27 1.833,0 3.083,1.27 1.25,1.271 1.25,3.063 0,1.792 -1.25,3.063 -1.25,1.27 -3.083,1.27zM17.458,33.667q1.959,-3.75 5.063,-5.063 3.104,-1.312 5.354,-1.312 0.958,0 1.813,0.145 0.854,0.146 1.729,0.396 1.041,-1.541 1.75,-3.583 0.708,-2.042 0.708,-4.25 0,-5.792 -4.041,-9.834Q25.791,6.125 20,6.125t-9.834,4.041Q6.125,14.208 6.125,20q0,2.042 0.563,3.938 0.562,1.895 1.604,3.437 1.625,-0.833 3.52,-1.333 1.896,-0.5 3.813,-0.5 1.208,0 2.333,0.188 1.125,0.187 2.125,0.52 -0.833,0.458 -1.562,1 -0.729,0.542 -1.354,1.125 -0.459,-0.042 -0.813,-0.042h-0.729q-1.375,0 -2.916,0.355 -1.542,0.354 -2.751,0.937 1.5,1.583 3.438,2.645 1.937,1.063 4.062,1.397zM20,36.667q-3.417,0 -6.459,-1.313 -3.041,-1.312 -5.312,-3.583 -2.271,-2.271 -3.583,-5.313Q3.333,23.418 3.333,20q0,-3.458 1.313,-6.479Q5.958,10.5 8.229,8.229t5.313,-3.583Q16.582,3.333 20,3.333q3.458,0 6.479,1.313 3.021,1.312 5.292,3.583t3.584,5.292q1.312,3.021 1.312,6.479 0,3.417 -1.313,6.459 -1.312,3.041 -3.583,5.312 -2.271,2.271 -5.292,3.584 -3.021,1.312 -6.479,1.312z"/>
+
+    </group>
+
+</vector>
+
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6076645..8aa9201 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3638,6 +3638,9 @@
              to re-retrieve all resources (including view layouts, drawables, etc)
              to correctly handle any configuration change.-->
         <attr name="configChanges" />
+        <!-- Specifies whether the IME supports Handwriting using stylus. Defaults to false. -->
+        <attr name="supportsStylusHandwriting" format="boolean" />
+
     </declare-styleable>
 
     <!-- This is the subtype of InputMethod. Subtype can describe locales (for example, en_US and
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4feee41..be32c42 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3333,6 +3333,20 @@
          and one pSIM) -->
     <integer name="config_num_physical_slots">1</integer>
 
+    <!--The default "usage setting" indicating that the device is either a voice-centric
+    device (1) or a data-centric device (2). A voice-centric device will require that any cellular
+    service that it uses provides access to voice capability, and a data-centric device will
+    likewise require that the network provides access to data services. These settings are
+    sent to the cellular modem and control the behavior in accordance with 3gpp TS 24.301 sec 4.3
+    (and equivalent functionality in other generations of cellular).-->
+    <integer name="config_default_cellular_usage_setting">1</integer>
+
+    <!--The list of supported cellular usage settings for this device.-->
+    <integer-array translatable="false" name="config_supported_cellular_usage_settings">
+        <item>1</item>    <!-- USAGE_SETTING_VOICE_CENTRIC -->
+        <item>2</item>    <!-- USAGE_SETTING_DATA_CENTRIC -->
+    </integer-array>
+
     <!-- When a radio power off request is received, we will delay completing the request until
          either IMS moves to the deregistered state or the timeout defined by this configuration
          elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
@@ -5054,6 +5068,10 @@
         If given value is outside of this range, the option 1 (center) is assummed. -->
     <integer name="config_letterboxDefaultPositionForReachability">1</integer>
 
+    <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
+         treatment for stretched issues in camera viewfinder. -->
+    <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
+
     <!-- If true, hide the display cutout with display area -->
     <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
 
@@ -5523,4 +5541,13 @@
     </string-array>
 
     <integer name="config_chooser_max_targets_per_row">4</integer>
+
+    <!-- Package that provides the supervised user creation flow. This package must include an
+         activity with an intent filter for {@link UserManager.ACTION_CREATE_SUPERVISED_USER}.
+         When this resource is defined, an extra button in user settings screen will be shown
+         with a title defined in @*android:string/supervised_user_creation_label
+         and an icon defined in @*android:drawable/ic_add_supervised_user.
+         That button will fire an intent targeted for this package with the mentioned action.
+         When this resource is empty, that button will not be shown. -->
+    <string name="config_supervisedUserCreationPackage" translatable="false"></string>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b9c7564..3d3c860 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3249,6 +3249,7 @@
     <public name="canDisplayOnRemoteDevices" />
     <public name="supportedTypes" />
     <public name="resetEnabledSettingsOnAppDataCleared" />
+    <public name="supportsStylusHandwriting" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 769e667..b16e462 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5374,6 +5374,8 @@
     <string name="user_creation_account_exists">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> (a User with this account already exists) ?</string>
     <!-- Message to user that app is trying to create user for a specified account. [CHAR LIMIT=none] -->
     <string name="user_creation_adding">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> ?</string>
+    <!-- String label displayed on buttons that trigger the flow for creating supervised users. [CHAR LIMIT=35] -->
+    <string name="supervised_user_creation_label">Add supervised user</string>
 
     <!-- Locale picker strings -->
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 55bf24b..527865f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -492,6 +492,8 @@
   <java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
   <java-symbol type="string" name="config_deviceSpecificAudioService" />
   <java-symbol type="integer" name="config_num_physical_slots" />
+  <java-symbol type="integer" name="config_default_cellular_usage_setting" />
+  <java-symbol type="array" name="config_supported_cellular_usage_settings" />
   <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
   <java-symbol type="array" name="config_integrityRuleProviderPackages" />
   <java-symbol type="bool" name="config_useAssistantVolume" />
@@ -4292,6 +4294,7 @@
   <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
   <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
+  <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
 
@@ -4629,4 +4632,6 @@
   <java-symbol type="integer" name="config_mashPressVibrateTimeOnPowerButton" />
 
   <java-symbol type="string" name="config_systemGameService" />
+
+  <java-symbol type="string" name="config_supervisedUserCreationPackage"/>
 </resources>
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index 78a8f7b..c4c983d 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.TOOL_TYPE_FINGER;
@@ -214,4 +215,27 @@
         rotInvalid.transform(mat);
         assertEquals(-1, rotInvalid.getSurfaceRotation());
     }
+
+    @Test
+    public void testUsesPointerSourceByDefault() {
+        final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
+                ACTION_DOWN, 0 /* x */, 0 /* y */, 0 /* metaState */);
+        assertTrue(event.isFromSource(SOURCE_CLASS_POINTER));
+    }
+
+    @Test
+    public void testLocationOffsetOnlyAppliedToNonPointerSources() {
+        final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
+                ACTION_DOWN, 10 /* x */, 20 /* y */, 0 /* metaState */);
+        event.offsetLocation(40, 50);
+
+        // The offset should be applied since a pointer source is used by default.
+        assertEquals(50, (int) event.getX());
+        assertEquals(70, (int) event.getY());
+
+        // The offset should not be applied if the source is changed to a non-pointer source.
+        event.setSource(InputDevice.SOURCE_JOYSTICK);
+        assertEquals(10, (int) event.getX());
+        assertEquals(20, (int) event.getY());
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 4733f86..69e617a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -83,7 +83,7 @@
         final Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(outBatteryUsageStats, 0);
 
-        assertThat(parcel.dataSize()).isLessThan(5000);
+        assertThat(parcel.dataSize()).isLessThan(5500);
 
         parcel.setDataPosition(0);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 48a1da1..8d9d79d 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -26,6 +26,8 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
 import android.telephony.DataConnectionRealTimeInfo;
@@ -37,12 +39,15 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.google.common.collect.Range;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SuppressWarnings("GuardedBy")
 public class MobileRadioPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
@@ -55,7 +60,7 @@
             .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0)
             .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0)
             .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX,
-                    new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0})
+                    new double[]{720.0, 1080.0, 1440.0, 1800.0, 2160.0})
             .initMeasuredEnergyStatsLocked();
 
     @Test
@@ -81,7 +86,7 @@
 
         // Note established network
         stats.noteNetworkInterfaceForTransports("cellular",
-                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR });
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
@@ -89,7 +94,7 @@
         mStatsRule.setNetworkStats(networkStats);
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
-                new int[] {100, 200, 300, 400, 500}, 600);
+                new int[]{100, 200, 300, 400, 500}, 600);
         stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000);
 
         mStatsRule.setTime(12_000_000, 12_000_000);
@@ -119,6 +124,90 @@
     }
 
     @Test
+    public void testTimerBasedModel_byProcessState() {
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+        BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
+
+        mStatsRule.setTime(1000, 1000);
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Note application network activity
+        mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100));
+
+        stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000);
+
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000);
+
+        mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 250, 2000, 80, 200));
+
+        stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000);
+
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC()).isAtMost(0);
+        // 12000-8000 = 4000 ms == 4_000_000 us
+        assertThat(uid.getMobileRadioActiveTimeInProcessState(BatteryConsumer.PROCESS_STATE_ANY))
+                .isEqualTo(4_000_000);
+        assertThat(uid.getMobileRadioActiveTimeInProcessState(
+                BatteryConsumer.PROCESS_STATE_FOREGROUND))
+                .isEqualTo(3_000_000);
+        assertThat(uid.getMobileRadioActiveTimeInProcessState(
+                BatteryConsumer.PROCESS_STATE_BACKGROUND))
+                .isEqualTo(1_000_000);
+        assertThat(uid.getMobileRadioActiveTimeInProcessState(
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE))
+                .isEqualTo(0);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
+                .powerProfileModeledOnly()
+                .includePowerModels()
+                .includeProcessStateData()
+                .build(), calculator);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+
+        final BatteryConsumer.Key foreground = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        final BatteryConsumer.Key background = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND);
+        final BatteryConsumer.Key fgs = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+        assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(1.2);
+        assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.4);
+        assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
+    }
+
+    @Test
     public void testMeasuredEnergyBasedModel() {
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
@@ -141,7 +230,7 @@
 
         // Note established network
         stats.noteNetworkInterfaceForTransports("cellular",
-                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR });
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
@@ -149,7 +238,7 @@
         mStatsRule.setNetworkStats(networkStats);
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
-                new int[] {100, 200, 300, 400, 500}, 600);
+                new int[]{100, 200, 300, 400, 500}, 600);
         stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000);
 
         mStatsRule.setTime(12_000_000, 12_000_000);
@@ -177,4 +266,87 @@
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
+
+    @Test
+    public void testMeasuredEnergyBasedModel_byProcessState() {
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+        BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
+
+        mStatsRule.setTime(1000, 1000);
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Note application network activity
+        mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100));
+
+        stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000);
+
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000);
+
+        mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 250, 2000, 80, 200));
+
+        stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000);
+
+        mStatsRule.setTime(20000, 20000);
+
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC())
+                .isIn(Range.open(20_000_000L, 21_000_000L));
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+                BatteryConsumer.PROCESS_STATE_FOREGROUND))
+                .isIn(Range.open(13_000_000L, 14_000_000L));
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+                BatteryConsumer.PROCESS_STATE_BACKGROUND))
+                .isIn(Range.open(7_000_000L, 8_000_000L));
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE))
+                .isEqualTo(0);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
+                .includePowerModels()
+                .includeProcessStateData()
+                .build(), calculator);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+
+        final BatteryConsumer.Key foreground = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        final BatteryConsumer.Key background = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND);
+        final BatteryConsumer.Key fgs = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+        assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(3.62064);
+        assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(2.08130);
+        assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index e4c83f1..4faf349 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -233,6 +233,11 @@
         public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) {
             return null;
         }
+
+        @Override
+        public Future<?> scheduleSyncDueToProcessStateChange(long delayMillis) {
+            return null;
+        }
     }
 }
 
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 624940b..e61a665 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -427,7 +427,6 @@
         <permission name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS" />
         <permission name="android.permission.MANAGE_COMPANION_DEVICES" />
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
-        <permission name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" />
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
         <permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
         <!-- Permission required for testing registering pull atom callbacks. -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 6bfbd8d..9584994 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1201,6 +1201,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-846931068": {
+      "message": "Update camera compat control state to %s for taskId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
+    },
     "-846078709": {
       "message": "Configuration doesn't matter in finishing %s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml b/libs/WindowManager/Shell/res/color/compat_background_ripple.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml
rename to libs/WindowManager/Shell/res/color/compat_background_ripple.xml
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml
new file mode 100644
index 0000000..1c8cb91
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_button.xml
@@ -0,0 +1,33 @@
+<!--
+     Copyright (C) 2019 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="43dp"
+        android:viewportWidth="48"
+        android:viewportHeight="43">
+    <group>
+        <clip-path
+                android:pathData="M48,43l-48,-0l-0,-43l48,-0z"/>
+        <path
+                android:pathData="M24,43C37.2548,43 48,32.2548 48,19L48,0L0,-0L0,19C0,32.2548 10.7452,43 24,43Z"
+                android:fillColor="@color/compat_controls_background"
+                android:strokeAlpha="0.8"
+                android:fillAlpha="0.8"/>
+        <path
+                android:pathData="M31,12.41L29.59,11L24,16.59L18.41,11L17,12.41L22.59,18L17,23.59L18.41,25L24,19.41L29.59,25L31,23.59L25.41,18L31,12.41Z"
+                android:fillColor="@color/compat_controls_text"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml
new file mode 100644
index 0000000..c810139
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_dismiss_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/compat_background_ripple">
+    <item android:drawable="@drawable/camera_compat_dismiss_button"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml
new file mode 100644
index 0000000..c796b59
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_button.xml
@@ -0,0 +1,32 @@
+<!--
+     Copyright (C) 2019 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="43dp"
+        android:viewportWidth="48"
+        android:viewportHeight="43">
+    <path
+            android:pathData="M24,0C10.7452,0 0,10.7452 0,24V43H48V24C48,10.7452 37.2548,0 24,0Z"
+            android:fillColor="@color/compat_controls_background"
+            android:strokeAlpha="0.8"
+            android:fillAlpha="0.8"/>
+    <path
+            android:pathData="M32,17H28.83L27,15H21L19.17,17H16C14.9,17 14,17.9 14,19V31C14,32.1 14.9,33 16,33H32C33.1,33 34,32.1 34,31V19C34,17.9 33.1,17 32,17ZM32,31H16V19H32V31Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M24.6618,22C23.0436,22 21.578,22.6187 20.4483,23.625L18.25,21.375V27H23.7458L21.5353,24.7375C22.3841,24.0125 23.4649,23.5625 24.6618,23.5625C26.8235,23.5625 28.6616,25.0062 29.3028,27L30.75,26.5125C29.9012,23.8938 27.5013,22 24.6618,22Z"
+            android:fillColor="@color/compat_controls_text"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml
new file mode 100644
index 0000000..3e9fe6d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_applied_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/compat_background_ripple">
+    <item android:drawable="@drawable/camera_compat_treatment_applied_button"/>
+</ripple>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml
new file mode 100644
index 0000000..af505d1
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_button.xml
@@ -0,0 +1,53 @@
+<!--
+     Copyright (C) 2019 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="43dp"
+        android:viewportWidth="48"
+        android:viewportHeight="43">
+    <path
+            android:pathData="M24,0C10.7452,0 0,10.7452 0,24V43H48V24C48,10.7452 37.2548,0 24,0Z"
+            android:fillColor="@color/compat_controls_background"
+            android:strokeAlpha="0.8"
+            android:fillAlpha="0.8"/>
+    <path
+            android:pathData="M32,17H28.83L27,15H21L19.17,17H16C14.9,17 14,17.9 14,19V31C14,32.1 14.9,33 16,33H32C33.1,33 34,32.1 34,31V19C34,17.9 33.1,17 32,17ZM32,31H16V19H32V31Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M18,29L18,25.5L19.5,25.5L19.5,29L18,29Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M30,29L30,25.5L28.5,25.5L28.5,29L30,29Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M30,21L30,24.5L28.5,24.5L28.5,21L30,21Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M18,21L18,24.5L19.5,24.5L19.5,21L18,21Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M18,27.5L21.5,27.5L21.5,29L18,29L18,27.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M30,27.5L26.5,27.5L26.5,29L30,29L30,27.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M30,22.5L26.5,22.5L26.5,21L30,21L30,22.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+    <path
+            android:pathData="M18,22.5L21.5,22.5L21.5,21L18,21L18,22.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml
new file mode 100644
index 0000000..c0f1c89
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/camera_compat_treatment_suggested_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/compat_background_ripple">
+    <item android:drawable="@drawable/camera_compat_treatment_suggested_button"/>
+</ripple>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index ab74e43..e6ae282 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -21,7 +21,9 @@
         android:viewportHeight="48">
     <path
         android:fillColor="@color/compat_controls_background"
-        android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" />
+        android:strokeAlpha="0.8"
+        android:fillAlpha="0.8"
+        android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
     <group
         android:translateX="12"
         android:translateY="12">
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
index 95decff..6551edf 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
@@ -15,6 +15,6 @@
   ~ limitations under the License.
   -->
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/size_compat_background_ripple">
+        android:color="@color/compat_background_ripple">
     <item android:drawable="@drawable/size_compat_restart_button"/>
 </ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
index c04e258e..4ac972c 100644
--- a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
@@ -16,7 +16,7 @@
 -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:clipToPadding="false"
@@ -26,7 +26,7 @@
 
     <TextView
         android:id="@+id/compat_mode_hint_text"
-        android:layout_width="188dp"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:lineSpacingExtra="4sp"
         android:background="@drawable/compat_hint_bubble"
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index 6f946b2..c99f3fe 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -21,24 +21,51 @@
     android:orientation="vertical"
     android:gravity="bottom|end">
 
-    <include android:id="@+id/size_compat_hint"
-         layout="@layout/compat_mode_hint"/>
+    <include android:id="@+id/camera_compat_hint"
+        android:visibility="gone"
+        android:layout_width="@dimen/camera_compat_hint_width"
+        android:layout_height="wrap_content"
+        layout="@layout/compat_mode_hint"/>
 
-    <FrameLayout
-        android:layout_width="@dimen/size_compat_button_width"
-        android:layout_height="@dimen/size_compat_button_height"
+    <LinearLayout
+        android:id="@+id/camera_compat_control"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:clipToPadding="false"
-        android:paddingBottom="16dp">
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="16dp"
+        android:orientation="vertical">
 
         <ImageButton
-            android:id="@+id/size_compat_restart_button"
+            android:id="@+id/camera_compat_treatment_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:src="@drawable/size_compat_restart_button_ripple"
-            android:background="@android:color/transparent"
-            android:contentDescription="@string/restart_button_description"/>
+            android:background="@android:color/transparent"/>
 
-    </FrameLayout>
+        <ImageButton
+            android:id="@+id/camera_compat_dismiss_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/camera_compat_dismiss_ripple"
+            android:background="@android:color/transparent"
+            android:contentDescription="@string/camera_compat_dismiss_button_description"/>
+
+    </LinearLayout>
+
+    <include android:id="@+id/size_compat_hint"
+        android:visibility="gone"
+        android:layout_width="@dimen/size_compat_hint_width"
+        android:layout_height="wrap_content"
+        layout="@layout/compat_mode_hint"/>
+
+    <ImageButton
+        android:id="@+id/size_compat_restart_button"
+        android:visibility="gone"
+        android:layout_width="@dimen/size_compat_button_width"
+        android:layout_height="@dimen/size_compat_button_height"
+        android:src="@drawable/size_compat_restart_button_ripple"
+        android:background="@android:color/transparent"
+        android:contentDescription="@string/restart_button_description"/>
 
 </com.android.wm.shell.compatui.CompatUILayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 18e91f4..d338e3b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -216,6 +216,12 @@
          - compat_hint_corner_radius - compat_hint_point_width /2). -->
     <dimen name="compat_hint_padding_end">7dp</dimen>
 
+    <!-- The width of the size compat hint. -->
+    <dimen name="size_compat_hint_width">188dp</dimen>
+
+    <!-- The width of the camera compat hint. -->
+    <dimen name="camera_compat_hint_width">143dp</dimen>
+
     <!-- The width of the brand image on staring surface. -->
     <dimen name="starting_surface_brand_image_width">200dp</dimen>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index c88fc16..ab0013a 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -158,4 +158,17 @@
 
     <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
     <string name="restart_button_description">Tap to restart this app and go full screen.</string>
+
+    <!-- Description of the camera compat button for applying stretched issues treatment in the hint for
+         compatibility control. [CHAR LIMIT=NONE] -->
+    <string name="camera_compat_treatment_suggested_button_description">Camera issues?\nTap to refit</string>
+
+    <!-- Description of the camera compat button for reverting stretched issues treatment in the hint for
+         compatibility control. [CHAR LIMIT=NONE] -->
+    <string name="camera_compat_treatment_applied_button_description">Didn\u2019t fix it?\nTap to revert</string>
+
+    <!-- Accessibillity description of the camera dismiss button for stretched issues in the hint for
+         compatibility control. [CHAR LIMIT=NONE] -->
+    <string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string>
+
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 8b3a356..91ea436 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -458,7 +458,7 @@
                 newListener.onTaskInfoChanged(taskInfo);
             }
             notifyLocusVisibilityIfNeeded(taskInfo);
-            if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
+            if (updated || !taskInfo.equalsForCompatUi(data.getTaskInfo())) {
                 // Notify the compat UI if the listener or task info changed.
                 notifyCompatUI(taskInfo, newListener);
             }
@@ -607,6 +607,19 @@
         restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
     }
 
+    @Override
+    public void onCameraControlStateUpdated(
+            int taskId, @TaskInfo.CameraCompatControlState int state) {
+        final TaskAppearedInfo info;
+        synchronized (mLock) {
+            info = mTasks.get(taskId);
+        }
+        if (info == null) {
+            return;
+        }
+        updateCameraCompatControlState(info.getTaskInfo().token, state);
+    }
+
     private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
             int event) {
         ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -633,14 +646,11 @@
         // The task is vanished or doesn't support compat UI, notify to remove compat UI
         // on this Task if there is any.
         if (taskListener == null || !taskListener.supportCompatUI()
-                || !taskInfo.topActivityInSizeCompat || !taskInfo.isVisible) {
-            mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
-                    null /* taskConfig */, null /* taskListener */);
+                || !taskInfo.hasCompatUI() || !taskInfo.isVisible) {
+            mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */);
             return;
         }
-
-        mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
-                taskInfo.configuration, taskListener);
+        mCompatUI.onCompatInfoChanged(taskInfo, taskListener);
     }
 
     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b8ac87f..a9e0c48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -180,8 +180,6 @@
 
     /** Applies new configuration, returns {@code false} if there's no effect to the layout. */
     public boolean updateConfiguration(Configuration configuration) {
-        boolean affectsLayout = false;
-
         // Update the split bounds when necessary. Besides root bounds changed, split bounds need to
         // be updated when the rotation changed to cover the case that users rotated the screen 180
         // degrees.
@@ -214,6 +212,24 @@
         return true;
     }
 
+    /** Rotate the layout to specific rotation and assume its new bounds. */
+    public void rotateTo(int newRotation) {
+        final int rotationDelta = (newRotation - mRotation + 4) % 4;
+        final boolean changeOrient = (rotationDelta % 2) != 0;
+
+        mRotation = newRotation;
+        Rect tmpRect = new Rect(mRootBounds);
+        if (changeOrient) {
+            tmpRect.set(mRootBounds.top, mRootBounds.left, mRootBounds.bottom, mRootBounds.right);
+        }
+
+        final Configuration config = new Configuration();
+        config.setTo(mContext.getResources().getConfiguration());
+        config.orientation = mOrientation;
+        config.windowConfiguration.setBounds(tmpRect);
+        updateConfiguration(config);
+    }
+
     private void initDividerPosition(Rect oldBounds) {
         final float snapRatio = (float) mDividePosition
                 / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index e0b2387..8f4cfb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -17,6 +17,8 @@
 package com.android.wm.shell.compatui;
 
 import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
@@ -53,12 +55,14 @@
 public class CompatUIController implements OnDisplaysChangedListener,
         DisplayImeController.ImePositionProcessor {
 
-    /** Callback for size compat UI interaction. */
+    /** Callback for compat UI interaction. */
     public interface CompatUICallback {
         /** Called when the size compat restart button appears. */
         void onSizeCompatRestartButtonAppeared(int taskId);
         /** Called when the size compat restart button is clicked. */
         void onSizeCompatRestartButtonClicked(int taskId);
+        /** Called when the camera compat control state is updated. */
+        void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state);
     }
 
     private static final String TAG = "CompatUIController";
@@ -86,10 +90,12 @@
 
     private CompatUICallback mCallback;
 
-    /** Only show once automatically in the process life. */
-    private boolean mHasShownHint;
-    /** Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't
-     * be shown. */
+    // Only show once automatically in the process life.
+    private boolean mHasShownSizeCompatHint;
+    private boolean mHasShownCameraCompatHint;
+
+    // Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't
+    // be shown.
     private boolean mKeyguardOccluded;
 
     public CompatUIController(Context context,
@@ -122,23 +128,20 @@
      * Called when the Task info changed. Creates and updates the compat UI if there is an
      * activity in size compat, or removes the UI if there is no size compat activity.
      *
-     * @param displayId display the task and activity are in.
-     * @param taskId task the activity is in.
-     * @param taskConfig task config to place the compat UI with.
+     * @param taskInfo {@link TaskInfo} task the activity is in.
      * @param taskListener listener to handle the Task Surface placement.
      */
-    public void onCompatInfoChanged(int displayId, int taskId,
-            @Nullable Configuration taskConfig,
+    public void onCompatInfoChanged(TaskInfo taskInfo,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
-        if (taskConfig == null || taskListener == null) {
+        if (taskInfo.configuration == null || taskListener == null) {
             // Null token means the current foreground activity is not in compatibility mode.
-            removeLayout(taskId);
-        } else if (mActiveLayouts.contains(taskId)) {
+            removeLayout(taskInfo.taskId);
+        } else if (mActiveLayouts.contains(taskInfo.taskId)) {
             // UI already exists, update the UI layout.
-            updateLayout(taskId, taskConfig, taskListener);
+            updateLayout(taskInfo, taskListener);
         } else {
             // Create a new compat UI.
-            createLayout(displayId, taskId, taskConfig, taskListener);
+            createLayout(taskInfo, taskListener);
         }
     }
 
@@ -215,38 +218,45 @@
         return mDisplaysWithIme.contains(displayId);
     }
 
-    private void createLayout(int displayId, int taskId, Configuration taskConfig,
-            ShellTaskOrganizer.TaskListener taskListener) {
-        final Context context = getOrCreateDisplayContext(displayId);
+    private void createLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
         if (context == null) {
-            Log.e(TAG, "Cannot get context for display " + displayId);
+            Log.e(TAG, "Cannot get context for display " + taskInfo.displayId);
             return;
         }
 
         final CompatUIWindowManager compatUIWindowManager =
-                createLayout(context, displayId, taskId, taskConfig, taskListener);
-        mActiveLayouts.put(taskId, compatUIWindowManager);
-        compatUIWindowManager.createLayout(showOnDisplay(displayId));
+                createLayout(context, taskInfo, taskListener);
+        mActiveLayouts.put(taskInfo.taskId, compatUIWindowManager);
+        compatUIWindowManager.createLayout(showOnDisplay(taskInfo.displayId),
+                taskInfo.topActivityInSizeCompat, taskInfo.cameraCompatControlState);
     }
 
     @VisibleForTesting
-    CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
-            Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
+    CompatUIWindowManager createLayout(Context context, TaskInfo taskInfo,
+            ShellTaskOrganizer.TaskListener taskListener) {
         final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context,
-                taskConfig, mSyncQueue, mCallback, taskId, taskListener,
-                mDisplayController.getDisplayLayout(displayId), mHasShownHint);
-        // Only show hint for the first time.
-        mHasShownHint = true;
+                taskInfo.configuration, mSyncQueue, mCallback, taskInfo.taskId, taskListener,
+                mDisplayController.getDisplayLayout(taskInfo.displayId), mHasShownSizeCompatHint,
+                mHasShownCameraCompatHint);
+        // Only show hints for the first time.
+        if (taskInfo.topActivityInSizeCompat) {
+            mHasShownSizeCompatHint = true;
+        }
+        if (taskInfo.hasCameraCompatControl()) {
+            mHasShownCameraCompatHint = true;
+        }
         return compatUIWindowManager;
     }
 
-    private void updateLayout(int taskId, Configuration taskConfig,
-            ShellTaskOrganizer.TaskListener taskListener) {
-        final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
+    private void updateLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+        final CompatUIWindowManager layout = mActiveLayouts.get(taskInfo.taskId);
         if (layout == null) {
             return;
         }
-        layout.updateCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId()));
+        layout.updateCompatInfo(taskInfo.configuration, taskListener,
+                showOnDisplay(layout.getDisplayId()), taskInfo.topActivityInSizeCompat,
+                taskInfo.cameraCompatControlState);
     }
 
     private void removeLayout(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index ea4f209..29b2baa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.compatui;
 
+import android.annotation.IdRes;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
@@ -53,6 +56,53 @@
         mWindowManager = windowManager;
     }
 
+    void updateCameraTreatmentButton(@CameraCompatControlState int newState) {
+        int buttonBkgId = newState == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+                ? R.drawable.camera_compat_treatment_suggested_ripple
+                : R.drawable.camera_compat_treatment_applied_ripple;
+        int hintStringId = newState == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+                ? R.string.camera_compat_treatment_suggested_button_description
+                : R.string.camera_compat_treatment_applied_button_description;
+        final ImageButton button = findViewById(R.id.camera_compat_treatment_button);
+        button.setImageResource(buttonBkgId);
+        button.setContentDescription(getResources().getString(hintStringId));
+        final LinearLayout hint = findViewById(R.id.camera_compat_hint);
+        ((TextView) hint.findViewById(R.id.compat_mode_hint_text)).setText(hintStringId);
+    }
+
+    void setSizeCompatHintVisibility(boolean show) {
+        setViewVisibility(R.id.size_compat_hint, show);
+    }
+
+    void setCameraCompatHintVisibility(boolean show) {
+        setViewVisibility(R.id.camera_compat_hint, show);
+    }
+
+    void setRestartButtonVisibility(boolean show) {
+        setViewVisibility(R.id.size_compat_restart_button, show);
+        // Hint should never be visible without button.
+        if (!show) {
+            setSizeCompatHintVisibility(/* show= */ false);
+        }
+    }
+
+    void setCameraControlVisibility(boolean show) {
+        setViewVisibility(R.id.camera_compat_control, show);
+        // Hint should never be visible without button.
+        if (!show) {
+            setCameraCompatHintVisibility(/* show= */ false);
+        }
+    }
+
+    private void setViewVisibility(@IdRes int resId, boolean show) {
+        final View view = findViewById(resId);
+        int visibility = show ? View.VISIBLE : View.GONE;
+        if (view.getVisibility() == visibility) {
+            return;
+        }
+        view.setVisibility(visibility);
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -61,15 +111,6 @@
         mWindowManager.relayout();
     }
 
-    void setSizeCompatHintVisibility(boolean show) {
-        final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint);
-        int visibility = show ? View.VISIBLE : View.GONE;
-        if (sizeCompatHint.getVisibility() == visibility) {
-            return;
-        }
-        sizeCompatHint.setVisibility(visibility);
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -85,5 +126,26 @@
         ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
                 .setText(R.string.restart_button_description);
         sizeCompatHint.setOnClickListener(view -> setSizeCompatHintVisibility(/* show= */ false));
+
+        final ImageButton cameraTreatmentButton =
+                findViewById(R.id.camera_compat_treatment_button);
+        cameraTreatmentButton.setOnClickListener(
+                view -> mWindowManager.onCameraTreatmentButtonClicked());
+        cameraTreatmentButton.setOnLongClickListener(view -> {
+            mWindowManager.onCameraButtonLongClicked();
+            return true;
+        });
+
+        final ImageButton cameraDismissButton = findViewById(R.id.camera_compat_dismiss_button);
+        cameraDismissButton.setOnClickListener(
+                view -> mWindowManager.onCameraDismissButtonClicked());
+        cameraDismissButton.setOnLongClickListener(view -> {
+            mWindowManager.onCameraButtonLongClicked();
+            return true;
+        });
+
+        final LinearLayout cameraCompatHint = findViewById(R.id.camera_compat_hint);
+        cameraCompatHint.setOnClickListener(
+                view -> setCameraCompatHintVisibility(/* show= */ false));
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 997ad04..44526b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -23,6 +27,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import android.annotation.Nullable;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -63,8 +68,17 @@
     private ShellTaskOrganizer.TaskListener mTaskListener;
     private DisplayLayout mDisplayLayout;
 
+    // Remember the last reported states in case visibility changes due to keyguard or
+    // IME updates.
     @VisibleForTesting
-    boolean mShouldShowHint;
+    boolean mHasSizeCompat;
+    @CameraCompatControlState
+    private int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
+
+    @VisibleForTesting
+    boolean mShouldShowSizeCompatHint;
+    @VisibleForTesting
+    boolean mShouldShowCameraCompatHint;
 
     @Nullable
     @VisibleForTesting
@@ -78,7 +92,7 @@
     CompatUIWindowManager(Context context, Configuration taskConfig,
             SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback,
             int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
-            boolean hasShownHint) {
+             boolean hasShownSizeCompatHint, boolean hasShownCameraCompatHint) {
         super(taskConfig, null /* rootSurface */, null /* hostInputToken */);
         mContext = context;
         mSyncQueue = syncQueue;
@@ -88,7 +102,8 @@
         mTaskId = taskId;
         mTaskListener = taskListener;
         mDisplayLayout = displayLayout;
-        mShouldShowHint = !hasShownHint;
+        mShouldShowSizeCompatHint = !hasShownSizeCompatHint;
+        mShouldShowCameraCompatHint = !hasShownCameraCompatHint;
         mStableBounds = new Rect();
         mDisplayLayout.getStableBounds(mStableBounds);
     }
@@ -113,7 +128,10 @@
     }
 
     /** Creates the layout for compat controls. */
-    void createLayout(boolean show) {
+    void createLayout(boolean show, boolean hasSizeCompat,
+            @CameraCompatControlState int cameraCompatControlState) {
+        mHasSizeCompat = hasSizeCompat;
+        mCameraCompatControlState = cameraCompatControlState;
         if (!show || mCompatUILayout != null) {
             // Wait until compat controls should be visible.
             return;
@@ -122,16 +140,27 @@
         initCompatUi();
         updateSurfacePosition();
 
-        mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+        if (hasSizeCompat) {
+            mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+        }
+    }
+
+    private void createLayout(boolean show) {
+        createLayout(show, mHasSizeCompat, mCameraCompatControlState);
     }
 
     /** Called when compat info changed. */
     void updateCompatInfo(Configuration taskConfig,
-            ShellTaskOrganizer.TaskListener taskListener, boolean show) {
+            ShellTaskOrganizer.TaskListener taskListener, boolean show, boolean hasSizeCompat,
+            @CameraCompatControlState int cameraCompatControlState) {
         final Configuration prevTaskConfig = mTaskConfig;
         final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
         mTaskConfig = taskConfig;
         mTaskListener = taskListener;
+        final boolean prevHasSizeCompat = mHasSizeCompat;
+        final int prevCameraCompatControlState = mCameraCompatControlState;
+        mHasSizeCompat = hasSizeCompat;
+        mCameraCompatControlState = cameraCompatControlState;
 
         // Update configuration.
         mContext = mContext.createConfigurationContext(taskConfig);
@@ -144,6 +173,11 @@
             return;
         }
 
+        if (prevHasSizeCompat != mHasSizeCompat
+                || prevCameraCompatControlState != mCameraCompatControlState) {
+            updateVisibilityOfViews();
+        }
+
         if (!taskConfig.windowConfiguration.getBounds()
                 .equals(prevTaskConfig.windowConfiguration.getBounds())) {
             // Reposition the UI surfaces.
@@ -155,6 +189,7 @@
             mCompatUILayout.setLayoutDirection(taskConfig.getLayoutDirection());
             updateSurfacePosition();
         }
+
     }
 
     /** Called when the visibility of the UI should change. */
@@ -195,6 +230,34 @@
         mCallback.onSizeCompatRestartButtonClicked(mTaskId);
     }
 
+    /** Called when the camera treatment button is clicked. */
+    void onCameraTreatmentButtonClicked() {
+        if (!shouldShowCameraControl()) {
+            Log.w(TAG, "Camera compat shouldn't receive clicks in the hidden state.");
+            return;
+        }
+        // When a camera control is shown, only two states are allowed: "treament applied" and
+        // "treatment suggested". Clicks on the conrol's treatment button toggle between these
+        // two states.
+        mCameraCompatControlState =
+                mCameraCompatControlState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
+                        ? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED
+                        : CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState);
+        mCompatUILayout.updateCameraTreatmentButton(mCameraCompatControlState);
+    }
+
+    /** Called when the camera dismiss button is clicked. */
+    void onCameraDismissButtonClicked() {
+        if (!shouldShowCameraControl()) {
+            Log.w(TAG, "Camera compat shouldn't receive clicks in the hidden state.");
+            return;
+        }
+        mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED;
+        mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED);
+        mCompatUILayout.setCameraControlVisibility(/* show= */ false);
+    }
+
     /** Called when the restart button is long clicked. */
     void onRestartButtonLongClicked() {
         if (mCompatUILayout == null) {
@@ -203,6 +266,14 @@
         mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true);
     }
 
+    /** Called when either dismiss or treatment camera buttons is long clicked. */
+    void onCameraButtonLongClicked() {
+        if (mCompatUILayout == null) {
+            return;
+        }
+        mCompatUILayout.setCameraCompatHintVisibility(/* show= */ true);
+    }
+
     int getDisplayId() {
         return mDisplayId;
     }
@@ -213,6 +284,8 @@
 
     /** Releases the surface control and tears down the view hierarchy. */
     void release() {
+        // Hiding before releasing to avoid flickering when transitioning to the Home screen.
+        mCompatUILayout.setVisibility(View.GONE);
         mCompatUILayout = null;
 
         if (mViewHost != null) {
@@ -283,12 +356,35 @@
         mCompatUILayout = inflateCompatUILayout();
         mCompatUILayout.inject(this);
 
-        mCompatUILayout.setSizeCompatHintVisibility(mShouldShowHint);
+        updateVisibilityOfViews();
 
         mViewHost.setView(mCompatUILayout, getWindowLayoutParams());
+    }
 
-        // Only show by default for the first time.
-        mShouldShowHint = false;
+    private void updateVisibilityOfViews() {
+        // Size Compat mode restart button.
+        mCompatUILayout.setRestartButtonVisibility(mHasSizeCompat);
+        if (mHasSizeCompat && mShouldShowSizeCompatHint) {
+            mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true);
+            // Only show by default for the first time.
+            mShouldShowSizeCompatHint = false;
+        }
+
+        // Camera control for stretched issues.
+        mCompatUILayout.setCameraControlVisibility(shouldShowCameraControl());
+        if (shouldShowCameraControl() && mShouldShowCameraCompatHint) {
+            mCompatUILayout.setCameraCompatHintVisibility(/* show= */ true);
+            // Only show by default for the first time.
+            mShouldShowCameraCompatHint = false;
+        }
+        if (shouldShowCameraControl()) {
+            mCompatUILayout.updateCameraTreatmentButton(mCameraCompatControlState);
+        }
+    }
+
+    private boolean shouldShowCameraControl() {
+        return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
+                && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index d681a77..d70857a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -161,13 +161,14 @@
             SyncTransactionQueue syncQueue, Context context,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             @ShellMainThread ShellExecutor mainExecutor,
+            DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, Transitions transitions,
             TransactionPool transactionPool, IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
             Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
         return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
-                rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
+                rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
                 displayInsetsController, transitions, transactionPool, iconProvider,
                 recentTasks, stageTaskUnfoldControllerProvider);
     }
@@ -307,9 +308,11 @@
             Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
             PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
             PipBoundsState pipBoundsState, PipTransitionState pipTransitionState,
-            PhonePipMenuController pipMenuController) {
+            PhonePipMenuController pipMenuController,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
         return new PipTransition(context, pipBoundsState, pipTransitionState, pipMenuController,
-                pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
+                pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer,
+                pipSurfaceTransactionHelper);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index b31e6e0..0c443ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -62,6 +62,7 @@
 
     private final PipTransitionState mPipTransitionState;
     private final int mEnterExitAnimationDuration;
+    private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
     private Transitions.TransitionFinishCallback mFinishCallback;
     private Rect mExitDestinationBounds = new Rect();
@@ -74,12 +75,14 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipAnimationController pipAnimationController,
             Transitions transitions,
-            @NonNull ShellTaskOrganizer shellTaskOrganizer) {
+            @NonNull ShellTaskOrganizer shellTaskOrganizer,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
         super(pipBoundsState, pipMenuController, pipBoundsAlgorithm,
                 pipAnimationController, transitions, shellTaskOrganizer);
         mPipTransitionState = pipTransitionState;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
+        mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
     }
 
     @Override
@@ -286,7 +289,10 @@
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
         PipAnimationController.PipTransitionAnimator animator;
-        finishTransaction.setPosition(leash, destinationBounds.left, destinationBounds.top);
+        // Set corner radius for entering pip.
+        mSurfaceTransactionHelper
+                .crop(finishTransaction, leash, destinationBounds)
+                .round(finishTransaction, leash, true /* applyCornerRadius */);
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index afd5117..18cabd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -58,6 +58,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.RemoteCallable;
@@ -124,6 +125,7 @@
     private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
     private final ShellExecutor mMainExecutor;
     private final SplitScreenImpl mImpl = new SplitScreenImpl();
+    private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
     private final DisplayInsetsController mDisplayInsetsController;
     private final Transitions mTransitions;
@@ -138,7 +140,8 @@
     public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, Context context,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer,
-            ShellExecutor mainExecutor, DisplayImeController displayImeController,
+            ShellExecutor mainExecutor, DisplayController displayController,
+            DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
             Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
@@ -148,6 +151,7 @@
         mContext = context;
         mRootTDAOrganizer = rootTDAOrganizer;
         mMainExecutor = mainExecutor;
+        mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
         mTransitions = transitions;
@@ -176,7 +180,7 @@
         if (mStageCoordinator == null) {
             // TODO: Multi-display
             mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
-                    mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
+                    mRootTDAOrganizer, mTaskOrganizer, mDisplayController, mDisplayImeController,
                     mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
                     mIconProvider, mRecentTasksOptional, mUnfoldControllerProvider);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 16f4c72..54d8ece 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -144,8 +144,9 @@
 
             if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
                     || sideRoot.equals(change.getContainer()))) {
-                t.setWindowCrop(leash, change.getStartAbsBounds().width(),
-                        change.getStartAbsBounds().height());
+                t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
+                t.setWindowCrop(leash, change.getEndAbsBounds().width(),
+                        change.getEndAbsBounds().height());
             }
             boolean isOpening = isOpeningType(info.getType());
             if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 43a1d74b..d217686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -21,7 +21,9 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
@@ -83,6 +85,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -118,7 +121,8 @@
  * {@link #onStageHasChildrenChanged(StageListenerImpl).}
  */
 class StageCoordinator implements SplitLayout.SplitLayoutHandler,
-        RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
+        RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener,
+        DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler {
 
     private static final String TAG = StageCoordinator.class.getSimpleName();
 
@@ -142,8 +146,10 @@
     private DisplayAreaInfo mDisplayAreaInfo;
     private final Context mContext;
     private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+    private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
     private final DisplayInsetsController mDisplayInsetsController;
+    private final TransactionPool mTransactionPool;
     private final SplitScreenTransitions mSplitTransitions;
     private final SplitscreenEventLogger mLogger;
     private final Optional<RecentTasksController> mRecentTasks;
@@ -163,7 +169,7 @@
         if (!isSplitScreenVisible()) {
             // Update divider state after animation so that it is still around and positioned
             // properly for the animation itself.
-            setDividerVisibility(false);
+            mSplitLayout.release();
             mSplitLayout.resetDividerPosition();
         }
     };
@@ -183,6 +189,7 @@
 
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+            DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, Transitions transitions,
             TransactionPool transactionPool, SplitscreenEventLogger logger,
@@ -217,8 +224,10 @@
                 mSurfaceSession,
                 iconProvider,
                 mSideUnfoldController);
+        mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
+        mTransactionPool = transactionPool;
         mRootTDAOrganizer.registerListener(displayId, this);
         final DeviceStateManager deviceStateManager =
                 mContext.getSystemService(DeviceStateManager.class);
@@ -226,13 +235,15 @@
                 new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
         mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
                 mOnTransitionAnimationComplete);
+        mDisplayController.addDisplayWindowListener(this);
         transitions.addHandler(this);
     }
 
     @VisibleForTesting
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
-            MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
+            MainStage mainStage, SideStage sideStage, DisplayController displayController,
+            DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
             Transitions transitions, TransactionPool transactionPool,
             SplitscreenEventLogger logger,
@@ -245,8 +256,10 @@
         mTaskOrganizer = taskOrganizer;
         mMainStage = mainStage;
         mSideStage = sideStage;
+        mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
+        mTransactionPool = transactionPool;
         mRootTDAOrganizer.registerListener(displayId, this);
         mSplitLayout = splitLayout;
         mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
@@ -255,6 +268,7 @@
         mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
         mLogger = logger;
         mRecentTasks = recentTasks;
+        mDisplayController.addDisplayWindowListener(this);
         transitions.addHandler(this);
     }
 
@@ -630,8 +644,8 @@
                 .setWindowCrop(mSideStage.mRootLeash, null));
 
         // Hide divider and reset its position.
-        setDividerVisibility(false);
         mSplitLayout.resetDividerPosition();
+        mSplitLayout.release();
         mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason));
         // Log the exit
@@ -811,29 +825,12 @@
         }
     }
 
-    private void setDividerVisibility(boolean visible) {
-        if (mDividerVisible == visible) return;
-        mDividerVisible = visible;
-        if (visible) {
-            mSplitLayout.init();
-            updateUnfoldBounds();
-        } else {
-            mSplitLayout.release();
-        }
-        sendSplitVisibilityChanged();
-    }
-
     private void onStageVisibilityChanged(StageListenerImpl stageListener) {
         final boolean sideStageVisible = mSideStageListener.mVisible;
         final boolean mainStageVisible = mMainStageListener.mVisible;
         final boolean bothStageVisible = sideStageVisible && mainStageVisible;
         final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
         final boolean sameVisibility = sideStageVisible == mainStageVisible;
-        // Only add or remove divider when both visible or both invisible to avoid sometimes we only
-        // got one stage visibility changed for a moment and it will cause flicker.
-        if (sameVisibility) {
-            setDividerVisibility(bothStageVisible);
-        }
 
         if (bothStageInvisible) {
             if (mExitSplitScreenOnHide
@@ -854,11 +851,21 @@
             if (sameVisibility) {
                 t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
                         .setVisibility(mMainStage.mRootLeash, bothStageVisible);
-                applyDividerVisibility(t);
+                setDividerVisibility(bothStageVisible, t);
             }
         });
     }
 
+    private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
+        mDividerVisible = visible;
+        sendSplitVisibilityChanged();
+        if (t != null) {
+            applyDividerVisibility(t);
+        } else {
+            mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction));
+        }
+    }
+
     private void applyDividerVisibility(SurfaceControl.Transaction t) {
         final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
         if (dividerLeash == null) {
@@ -866,11 +873,10 @@
         }
 
         if (mDividerVisible) {
-            t.show(dividerLeash)
-                    .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
-                    .setPosition(dividerLeash,
-                            mSplitLayout.getDividerBounds().left,
-                            mSplitLayout.getDividerBounds().top);
+            t.show(dividerLeash);
+            t.setLayer(dividerLeash, SPLIT_DIVIDER_LAYER);
+            t.setPosition(dividerLeash,
+                    mSplitLayout.getDividerBounds().left, mSplitLayout.getDividerBounds().top);
         } else {
             t.hide(dividerLeash);
         }
@@ -1050,10 +1056,39 @@
         if (mSplitLayout != null
                 && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
                 && mMainStage.isActive()) {
+            // TODO(b/204925795): With Shell transition, We are handle roation case for apply split
+            //  bounds at onRotateDisplay. But still need to handle unfold case.
+            if (ENABLE_SHELL_TRANSITIONS) {
+                updateUnfoldBounds();
+                return;
+            }
             onLayoutSizeChanged(mSplitLayout);
         }
     }
 
+    @Override
+    public void onDisplayAdded(int displayId) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+        mDisplayController.addDisplayChangingController(this::onRotateDisplay);
+    }
+
+    private void onRotateDisplay(int displayId, int fromRotation, int toRotation,
+            WindowContainerTransaction wct) {
+        if (!mMainStage.isActive()) return;
+        // Only do this when shell transition
+        if (!ENABLE_SHELL_TRANSITIONS) return;
+
+        final SurfaceControl.Transaction t = mTransactionPool.acquire();
+        setDividerVisibility(false, t);
+        mSplitLayout.rotateTo(toRotation);
+        updateWindowBounds(mSplitLayout, wct);
+        updateUnfoldBounds();
+        t.apply();
+        mTransactionPool.release(t);
+    }
+
     private void onFoldedStateChanged(boolean folded) {
         mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         if (!folded) return;
@@ -1192,6 +1227,10 @@
                         Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
                                 + " with " + taskInfo.taskId + " before startAnimation().");
                     }
+                } else if (info.getType() == TRANSIT_CHANGE
+                        && change.getStartRotation() != change.getEndRotation()) {
+                    // Show the divider after transition finished.
+                    setDividerVisibility(true, finishTransaction);
                 }
             }
             if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
@@ -1216,7 +1255,7 @@
         } else if (mSplitTransitions.mPendingDismiss != null
                 && mSplitTransitions.mPendingDismiss.mTransition == transition) {
             shouldAnimate = startPendingDismissAnimation(
-                    mSplitTransitions.mPendingDismiss, info, startTransaction);
+                    mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
         }
         if (!shouldAnimate) return false;
 
@@ -1249,7 +1288,8 @@
         }
 
         // Update local states (before animating).
-        setDividerVisibility(true);
+        mSplitLayout.init();
+        setDividerVisibility(true, t);
         setSplitsVisible(true);
 
         addDividerBarToTransition(info, t, true /* show */);
@@ -1286,7 +1326,8 @@
 
     private boolean startPendingDismissAnimation(
             @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.Transaction finishT) {
         // Make some noise if things aren't totally expected. These states shouldn't effect
         // transitions locally, but remotes (like Launcher) may get confused if they were
         // depending on listener callbacks. This can happen because task-organizer callbacks
@@ -1343,8 +1384,8 @@
             logExit(dismissTransition.mReason);
             // TODO: Have a proper remote for this. Until then, though, reset state and use the
             //       normal animation stuff (which falls back to the normal launcher remote).
-            t.hide(mSplitLayout.getDividerLeash());
-            setDividerVisibility(false);
+            setDividerVisibility(false, t);
+            mSplitLayout.release();
             mSplitTransitions.mPendingDismiss = null;
             return false;
         } else {
@@ -1356,6 +1397,11 @@
         // We're dismissing split by moving the other one to fullscreen.
         // Since we don't have any animations for this yet, just use the internal example
         // animations.
+
+        // Hide divider and dim layer on transition finished.
+        setDividerVisibility(false, finishT);
+        finishT.hide(mMainStage.mDimLayer);
+        finishT.hide(mSideStage.mDimLayer);
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index a1c0864..324b8b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -358,8 +358,8 @@
 
                 // No default animation for this, so just update bounds/position.
                 startTransaction.setPosition(change.getLeash(),
-                        change.getEndAbsBounds().left - change.getEndRelOffset().x,
-                        change.getEndAbsBounds().top - change.getEndRelOffset().y);
+                        change.getEndAbsBounds().left - info.getRootOffset().x,
+                        change.getEndAbsBounds().top - info.getRootOffset().y);
                 if (isTask) {
                     // Skip non-tasks since those usually have null bounds.
                     startTransaction.setWindowCrop(change.getLeash(),
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 16fc048..59bbdfe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -33,7 +33,6 @@
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Assume.assumeFalse
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -84,12 +83,6 @@
             }
         }
 
-    @Before
-    fun onBefore() {
-        // This CUJ don't work in shell transitions because of b/204570898 b/204562589 b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-    }
-
     /**
      * Checks that all parts of the screen are covered at the start and end of the transition
      */
@@ -128,6 +121,8 @@
     @Presubmit
     @Test
     fun appLayerRotates_EndingBounds() {
+        // This CUJ don't work in shell transitions because of b/204570898 b/204562589 b/206753786
+        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayersEnd {
             visibleRegion(fixedApp.component).coversExactly(screenBoundsEnd)
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index a3b98a8f..825320b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
 import android.content.Context;
 import android.content.LocusId;
 import android.content.pm.ParceledListSlice;
@@ -334,8 +335,7 @@
         mOrganizer.onTaskAppeared(taskInfo1, null);
 
         // sizeCompatActivity is null if top activity is not in size compat.
-        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                null /* taskConfig */, null /* taskListener */);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
 
         // sizeCompatActivity is non-null if top activity is in size compat.
         clearInvocations(mCompatUI);
@@ -345,8 +345,7 @@
         taskInfo2.topActivityInSizeCompat = true;
         taskInfo2.isVisible = true;
         mOrganizer.onTaskInfoChanged(taskInfo2);
-        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                taskInfo1.configuration, taskListener);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
 
         // Not show size compat UI if task is not visible.
         clearInvocations(mCompatUI);
@@ -356,13 +355,82 @@
         taskInfo3.topActivityInSizeCompat = true;
         taskInfo3.isVisible = false;
         mOrganizer.onTaskInfoChanged(taskInfo3);
-        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                null /* taskConfig */, null /* taskListener */);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
 
         clearInvocations(mCompatUI);
         mOrganizer.onTaskVanished(taskInfo1);
-        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                null /* taskConfig */, null /* taskListener */);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+    }
+
+    @Test
+    public void testOnCameraCompatActivityChanged() {
+        final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+        taskInfo1.displayId = DEFAULT_DISPLAY;
+        taskInfo1.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+        final TrackingTaskListener taskListener = new TrackingTaskListener();
+        mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+        mOrganizer.onTaskAppeared(taskInfo1, null);
+
+        // Task listener sent to compat UI is null if top activity doesn't request a camera
+        // compat control.
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+
+        // Task linster is non-null when request a camera compat control for a visible task.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo2 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo2.displayId = taskInfo1.displayId;
+        taskInfo2.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        taskInfo2.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo2);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+
+        // CompatUIController#onCompatInfoChanged is called when requested state for a camera
+        // compat control changes for a visible task.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo3 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo3.displayId = taskInfo1.displayId;
+        taskInfo3.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+        taskInfo3.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo3);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener);
+
+        // CompatUIController#onCompatInfoChanged is called when a top activity goes in size compat
+        // mode for a visible task that has a compat control.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo4 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo4.displayId = taskInfo1.displayId;
+        taskInfo4.topActivityInSizeCompat = true;
+        taskInfo4.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+        taskInfo4.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo4);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener);
+
+        // Task linster is null when a camera compat control is dimissed for a visible task.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo5 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo5.displayId = taskInfo1.displayId;
+        taskInfo5.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+        taskInfo5.isVisible = true;
+        mOrganizer.onTaskInfoChanged(taskInfo5);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */);
+
+        // Task linster is null when request a camera compat control for a invisible task.
+        clearInvocations(mCompatUI);
+        final RunningTaskInfo taskInfo6 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo6.displayId = taskInfo1.displayId;
+        taskInfo6.cameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        taskInfo6.isVisible = false;
+        mOrganizer.onTaskInfoChanged(taskInfo6);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */);
+
+        clearInvocations(mCompatUI);
+        mOrganizer.onTaskVanished(taskInfo1);
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index f622edb..4352fd3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -29,6 +33,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
@@ -90,8 +97,8 @@
         mController = new CompatUIController(mContext, mMockDisplayController,
                 mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
             @Override
-            CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
-                    Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
+            CompatUIWindowManager createLayout(Context context, TaskInfo taskInfo,
+                    ShellTaskOrganizer.TaskListener taskListener) {
                 return mMockLayout;
             }
         };
@@ -106,23 +113,59 @@
 
     @Test
     public void testOnCompatInfoChanged() {
-        final Configuration taskConfig = new Configuration();
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // Verify that the restart button is added with non-null size compat info.
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
-                eq(mMockTaskListener));
+        verify(mController).createLayout(any(), eq(taskInfo), eq(mMockTaskListener));
 
         // Verify that the restart button is updated with non-null new size compat info.
-        final Configuration newTaskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN),
+                mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
-                true /* show */);
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
-        // Verify that the restart button is removed with null size compat info.
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener);
+        // Verify that the restart button is updated with new camera state.
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED),
+                mMockTaskListener);
+
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED),
+                mMockTaskListener);
+
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Verify that compat controls are removed with null compat info.
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN),
+                null /* taskListener */);
+
+        verify(mMockLayout).release();
+
+        clearInvocations(mMockLayout);
+        clearInvocations(mController);
+        // Verify that compat controls are removed with dismissed camera state.
+        taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+        verify(mController).createLayout(any(), eq(taskInfo), eq(mMockTaskListener));
+
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_DISMISSED),
+                null /* taskListener */);
 
         verify(mMockLayout).release();
     }
@@ -139,8 +182,8 @@
     @Test
     public void testOnDisplayRemoved() {
         mController.onDisplayAdded(DISPLAY_ID);
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN),
                 mMockTaskListener);
 
         mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -157,16 +200,14 @@
 
     @Test
     public void testOnDisplayConfigurationChanged() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
-                mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
-        final Configuration newTaskConfig = new Configuration();
-        mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig);
+        mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
 
         verify(mMockLayout, never()).updateDisplayLayout(any());
 
-        mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig);
+        mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration());
 
         verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
     }
@@ -174,9 +215,8 @@
     @Test
     public void testInsetsChanged() {
         mController.onDisplayAdded(DISPLAY_ID);
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
-                mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
         InsetsState insetsState = new InsetsState();
         InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
         insetsSource.setFrame(0, 0, 1000, 1000);
@@ -196,8 +236,8 @@
 
     @Test
     public void testChangeButtonVisibilityOnImeShowHide() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         // Verify that the restart button is hidden after IME is showing.
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
@@ -205,10 +245,11 @@
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button remains hidden while IME is showing.
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
-                false /* show */);
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                false /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // Verify button is shown after IME is hidden.
         mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
@@ -218,8 +259,8 @@
 
     @Test
     public void testChangeButtonVisibilityOnKeyguardOccludedChanged() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         // Verify that the restart button is hidden after keyguard becomes occluded.
         mController.onKeyguardOccludedChanged(true);
@@ -227,10 +268,11 @@
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button remains hidden while keyguard is occluded.
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
-                false /* show */);
+        verify(mMockLayout).updateCompatInfo(new Configuration(), mMockTaskListener,
+                false /* show */,  true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // Verify button is shown after keyguard becomes not occluded.
         mController.onKeyguardOccludedChanged(false);
@@ -240,8 +282,8 @@
 
     @Test
     public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
         mController.onKeyguardOccludedChanged(true);
@@ -263,8 +305,8 @@
 
     @Test
     public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() {
-        final Configuration taskConfig = new Configuration();
-        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
         mController.onKeyguardOccludedChanged(true);
@@ -283,4 +325,14 @@
 
         verify(mMockLayout).updateVisibility(true);
     }
+
+    private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
+            @CameraCompatControlState int cameraCompatControlState) {
+        RunningTaskInfo taskInfo = new RunningTaskInfo();
+        taskInfo.taskId = taskId;
+        taskInfo.displayId = displayId;
+        taskInfo.topActivityInSizeCompat = hasSizeCompat;
+        taskInfo.cameraCompatControlState = cameraCompatControlState;
+        return taskInfo;
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 2c3987b..353d8fe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -16,6 +16,11 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.mockito.Mockito.doNothing;
@@ -69,7 +74,7 @@
 
         mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
                 mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
-                false /* hasShownHint */);
+                false /* hasShownSizeCompatHint */, false /* hasShownCameraCompatHint */);
 
         mCompatUILayout = (CompatUILayout)
                 LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
@@ -78,6 +83,7 @@
         spyOn(mWindowManager);
         spyOn(mCompatUILayout);
         doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+        doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
     }
 
     @Test
@@ -86,7 +92,6 @@
         button.performClick();
 
         verify(mWindowManager).onRestartButtonClicked();
-        doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
         verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
     }
 
@@ -102,10 +107,92 @@
 
     @Test
     public void testOnClickForSizeCompatHint() {
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
         final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint);
         sizeCompatHint.performClick();
 
         verify(mCompatUILayout).setSizeCompatHintVisibility(/* show= */ false);
     }
+
+    @Test
+    public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+        final ImageButton button =
+                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+        button.performClick();
+
+        verify(mWindowManager).onCameraTreatmentButtonClicked();
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        button.performClick();
+
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+    }
+
+    @Test
+    public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        final ImageButton button =
+                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+        button.performClick();
+
+        verify(mWindowManager).onCameraTreatmentButtonClicked();
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        button.performClick();
+
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+    }
+
+    @Test
+    public void testOnCameraDismissButtonClicked() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        final ImageButton button =
+                mCompatUILayout.findViewById(R.id.camera_compat_dismiss_button);
+        button.performClick();
+
+        verify(mWindowManager).onCameraDismissButtonClicked();
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+        verify(mCompatUILayout).setCameraControlVisibility(/* show */ false);
+    }
+
+    @Test
+    public void testOnLongClickForCameraTreatementButton() {
+        doNothing().when(mWindowManager).onCameraButtonLongClicked();
+
+        final ImageButton button =
+                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+        button.performLongClick();
+
+        verify(mWindowManager).onCameraButtonLongClicked();
+    }
+
+    @Test
+    public void testOnLongClickForCameraDismissButton() {
+        doNothing().when(mWindowManager).onCameraButtonLongClicked();
+
+        final ImageButton button = mCompatUILayout.findViewById(R.id.camera_compat_dismiss_button);
+        button.performLongClick();
+
+        verify(mWindowManager).onCameraButtonLongClicked();
+    }
+
+    @Test
+    public void testOnClickForCameraCompatHint() {
+        mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        final LinearLayout hint = mCompatUILayout.findViewById(R.id.camera_compat_hint);
+        hint.performClick();
+
+        verify(mCompatUILayout).setCameraCompatHintVisibility(/* show= */ false);
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d5dcf2e..11c7973 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -23,6 +27,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -81,7 +86,7 @@
 
         mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
                 mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
-                false /* hasShownHint */);
+                false /* hasShownSizeCompatHint */, false /* hasShownSizeCompatHint */);
 
         spyOn(mWindowManager);
         doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
@@ -91,31 +96,35 @@
     @Test
     public void testCreateSizeCompatButton() {
         // Not create layout if show is false.
-        mWindowManager.createLayout(false /* show */);
+        mWindowManager.createLayout(false /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager, never()).inflateCompatUILayout();
 
         // Not create hint popup.
-        mWindowManager.mShouldShowHint = false;
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.mShouldShowSizeCompatHint = false;
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).inflateCompatUILayout();
-        verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+        verify(mCompatUILayout, never()).setSizeCompatHintVisibility(true /* show */);
 
         // Create hint popup.
         mWindowManager.release();
-        mWindowManager.mShouldShowHint = true;
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.mShouldShowSizeCompatHint = true;
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager, times(2)).inflateCompatUILayout();
         assertNotNull(mCompatUILayout);
         verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
-        assertFalse(mWindowManager.mShouldShowHint);
+        assertFalse(mWindowManager.mShouldShowSizeCompatHint);
     }
 
     @Test
     public void testRelease() {
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).inflateCompatUILayout();
 
@@ -126,32 +135,60 @@
 
     @Test
     public void testUpdateCompatInfo() {
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // No diff
         clearInvocations(mWindowManager);
-        mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */);
+        mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager, never()).updateSurfacePosition();
         verify(mWindowManager, never()).release();
-        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt());
 
         // Change task listener, recreate button.
         clearInvocations(mWindowManager);
         final ShellTaskOrganizer.TaskListener newTaskListener = mock(
                 ShellTaskOrganizer.TaskListener.class);
         mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener,
-                true /* show */);
+                true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).release();
-        verify(mWindowManager).createLayout(anyBoolean());
+        verify(mWindowManager).createLayout(anyBoolean(), anyBoolean(), anyInt());
+
+        // Change Camera Compat state, show a control.
+        mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, true /* show */,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        verify(mCompatUILayout).setCameraControlVisibility(/* show */ true);
+        verify(mCompatUILayout).updateCameraTreatmentButton(
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        clearInvocations(mWindowManager);
+        clearInvocations(mCompatUILayout);
+        // Change Camera Compat state, update a control.
+        mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener, true /* show */,
+                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mCompatUILayout).setCameraControlVisibility(/* show */ true);
+        verify(mCompatUILayout).updateCameraTreatmentButton(
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        clearInvocations(mWindowManager);
+        clearInvocations(mCompatUILayout);
+        // Change Camera Compat state to hidden, hide a control.
+        mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener,
+                true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        verify(mCompatUILayout).setCameraControlVisibility(/* show */ false);
 
         // Change task bounds, update position.
         clearInvocations(mWindowManager);
         final Configuration newTaskConfiguration = new Configuration();
         newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
         mWindowManager.updateCompatInfo(newTaskConfiguration, newTaskListener,
-                true /* show */);
+                true /* show */, true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).updateSurfacePosition();
     }
@@ -201,23 +238,25 @@
     public void testUpdateVisibility() {
         // Create button if it is not created.
         mWindowManager.mCompatUILayout = null;
+        mWindowManager.mHasSizeCompat = true;
         mWindowManager.updateVisibility(true /* show */);
 
-        verify(mWindowManager).createLayout(true /* show */);
+        verify(mWindowManager).createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         // Hide button.
         clearInvocations(mWindowManager);
         doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility();
         mWindowManager.updateVisibility(false /* show */);
 
-        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt());
         verify(mCompatUILayout).setVisibility(View.GONE);
 
         // Show button.
         doReturn(View.GONE).when(mCompatUILayout).getVisibility();
         mWindowManager.updateVisibility(true /* show */);
 
-        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mWindowManager, never()).createLayout(anyBoolean(), anyBoolean(), anyInt());
         verify(mCompatUILayout).setVisibility(View.VISIBLE);
     }
 
@@ -230,6 +269,37 @@
     }
 
     @Test
+    public void testOnCameraDismissButtonClicked() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        clearInvocations(mCompatUILayout);
+        mWindowManager.onCameraDismissButtonClicked();
+
+        verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+        verify(mCompatUILayout).setCameraControlVisibility(/* show= */ false);
+    }
+
+    @Test
+    public void testOnCameraTreatmentButtonClicked() {
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        clearInvocations(mCompatUILayout);
+        mWindowManager.onCameraTreatmentButtonClicked();
+
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+        verify(mCompatUILayout).updateCameraTreatmentButton(
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        mWindowManager.onCameraTreatmentButtonClicked();
+
+        verify(mCallback).onCameraControlStateUpdated(
+                TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        verify(mCompatUILayout).updateCameraTreatmentButton(
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+    }
+
+    @Test
     public void testOnRestartButtonClicked() {
         mWindowManager.onRestartButtonClicked();
 
@@ -239,15 +309,60 @@
     @Test
     public void testOnRestartButtonLongClicked_showHint() {
        // Not create hint popup.
-        mWindowManager.mShouldShowHint = false;
-        mWindowManager.createLayout(true /* show */);
+        mWindowManager.mShouldShowSizeCompatHint = false;
+        mWindowManager.createLayout(true /* show */, true /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
 
         verify(mWindowManager).inflateCompatUILayout();
-        verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+        verify(mCompatUILayout, never()).setSizeCompatHintVisibility(true /* show */);
 
         mWindowManager.onRestartButtonLongClicked();
 
         verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
     }
 
+    @Test
+    public void testOnCamerControlLongClicked_showHint() {
+       // Not create hint popup.
+        mWindowManager.mShouldShowCameraCompatHint = false;
+        mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mWindowManager).inflateCompatUILayout();
+        verify(mCompatUILayout, never()).setCameraCompatHintVisibility(true /* show */);
+
+        mWindowManager.onCameraButtonLongClicked();
+
+        verify(mCompatUILayout).setCameraCompatHintVisibility(true /* show */);
+    }
+
+    @Test
+    public void testCreateCameraCompatControl() {
+        // Not create layout if show is false.
+        mWindowManager.createLayout(false /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mWindowManager, never()).inflateCompatUILayout();
+
+        // Not create hint popup.
+        mWindowManager.mShouldShowCameraCompatHint = false;
+        mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mWindowManager).inflateCompatUILayout();
+        verify(mCompatUILayout, never()).setCameraCompatHintVisibility(true /* show */);
+        verify(mCompatUILayout).setCameraControlVisibility(true /* show */);
+
+        // Create hint popup.
+        mWindowManager.release();
+        mWindowManager.mShouldShowCameraCompatHint = true;
+        mWindowManager.createLayout(true /* show */, false /* hasSizeCompat */,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(mWindowManager, times(2)).inflateCompatUILayout();
+        assertNotNull(mCompatUILayout);
+        verify(mCompatUILayout, times(2)).setCameraControlVisibility(true /* show */);
+        assertFalse(mWindowManager.mShouldShowCameraCompatHint);
+    }
+
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index aab1e3a..dda1a82 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -31,6 +31,7 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -69,15 +70,15 @@
 
         TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
                 RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
-                MainStage mainStage, SideStage sideStage, DisplayImeController imeController,
-                DisplayInsetsController insetsController, SplitLayout splitLayout,
-                Transitions transitions, TransactionPool transactionPool,
+                MainStage mainStage, SideStage sideStage, DisplayController displayController,
+                DisplayImeController imeController, DisplayInsetsController insetsController,
+                SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool,
                 SplitscreenEventLogger logger,
                 Optional<RecentTasksController> recentTasks,
                 Provider<Optional<StageTaskUnfoldController>> unfoldController) {
             super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
-                    sideStage, imeController, insetsController, splitLayout, transitions,
-                    transactionPool, logger, recentTasks, unfoldController);
+                    sideStage, displayController, imeController, insetsController, splitLayout,
+                    transitions, transactionPool, logger, recentTasks, unfoldController);
 
             // Prepare default TaskDisplayArea for testing.
             mDisplayAreaInfo = new DisplayAreaInfo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index be1ef09..ea94cf0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -65,6 +65,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
@@ -89,6 +90,7 @@
     @Mock private ShellTaskOrganizer mTaskOrganizer;
     @Mock private SyncTransactionQueue mSyncQueue;
     @Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+    @Mock private DisplayController mDisplayController;
     @Mock private DisplayImeController mDisplayImeController;
     @Mock private DisplayInsetsController mDisplayInsetsController;
     @Mock private TransactionPool mTransactionPool;
@@ -124,8 +126,8 @@
         mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
-                mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
-                mTransactionPool, mLogger, Optional.empty(), Optional::empty);
+                mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout,
+                mTransitions, mTransactionPool, mLogger, Optional.empty(), Optional::empty);
         mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
         doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
                 .when(mTransitions).startTransition(anyInt(), any(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index fb6300c..099987a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -51,6 +51,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -91,6 +92,8 @@
     @Mock
     private SplitLayout mSplitLayout;
     @Mock
+    private DisplayController mDisplayController;
+    @Mock
     private DisplayImeController mDisplayImeController;
     @Mock
     private DisplayInsetsController mDisplayInsetsController;
@@ -293,7 +296,7 @@
     private StageCoordinator createStageCoordinator(SplitLayout splitLayout) {
         return new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
-                mDisplayImeController, mDisplayInsetsController, splitLayout,
+                mDisplayController, mDisplayImeController, mDisplayInsetsController, splitLayout,
                 mTransitions, mTransactionPool, mLogger, Optional.empty(),
                 new UnfoldControllerProvider());
     }
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index a0f6fb9..9147c12 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -2720,6 +2720,42 @@
          */
         public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
 
+        /**
+         * The flag indicating whether this TV program is scrambled or not.
+         *
+         * <p>Use the same coding for scrambled in the underlying broadcast standard
+         * if {@code free_ca_mode} in EIT is defined there (e.g. ETSI EN 300 468).
+         *
+         * <p>Type: INTEGER (boolean)
+         */
+        public static final String COLUMN_SCRAMBLED = "scrambled";
+
+        /**
+         * The comma-separated series IDs of this TV program for episodic TV shows.
+         *
+         * <p>This is used to indicate the series IDs.
+         * Programs in the same series share a series ID.
+         * Use this instead of {@link #COLUMN_SERIES_ID} if more than one series IDs
+         * are assigned to the TV program.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
+
+        /**
+         * The internal ID used by individual TV input services.
+         *
+         * <p>This is internal to the provider that inserted it, and should not be decoded by other
+         * apps.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+
         private Programs() {}
 
         /** Canonical genres for TV programs. */
@@ -3052,6 +3088,32 @@
         public static final String COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS =
                 "recording_expire_time_utc_millis";
 
+        /**
+         * The comma-separated series IDs of this TV program for episodic TV shows.
+         *
+         * <p>This is used to indicate the series IDs.
+         * Programs in the same series share a series ID.
+         * Use this instead of {@link #COLUMN_SERIES_ID} if more than one series IDs
+         * are assigned to the TV program.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
+
+        /**
+         * The internal ID used by individual TV input services.
+         *
+         * <p>This is internal to the provider that inserted it, and should not be decoded by other
+         * apps.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+
         private RecordedPrograms() {}
     }
 
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettings.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java
index 58e22c9..94fda30 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettings.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java
@@ -45,8 +45,19 @@
     public boolean isCrcEnabled() {
         return mCrcEnabled;
     }
+
     /**
-     * Returns whether the filter repeats the data with the same version.
+     * Returns whether the filter repeats the data.
+     *
+     * If {@code false}, for {@link SectionSettingsWithTableInfo}, HAL filters out all sections
+     * based on {@link SectionSettingsWithTableInfo} TableId and Version, and stops filtering data.
+     * For {@link SectionSettingsWithSectionBits}, HAL filters out the first section which matches
+     * the {@link SectionSettingsWithSectionBits} configuration, and stops filtering data.
+     *
+     * If {@code true}, for {@link SectionSettingsWithTableInfo}, HAL filters out all sections based
+     * on {@link SectionSettingsWithTableInfo} TableId and Version, and repeats. For
+     * {@link SectionSettingsWithSectionBits}, HAL filters out sections which match the
+     * {@link SectionSettingsWithSectionBits} configuration, and repeats.
      */
     public boolean isRepeat() {
         return mIsRepeat;
@@ -83,8 +94,20 @@
             mCrcEnabled = crcEnabled;
             return self();
         }
+
         /**
-         * Sets whether the filter repeats the data with the same version.
+         * Sets whether the filter repeats the data.
+         *
+         * If {@code false}, for {@link SectionSettingsWithTableInfo}, HAL filters out all sections
+         * based on {@link SectionSettingsWithTableInfo} TableId and Version, and stops filtering
+         * data. For {@link SectionSettingsWithSectionBits}, HAL filters out the first section which
+         * matches the {@link SectionSettingsWithSectionBits} configuration, and stops filtering
+         * data.
+         *
+         * If {@code true}, for {@link SectionSettingsWithTableInfo}, HAL filters out all sections
+         * based on {@link SectionSettingsWithTableInfo} TableId and Version, and repeats. For
+         * {@link SectionSettingsWithSectionBits}, HAL filters out sections which match the
+         * {@link SectionSettingsWithSectionBits} configuration, and repeats.
          */
         @NonNull
         public T setRepeat(boolean isRepeat) {
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
index 5e647fe..a84e7a9 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
@@ -23,7 +23,6 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index 8b9c14d..cb5a025 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -24,6 +24,8 @@
 import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
 import static android.net.NetworkIdentity.OEM_NONE;
+import static android.net.NetworkIdentity.OEM_PAID;
+import static android.net.NetworkIdentity.OEM_PRIVATE;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
@@ -35,6 +37,7 @@
 import static android.net.NetworkStats.ROAMING_YES;
 import static android.net.wifi.WifiInfo.sanitizeSsid;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -48,6 +51,8 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
@@ -60,18 +65,58 @@
  *
  * @hide
  */
-public class NetworkTemplate implements Parcelable {
-    private static final String TAG = "NetworkTemplate";
+// @SystemApi(client = MODULE_LIBRARIES)
+public final class NetworkTemplate implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "MATCH_" }, value = {
+            MATCH_MOBILE,
+            MATCH_WIFI,
+            MATCH_ETHERNET,
+            MATCH_BLUETOOTH,
+            MATCH_CARRIER
+    })
+    public @interface TemplateMatchRule{}
 
+    /** Match rule to match cellular networks with given Subscriber Ids. */
     public static final int MATCH_MOBILE = 1;
+    /** Match rule to match wifi networks. */
     public static final int MATCH_WIFI = 4;
+    /** Match rule to match ethernet networks. */
     public static final int MATCH_ETHERNET = 5;
+    /**
+     * Match rule to match all cellular networks.
+     *
+     * @hide
+     */
     public static final int MATCH_MOBILE_WILDCARD = 6;
+    /**
+     * Match rule to match all wifi networks.
+     *
+     * @hide
+     */
     public static final int MATCH_WIFI_WILDCARD = 7;
+    /** Match rule to match bluetooth networks. */
     public static final int MATCH_BLUETOOTH = 8;
+    /**
+     * Match rule to match networks with {@link Connectivity#TYPE_PROXY} as the legacy network type.
+     *
+     * @hide
+     */
     public static final int MATCH_PROXY = 9;
+    /**
+     * Match rule to match all networks with subscriberId inside the template. Some carriers
+     * may offer non-cellular networks like WiFi, which will be matched by this rule.
+     */
     public static final int MATCH_CARRIER = 10;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "SUBSCRIBER_ID_MATCH_RULE_" }, value = {
+            SUBSCRIBER_ID_MATCH_RULE_EXACT,
+            SUBSCRIBER_ID_MATCH_RULE_ALL
+    })
+    public @interface SubscriberIdMatchRule{}
     /**
      * Value of the match rule of the subscriberId to match networks with specific subscriberId.
      */
@@ -92,8 +137,6 @@
     /**
      * Include all network types when filtering. This is meant to merge in with the
      * {@code TelephonyManager.NETWORK_TYPE_*} constants, and thus needs to stay in sync.
-     *
-     * @hide
      */
     public static final int NETWORK_TYPE_ALL = -1;
     /**
@@ -106,21 +149,37 @@
      */
     public static final int NETWORK_TYPE_5G_NSA = -2;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "OEM_MANAGED_" }, value = {
+            OEM_MANAGED_ALL,
+            OEM_MANAGED_NO,
+            OEM_MANAGED_YES,
+            OEM_MANAGED_PAID,
+            OEM_MANAGED_PRIVATE
+    })
+    public @interface OemManaged{}
+
     /**
      * Value to match both OEM managed and unmanaged networks (all networks).
-     * @hide
      */
     public static final int OEM_MANAGED_ALL = -1;
     /**
      * Value to match networks which are not OEM managed.
-     * @hide
      */
     public static final int OEM_MANAGED_NO = OEM_NONE;
     /**
      * Value to match any OEM managed network.
-     * @hide
      */
     public static final int OEM_MANAGED_YES = -2;
+    /**
+     * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}.
+     */
+    public static final int OEM_MANAGED_PAID = OEM_PAID;
+    /**
+     * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}.
+     */
+    public static final int OEM_MANAGED_PRIVATE = OEM_PRIVATE;
 
     private static boolean isKnownMatchRule(final int rule) {
         switch (rule) {
@@ -142,6 +201,8 @@
     /**
      * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
      * the given IMSI.
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
@@ -152,6 +213,8 @@
      * Template to match cellular networks with the given IMSI, {@code ratType} and
      * {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when
      * filtering. See {@code TelephonyManager.NETWORK_TYPE_*}.
+     *
+     * @hide
      */
     public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
             @NetworkType int ratType, int metered) {
@@ -168,6 +231,8 @@
     /**
      * Template to match metered {@link ConnectivityManager#TYPE_MOBILE} networks,
      * regardless of IMSI.
+     *
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static NetworkTemplate buildTemplateMobileWildcard() {
@@ -177,6 +242,8 @@
     /**
      * Template to match all metered {@link ConnectivityManager#TYPE_WIFI} networks,
      * regardless of SSID.
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateWifiWildcard() {
@@ -185,6 +252,7 @@
         return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
     }
 
+    /** @hide */
     @Deprecated
     @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateWifi() {
@@ -194,6 +262,8 @@
     /**
      * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
      * given SSID.
+     *
+     * @hide
      */
     public static NetworkTemplate buildTemplateWifi(@NonNull String networkId) {
         Objects.requireNonNull(networkId);
@@ -221,6 +291,8 @@
     /**
      * Template to combine all {@link ConnectivityManager#TYPE_ETHERNET} style
      * networks together.
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public static NetworkTemplate buildTemplateEthernet() {
@@ -230,6 +302,8 @@
     /**
      * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
      * networks together.
+     *
+     * @hide
      */
     public static NetworkTemplate buildTemplateBluetooth() {
         return new NetworkTemplate(MATCH_BLUETOOTH, null, null);
@@ -238,6 +312,8 @@
     /**
      * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
      * networks together.
+     *
+     * @hide
      */
     public static NetworkTemplate buildTemplateProxy() {
         return new NetworkTemplate(MATCH_PROXY, null, null);
@@ -245,6 +321,8 @@
 
     /**
      * Template to match all metered carrier networks with the given IMSI.
+     *
+     * @hide
      */
     public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
         Objects.requireNonNull(subscriberId);
@@ -298,12 +376,14 @@
         }
     }
 
+    /** @hide */
     // TODO: Deprecate this constructor, mark it @UnsupportedAppUsage(maxTargetSdk = S)
     @UnsupportedAppUsage
     public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
         this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
     }
 
+    /** @hide */
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String networkId) {
         // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
@@ -316,6 +396,7 @@
                 OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
+    /** @hide */
     // TODO: Remove it after updating all of the caller.
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String networkId, int metered, int roaming, int defaultNetwork, int subType,
@@ -324,6 +405,7 @@
                 defaultNetwork, subType, oemManaged, SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
+    /** @hide */
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String networkId, int metered, int roaming, int defaultNetwork, int subType,
             int oemManaged, int subscriberIdMatchRule) {
@@ -360,7 +442,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mMatchRule);
         dest.writeString(mSubscriberId);
         dest.writeStringArray(mMatchSubscriberIds);
@@ -448,6 +530,7 @@
         }
     }
 
+    /** @hide */
     public boolean isMatchRuleMobile() {
         switch (mMatchRule) {
             case MATCH_MOBILE:
@@ -458,6 +541,12 @@
         }
     }
 
+    /**
+     * Check if the template can be persisted into disk.
+     *
+     * @hide
+     */
+    // TODO: Move to the NetworkPolicy.
     public boolean isPersistable() {
         switch (mMatchRule) {
             case MATCH_MOBILE_WILDCARD:
@@ -476,11 +565,18 @@
         }
     }
 
+    /**
+     * Get match rule of the template. See {@code MATCH_*}.
+     */
     @UnsupportedAppUsage
     public int getMatchRule() {
         return mMatchRule;
     }
 
+    /**
+     * Get subscriber Id of the template.
+     */
+    @Nullable
     @UnsupportedAppUsage
     public String getSubscriberId() {
         return mSubscriberId;
@@ -490,16 +586,25 @@
         return mNetworkId;
     }
 
+    /**
+     * Get Subscriber Id Match Rule of the template.
+     */
     public int getSubscriberIdMatchRule() {
         return mSubscriberIdMatchRule;
     }
 
+    /**
+     * Get meteredness filter of the template.
+     */
+    @NetworkStats.Meteredness
     public int getMeteredness() {
         return mMetered;
     }
 
     /**
      * Test if given {@link NetworkIdentity} matches this template.
+     *
+     * @hide
      */
     public boolean matches(NetworkIdentity ident) {
         if (!matchesMetered(ident)) return false;
@@ -565,6 +670,8 @@
      * Check if this template matches {@code subscriberId}. Returns true if this
      * template was created with {@code SUBSCRIBER_ID_MATCH_RULE_ALL}, or with a
      * {@code mMatchSubscriberIds} array that contains {@code subscriberId}.
+     *
+     * @hide
      */
     public boolean matchesSubscriberId(@Nullable String subscriberId) {
         return mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL
@@ -599,10 +706,13 @@
      * The mapping is corresponding to {@code TelephonyManager#NETWORK_CLASS_BIT_MASK_*}.
      *
      * @param ratType An integer defined in {@code TelephonyManager#NETWORK_TYPE_*}.
+     *
+     * @hide
      */
     // TODO: 1. Consider move this to TelephonyManager if used by other modules.
     //       2. Consider make this configurable.
     //       3. Use TelephonyManager APIs when available.
+    // TODO: @SystemApi when ready.
     public static int getCollapsedRatType(int ratType) {
         switch (ratType) {
             case TelephonyManager.NETWORK_TYPE_GPRS:
@@ -639,7 +749,10 @@
     /**
      * Return all supported collapsed RAT types that could be returned by
      * {@link #getCollapsedRatType(int)}.
+     *
+     * @hide
      */
+    // TODO: @SystemApi when ready.
     @NonNull
     public static final int[] getAllCollapsedRatTypes() {
         final int[] ratTypes = TelephonyManager.getAllNetworkTypes();
@@ -779,6 +892,8 @@
      * active merge set [A,B], we'd return a new template that primarily matches
      * A, but also matches B.
      * TODO: remove and use {@link #normalize(NetworkTemplate, List)}.
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
@@ -797,7 +912,10 @@
      * For example, given an incoming template matching B, and the currently
      * active merge set [A,B], we'd return a new template that primarily matches
      * A, but also matches B.
+     *
+     * @hide
      */
+    // TODO: @SystemApi when ready.
     public static NetworkTemplate normalize(NetworkTemplate template, List<String[]> mergedList) {
         // Now there are several types of network which uses SubscriberId to store network
         // information. For instances:
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b25e9a1..a3f07d8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -207,6 +207,7 @@
     <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
     <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
     <uses-permission android:name="android.permission.QUERY_ADMIN_POLICY" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES" />
     <uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" />
     <uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" />
     <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index a16f5cd..da9a92a 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -20,9 +20,11 @@
 import android.app.smartspace.SmartspaceAction;
 import android.app.smartspace.SmartspaceTarget;
 import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -39,6 +41,7 @@
 public interface BcSmartspaceDataPlugin extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
     int VERSION = 1;
+    String TAG = "BcSmartspaceDataPlugin";
 
     /** Register a listener to get Smartspace data. */
     void registerListener(SmartspaceTargetListener listener);
@@ -124,10 +127,14 @@
     /** Interface for launching Intents, which can differ on the lockscreen */
     interface IntentStarter {
         default void startFromAction(SmartspaceAction action, View v, boolean showOnLockscreen) {
-            if (action.getIntent() != null) {
-                startIntent(v, action.getIntent(), showOnLockscreen);
-            } else if (action.getPendingIntent() != null) {
-                startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+            try {
+                if (action.getIntent() != null) {
+                    startIntent(v, action.getIntent(), showOnLockscreen);
+                } else if (action.getPendingIntent() != null) {
+                    startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+                }
+            } catch (ActivityNotFoundException e) {
+                Log.w(TAG, "Could not launch intent for action: " + action, e);
             }
         }
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d4398a8..843c69f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -30,10 +30,10 @@
     <dimen name="navigation_bar_deadzone_size_max">32dp</dimen>
 
     <!-- dimensions for the navigation bar handle -->
-    <dimen name="navigation_handle_radius">2dp</dimen>
-    <dimen name="navigation_handle_bottom">10dp</dimen>
+    <dimen name="navigation_handle_radius">1dp</dimen>
+    <dimen name="navigation_handle_bottom">6dp</dimen>
     <dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen>
-    <dimen name="navigation_home_handle_width">108dp</dimen>
+    <dimen name="navigation_home_handle_width">72dp</dimen>
 
     <!-- Size of the nav bar edge panels, should be greater to the
          edge sensitivity + the drag threshold -->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e35b558..1496f17 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -636,7 +636,9 @@
         mIndicatorView.setText(message);
         mIndicatorView.setTextColor(mTextColorError);
         mIndicatorView.setVisibility(View.VISIBLE);
-        mIndicatorView.setSelected(true);
+        // select to enable marquee unless a screen reader is enabled
+        mIndicatorView.setSelected(!mAccessibilityManager.isEnabled()
+                || !mAccessibilityManager.isTouchExplorationEnabled());
         mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError());
 
         Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
index 07aec69..73e3aec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
@@ -75,7 +75,7 @@
     @Override
     protected void onViewDetached() {
         mPanelExpansionStateManager.removeExpansionListener(mPanelExpansionListener);
-        mDialogManager.registerListener(mDialogListener);
+        mDialogManager.unregisterListener(mDialogListener);
         mDumpManger.unregisterDumpable(getDumpTag());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java
index 13b1dc0..f965431 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java
@@ -142,7 +142,7 @@
 
     private void connect() {
         if (DEBUG) {
-            Log.d(TAG, "attempting to communal to communal source");
+            Log.d(TAG, "attempting to connect to communal source");
         }
 
         if (mCurrentConnection != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index fb601e3..c8cd432 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -21,6 +21,7 @@
 import android.animation.ValueAnimator
 import android.annotation.IntDef
 import android.content.Context
+import android.content.res.Configuration
 import android.graphics.Rect
 import android.util.MathUtils
 import android.view.View
@@ -41,6 +42,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.Utils
 import com.android.systemui.util.animation.UniqueObjectHostView
 import javax.inject.Inject
 
@@ -186,6 +188,8 @@
     @MediaLocation
     private var currentAttachmentLocation = -1
 
+    private var inSplitShade = false
+
     /**
      * Is there any active media in the carousel?
      */
@@ -390,8 +394,9 @@
     init {
         updateConfiguration()
         configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
-            override fun onDensityOrFontScaleChanged() {
+            override fun onConfigChanged(newConfig: Configuration?) {
                 updateConfiguration()
+                updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = true)
             }
         })
         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
@@ -467,6 +472,7 @@
     private fun updateConfiguration() {
         distanceForFullShadeTransition = context.resources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_media_transition_distance)
+        inSplitShade = Utils.shouldUseSplitNotificationShade(context.resources)
     }
 
     /**
@@ -803,7 +809,7 @@
     private fun getQSTransformationProgress(): Float {
         val currentHost = getHost(desiredLocation)
         val previousHost = getHost(previousLocation)
-        if (hasActiveMedia && currentHost?.location == LOCATION_QS) {
+        if (hasActiveMedia && (currentHost?.location == LOCATION_QS && !inSplitShade)) {
             if (previousHost?.location == LOCATION_QQS) {
                 if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) {
                     return qsExpansion
@@ -934,7 +940,7 @@
                 statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
         val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
         val location = when {
-            qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
+            (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
             qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
             !hasActiveMedia -> LOCATION_QS
             onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 042a337..1322ade 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media
 
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -26,6 +27,8 @@
 import com.android.systemui.R
 import com.android.systemui.util.animation.TransitionLayout
 
+private const val TAG = "PlayerViewHolder"
+
 /**
  * ViewHolder for a media player.
  */
@@ -94,7 +97,12 @@
     }
 
     fun marquee(start: Boolean, delay: Long) {
-        longPressText.getHandler().postDelayed({ longPressText.setSelected(start) }, delay)
+        val longPressTextHandler = longPressText.getHandler()
+        if (longPressTextHandler == null) {
+            Log.d(TAG, "marquee while longPressText.getHandler() is null", Exception())
+            return
+        }
+        longPressTextHandler.postDelayed({ longPressText.setSelected(start) }, delay)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6d78b9f..ce571e5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -39,11 +39,13 @@
 import android.app.ActivityOptions;
 import android.app.ExitTransitionCoordinator;
 import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
+import android.app.ICompatCameraControlCallback;
 import android.app.Notification;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
@@ -72,6 +74,7 @@
 import android.view.ScrollCaptureResponse;
 import android.view.SurfaceControl;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowInsets;
@@ -595,20 +598,35 @@
         withWindowAttached(() -> {
             requestScrollCapture();
             mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
-                    (overrideConfig, newDisplayId) -> {
-                        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
-                            // Hide the scroll chip until we know it's available in this orientation
-                            mScreenshotView.hideScrollChip();
-                            // Delay scroll capture eval a bit to allow the underlying activity
-                            // to set up in the new orientation.
-                            mScreenshotHandler.postDelayed(this::requestScrollCapture, 150);
-                            mScreenshotView.updateInsets(
-                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets());
-                            // screenshot animation calculations won't be valid anymore, so just end
-                            if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
-                                mScreenshotAnimation.end();
+                    new ViewRootImpl.ActivityConfigCallback() {
+                        @Override
+                        public void onConfigurationChanged(Configuration overrideConfig,
+                                int newDisplayId) {
+                            if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+                                // Hide the scroll chip until we know it's available in this
+                                // orientation
+                                mScreenshotView.hideScrollChip();
+                                // Delay scroll capture eval a bit to allow the underlying activity
+                                // to set up in the new orientation.
+                                mScreenshotHandler.postDelayed(
+                                        ScreenshotController.this::requestScrollCapture, 150);
+                                mScreenshotView.updateInsets(
+                                        mWindowManager.getCurrentWindowMetrics()
+                                                .getWindowInsets());
+                                // Screenshot animation calculations won't be valid anymore,
+                                // so just end
+                                if (mScreenshotAnimation != null
+                                        && mScreenshotAnimation.isRunning()) {
+                                    mScreenshotAnimation.end();
+                                }
                             }
                         }
+                        @Override
+                        public void requestCompatCameraControl(boolean showControl,
+                                boolean transformationApplied,
+                                ICompatCameraControlCallback callback) {
+                            Log.w(TAG, "Unexpected requestCompatCameraControl callback");
+                        }
                     });
         });
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index a44de2c..a4e2d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -241,6 +241,10 @@
         configurationController.addCallback(configChangeListener)
         statusBarStateController.addCallback(statusBarStateListener)
 
+        plugin.registerSmartspaceEventNotifier {
+                e -> session?.notifySmartspaceEvent(e)
+        }
+
         reloadSmartspace()
     }
 
@@ -266,6 +270,7 @@
         statusBarStateController.removeCallback(statusBarStateListener)
         session = null
 
+        plugin?.registerSmartspaceEventNotifier(null)
         plugin?.onTargetsAvailable(emptyList())
         Log.d(TAG, "Ending smartspace session for lockscreen")
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index bfe352d..aa511c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -146,6 +146,7 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         logOptionSelection(MetricsEvent.NOTIFICATION_SNOOZE_CLICKED, mDefaultOption);
+        dispatchConfigurationChanged(getResources().getConfiguration());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 518788b..79b05c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -412,7 +412,7 @@
     private NotificationShelf mShelf;
     private int mMaxDisplayedNotifications = -1;
     private float mKeyguardBottomPadding = -1;
-    private int mStatusBarHeight;
+    @VisibleForTesting int mStatusBarHeight;
     private int mMinInteractionHeight;
     private final Rect mClipRect = new Rect();
     private boolean mIsClipped;
@@ -4854,8 +4854,12 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getMinExpansionHeight() {
+        // shelf height is defined in dp but status bar height can be defined in px, that makes
+        // relation between them variable - sometimes one might be bigger than the other when
+        // changing density. That’s why we need to ensure we’re not subtracting negative value below
         return mShelf.getIntrinsicHeight()
-                - (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2
+                - Math.max(0,
+                (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2)
                 + mWaterfallTopInset;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index bf87a4a..a3ffb2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -22,6 +22,7 @@
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -82,6 +83,8 @@
     private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock
     private lateinit var configurationController: ConfigurationController
+    @Mock
+    private lateinit var uniqueObjectHostView: UniqueObjectHostView
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -94,6 +97,8 @@
 
     @Before
     fun setup() {
+        context.getOrCreateTestableResources().addOverride(
+                R.bool.config_use_split_notification_shade, false)
         mediaFrame = FrameLayout(context)
         `when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
         mediaHiearchyManager = MediaHierarchyManager(
@@ -124,7 +129,7 @@
     private fun setupHost(host: MediaHost, location: Int) {
         `when`(host.location).thenReturn(location)
         `when`(host.currentBounds).thenReturn(Rect())
-        `when`(host.hostView).thenReturn(UniqueObjectHostView(context))
+        `when`(host.hostView).thenReturn(uniqueObjectHostView)
         `when`(host.visible).thenReturn(true)
         mediaHiearchyManager.register(host)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index de627de..1961ab2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -214,6 +214,8 @@
 
         // THEN the session is created
         verify(smartspaceManager).createSmartspaceSession(any())
+        // THEN an event notifier is registered
+        verify(plugin).registerSmartspaceEventNotifier(any())
     }
 
     @Test
@@ -241,7 +243,7 @@
     }
 
     @Test
-    fun testEmptyListIsEmittedAfterDisconnect() {
+    fun testEmptyListIsEmittedAndNotifierRemovedAfterDisconnect() {
         // GIVEN a registered listener on an active session
         connectSession()
         clearInvocations(plugin)
@@ -250,8 +252,9 @@
         controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
         controller.disconnect()
 
-        // THEN the listener receives an empty list of targets
+        // THEN the listener receives an empty list of targets and unregisters the notifier
         verify(plugin).onTargetsAvailable(emptyList())
+        verify(plugin).registerSmartspaceEventNotifier(null)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 9be2837..eda0e83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static junit.framework.Assert.assertEquals;
@@ -102,6 +103,7 @@
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
     @Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
     @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    @Mock private NotificationShelf mNotificationShelf;
 
     @Before
     @UiThreadTest
@@ -123,13 +125,13 @@
         mDependency.injectTestDependency(GroupMembershipManager.class, mGroupMembershipManger);
         mDependency.injectTestDependency(GroupExpansionManager.class, mGroupExpansionManager);
         mDependency.injectTestDependency(AmbientState.class, mAmbientState);
+        mDependency.injectTestDependency(NotificationShelf.class, mNotificationShelf);
         mDependency.injectTestDependency(
                 UnlockedScreenOffAnimationController.class, mUnlockedScreenOffAnimationController);
 
         NotificationShelfController notificationShelfController =
                 mock(NotificationShelfController.class);
-        NotificationShelf notificationShelf = mock(NotificationShelf.class);
-        when(notificationShelfController.getView()).thenReturn(notificationShelf);
+        when(notificationShelfController.getView()).thenReturn(mNotificationShelf);
         when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
                 new NotificationSection[]{
                         mNotificationSection
@@ -159,7 +161,7 @@
                         anyBoolean());
         doNothing().when(mGroupExpansionManager).collapseGroups();
         doNothing().when(mExpandHelper).cancelImmediately();
-        doNothing().when(notificationShelf).setAnimationsEnabled(anyBoolean());
+        doNothing().when(mNotificationShelf).setAnimationsEnabled(anyBoolean());
     }
 
     @Test
@@ -202,21 +204,43 @@
 
     @Test
     @UiThreadTest
-    public void testSetExpandedHeight_blockingHelperManagerReceivedCallbacks() {
-        final float[] expectedHeight = {0f};
-        final float[] expectedAppear = {0f};
+    public void testSetExpandedHeight_listenerReceivedCallbacks() {
+        final float expectedHeight = 0f;
 
         mStackScroller.addOnExpandedHeightChangedListener((height, appear) -> {
-            Assert.assertEquals(expectedHeight[0], height, 0);
-            Assert.assertEquals(expectedAppear[0], appear, .1);
+            Assert.assertEquals(expectedHeight, height, 0);
         });
-        expectedHeight[0] = 1f;
-        expectedAppear[0] = 1f;
-        mStackScroller.setExpandedHeight(expectedHeight[0]);
+        mStackScroller.setExpandedHeight(expectedHeight);
+    }
 
-        expectedHeight[0] = 100f;
-        expectedAppear[0] = 0f;
-        mStackScroller.setExpandedHeight(expectedHeight[0]);
+    @Test
+    public void testAppearFractionCalculation() {
+        // appear start position
+        when(mNotificationShelf.getIntrinsicHeight()).thenReturn(100);
+        // because it's the same as shelf height, appear start position equals shelf height
+        mStackScroller.mStatusBarHeight = 100;
+        // appear end position
+        when(mEmptyShadeView.getHeight()).thenReturn(200);
+
+        assertEquals(0f, mStackScroller.calculateAppearFraction(100));
+        assertEquals(1f, mStackScroller.calculateAppearFraction(200));
+        assertEquals(0.5f, mStackScroller.calculateAppearFraction(150));
+    }
+
+    @Test
+    public void testAppearFractionCalculationIsNotNegativeWhenShelfBecomesSmaller() {
+        // this situation might occur if status bar height is defined in pixels while shelf height
+        // in dp and screen density changes - appear start position
+        // (calculated in NSSL#getMinExpansionHeight) that is adjusting for status bar might
+        // increase and become bigger that end position, which should be prevented
+
+        // appear start position
+        when(mNotificationShelf.getIntrinsicHeight()).thenReturn(80);
+        mStackScroller.mStatusBarHeight = 100;
+        // appear end position
+        when(mEmptyShadeView.getHeight()).thenReturn(90);
+
+        assertThat(mStackScroller.calculateAppearFraction(100)).isAtLeast(0);
     }
 
     @Test
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
index a03dcbd..1f5834d 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
@@ -18,5 +18,5 @@
 -->
 <resources>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">24dp</dimen>
+    <dimen name="navigation_bar_gesture_height">32dp</dimen>
 </resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
index c5d0c9e..ac1f022 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
@@ -18,13 +18,13 @@
 -->
 <resources>
     <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">24dp</dimen>
+    <dimen name="navigation_bar_height">16dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">24dp</dimen>
+    <dimen name="navigation_bar_height_landscape">16dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">24dp</dimen>
+    <dimen name="navigation_bar_width">16dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_frame_height">48dp</dimen>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">24dp</dimen>
+    <dimen name="navigation_bar_gesture_height">32dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
index c5d0c9e..ac1f022 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
 -->
 <resources>
     <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">24dp</dimen>
+    <dimen name="navigation_bar_height">16dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">24dp</dimen>
+    <dimen name="navigation_bar_height_landscape">16dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">24dp</dimen>
+    <dimen name="navigation_bar_width">16dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_frame_height">48dp</dimen>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">24dp</dimen>
+    <dimen name="navigation_bar_gesture_height">32dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
index c5d0c9e..ac1f022 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
 -->
 <resources>
     <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">24dp</dimen>
+    <dimen name="navigation_bar_height">16dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">24dp</dimen>
+    <dimen name="navigation_bar_height_landscape">16dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">24dp</dimen>
+    <dimen name="navigation_bar_width">16dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_frame_height">48dp</dimen>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">24dp</dimen>
+    <dimen name="navigation_bar_gesture_height">32dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
index c5d0c9e..ac1f022 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
 -->
 <resources>
     <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">24dp</dimen>
+    <dimen name="navigation_bar_height">16dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">24dp</dimen>
+    <dimen name="navigation_bar_height_landscape">16dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">24dp</dimen>
+    <dimen name="navigation_bar_width">16dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_frame_height">48dp</dimen>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">24dp</dimen>
+    <dimen name="navigation_bar_gesture_height">32dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index 85ab48c..8963337 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -17,6 +17,7 @@
 package com.android.server.backup.transport;
 
 import android.annotation.Nullable;
+import android.app.backup.BackupTransport;
 import android.app.backup.RestoreDescription;
 import android.app.backup.RestoreSet;
 import android.content.Intent;
@@ -24,50 +25,70 @@
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.backup.IBackupTransport;
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Client to {@link com.android.internal.backup.IBackupTransport}. Manages the call to the remote
  * transport service and delivers the results.
  */
 public class BackupTransportClient {
+    private static final String TAG = "BackupTransportClient";
+
     private final IBackupTransport mTransportBinder;
+    private final TransportStatusCallbackPool mCallbackPool;
 
     BackupTransportClient(IBackupTransport transportBinder) {
         mTransportBinder = transportBinder;
-
-        // This is a temporary fix to allow blocking calls.
-        // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking.
-        Binder.allowBlocking(mTransportBinder.asBinder());
+        mCallbackPool = new TransportStatusCallbackPool();
     }
 
     /**
      * See {@link IBackupTransport#name()}.
      */
     public String name() throws RemoteException {
-        return mTransportBinder.name();
+        AndroidFuture<String> resultFuture = new AndroidFuture<>();
+        mTransportBinder.name(resultFuture);
+        return getFutureResult(resultFuture);
     }
 
     /**
      * See {@link IBackupTransport#configurationIntent()}
      */
     public Intent configurationIntent() throws RemoteException {
-        return mTransportBinder.configurationIntent();
+        AndroidFuture<Intent> resultFuture = new AndroidFuture<>();
+        mTransportBinder.configurationIntent(resultFuture);
+        return getFutureResult(resultFuture);
     }
 
     /**
      * See {@link IBackupTransport#currentDestinationString()}
      */
     public String currentDestinationString() throws RemoteException {
-        return mTransportBinder.currentDestinationString();
+        AndroidFuture<String> resultFuture = new AndroidFuture<>();
+        mTransportBinder.currentDestinationString(resultFuture);
+        return getFutureResult(resultFuture);
     }
 
     /**
      * See {@link IBackupTransport#dataManagementIntent()}
      */
     public Intent dataManagementIntent() throws RemoteException {
-        return mTransportBinder.dataManagementIntent();
+        AndroidFuture<Intent> resultFuture = new AndroidFuture<>();
+        mTransportBinder.dataManagementIntent(resultFuture);
+        return getFutureResult(resultFuture);
     }
 
     /**
@@ -75,42 +96,67 @@
      */
     @Nullable
     public CharSequence dataManagementIntentLabel() throws RemoteException {
-        return mTransportBinder.dataManagementIntentLabel();
+        AndroidFuture<CharSequence> resultFuture = new AndroidFuture<>();
+        mTransportBinder.dataManagementIntentLabel(resultFuture);
+        return getFutureResult(resultFuture);
     }
 
     /**
      * See {@link IBackupTransport#transportDirName()}
      */
     public String transportDirName() throws RemoteException {
-        return mTransportBinder.transportDirName();
+        AndroidFuture<String> resultFuture = new AndroidFuture<>();
+        mTransportBinder.transportDirName(resultFuture);
+        return getFutureResult(resultFuture);
     }
 
     /**
      * See {@link IBackupTransport#initializeDevice()}
      */
     public int initializeDevice() throws RemoteException {
-        return mTransportBinder.initializeDevice();
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.initializeDevice(callback);
+            return callback.getOperationStatus();
+        } finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#clearBackupData(PackageInfo)}
      */
     public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
-        return mTransportBinder.clearBackupData(packageInfo);
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.clearBackupData(packageInfo, callback);
+            return callback.getOperationStatus();
+        } finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#finishBackup()}
      */
     public int finishBackup() throws RemoteException {
-        return mTransportBinder.finishBackup();
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.finishBackup(callback);
+            return callback.getOperationStatus();
+        }  finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#requestBackupTime()}
      */
     public long requestBackupTime() throws RemoteException {
-        return mTransportBinder.requestBackupTime();
+        AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+        mTransportBinder.requestBackupTime(resultFuture);
+        Long result = getFutureResult(resultFuture);
+        return result == null ? BackupTransport.TRANSPORT_ERROR : result;
     }
 
     /**
@@ -118,56 +164,91 @@
      */
     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
             throws RemoteException {
-        return mTransportBinder.performBackup(packageInfo, inFd, flags);
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.performBackup(packageInfo, inFd, flags, callback);
+            return callback.getOperationStatus();
+        }  finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#getAvailableRestoreSets()}
      */
     public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
-        return mTransportBinder.getAvailableRestoreSets();
+        AndroidFuture<List<RestoreSet>> resultFuture = new AndroidFuture<>();
+        mTransportBinder.getAvailableRestoreSets(resultFuture);
+        List<RestoreSet> result = getFutureResult(resultFuture);
+        return result == null ? null : result.toArray(new RestoreSet[] {});
     }
 
     /**
      * See {@link IBackupTransport#getCurrentRestoreSet()}
      */
     public long getCurrentRestoreSet() throws RemoteException {
-        return mTransportBinder.getCurrentRestoreSet();
+        AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+        mTransportBinder.getCurrentRestoreSet(resultFuture);
+        Long result = getFutureResult(resultFuture);
+        return result == null ? BackupTransport.TRANSPORT_ERROR : result;
     }
 
     /**
      * See {@link IBackupTransport#startRestore(long, PackageInfo[])}
      */
     public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
-        return mTransportBinder.startRestore(token, packages);
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.startRestore(token, packages, callback);
+            return callback.getOperationStatus();
+        }  finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#nextRestorePackage()}
      */
     public RestoreDescription nextRestorePackage() throws RemoteException {
-        return mTransportBinder.nextRestorePackage();
+        AndroidFuture<RestoreDescription> resultFuture = new AndroidFuture<>();
+        mTransportBinder.nextRestorePackage(resultFuture);
+        return getFutureResult(resultFuture);
     }
 
     /**
      * See {@link IBackupTransport#getRestoreData(ParcelFileDescriptor)}
      */
     public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
-        return mTransportBinder.getRestoreData(outFd);
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.getRestoreData(outFd, callback);
+            return callback.getOperationStatus();
+        }  finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#finishRestore()}
      */
     public void finishRestore() throws RemoteException {
-        mTransportBinder.finishRestore();
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.finishRestore(callback);
+            callback.getOperationStatus();
+        }  finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#requestFullBackupTime()}
      */
     public long requestFullBackupTime() throws RemoteException {
-        return mTransportBinder.requestFullBackupTime();
+        AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+        mTransportBinder.requestFullBackupTime(resultFuture);
+        Long result = getFutureResult(resultFuture);
+        return result == null ? BackupTransport.TRANSPORT_ERROR : result;
     }
 
     /**
@@ -175,28 +256,52 @@
      */
     public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket,
             int flags) throws RemoteException {
-        return mTransportBinder.performFullBackup(targetPackage, socket, flags);
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.performFullBackup(targetPackage, socket, flags, callback);
+            return callback.getOperationStatus();
+        }  finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#checkFullBackupSize(long)}
      */
     public int checkFullBackupSize(long size) throws RemoteException {
-        return mTransportBinder.checkFullBackupSize(size);
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.checkFullBackupSize(size, callback);
+            return callback.getOperationStatus();
+        }  finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#sendBackupData(int)}
      */
     public int sendBackupData(int numBytes) throws RemoteException {
-        return mTransportBinder.sendBackupData(numBytes);
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        mTransportBinder.sendBackupData(numBytes, callback);
+        try {
+            return callback.getOperationStatus();
+        } finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#cancelFullBackup()}
      */
     public void cancelFullBackup() throws RemoteException {
-        mTransportBinder.cancelFullBackup();
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.cancelFullBackup(callback);
+            callback.getOperationStatus();
+        } finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
@@ -204,34 +309,93 @@
      */
     public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup)
             throws RemoteException {
-        return mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup);
+        AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
+        mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup, resultFuture);
+        Boolean result = getFutureResult(resultFuture);
+        return result != null && result;
     }
 
     /**
      * See {@link IBackupTransport#getBackupQuota(String, boolean)}
      */
     public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException {
-        return mTransportBinder.getBackupQuota(packageName, isFullBackup);
+        AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+        mTransportBinder.getBackupQuota(packageName, isFullBackup, resultFuture);
+        Long result = getFutureResult(resultFuture);
+        return result == null ? BackupTransport.TRANSPORT_ERROR : result;
     }
 
     /**
      * See {@link IBackupTransport#getNextFullRestoreDataChunk(ParcelFileDescriptor)}
      */
     public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException {
-        return mTransportBinder.getNextFullRestoreDataChunk(socket);
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.getNextFullRestoreDataChunk(socket, callback);
+            return callback.getOperationStatus();
+        } finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#abortFullRestore()}
      */
     public int abortFullRestore() throws RemoteException {
-        return mTransportBinder.abortFullRestore();
+        TransportStatusCallback callback = mCallbackPool.acquire();
+        try {
+            mTransportBinder.abortFullRestore(callback);
+            return callback.getOperationStatus();
+        } finally {
+            mCallbackPool.recycle(callback);
+        }
     }
 
     /**
      * See {@link IBackupTransport#getTransportFlags()}
      */
     public int getTransportFlags() throws RemoteException {
-        return mTransportBinder.getTransportFlags();
+        AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+        mTransportBinder.getTransportFlags(resultFuture);
+        Integer result = getFutureResult(resultFuture);
+        return result == null ? BackupTransport.TRANSPORT_ERROR : result;
+    }
+
+    private <T> T getFutureResult(AndroidFuture<T> future) {
+        try {
+            return future.get(600, TimeUnit.SECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            Slog.w(TAG, "Failed to get result from transport:", e);
+            return null;
+        }
+    }
+
+    private static class TransportStatusCallbackPool {
+        private static final int MAX_POOL_SIZE = 100;
+
+        private final Object mPoolLock = new Object();
+        private final Queue<TransportStatusCallback> mCallbackPool = new ArrayDeque<>();
+
+        TransportStatusCallback acquire() {
+            synchronized (mPoolLock) {
+                if (mCallbackPool.isEmpty()) {
+                    return new TransportStatusCallback();
+                } else {
+                    return mCallbackPool.poll();
+                }
+            }
+        }
+
+        void recycle(TransportStatusCallback callback) {
+            synchronized (mPoolLock) {
+                if (mCallbackPool.size() > MAX_POOL_SIZE) {
+                    Slog.d(TAG, "TransportStatusCallback pool size exceeded");
+                    return;
+                }
+
+                callback.reset();
+                mCallbackPool.add(callback);
+            }
+        }
     }
 }
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java
new file mode 100644
index 0000000..a55178c
--- /dev/null
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.transport;
+
+import android.app.backup.BackupTransport;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.ITransportStatusCallback;
+
+public class TransportStatusCallback extends ITransportStatusCallback.Stub {
+    private static final String TAG = "TransportStatusCallback";
+    private static final int TIMEOUT_MILLIS = 600 * 1000; // 10 minutes.
+    private static final int OPERATION_STATUS_DEFAULT = 0;
+
+    private final int mOperationTimeout;
+
+    @GuardedBy("this")
+    private int mOperationStatus = OPERATION_STATUS_DEFAULT;
+    @GuardedBy("this")
+    private boolean mHasCompletedOperation = false;
+
+    public TransportStatusCallback() {
+        mOperationTimeout = TIMEOUT_MILLIS;
+    }
+
+    @VisibleForTesting
+    TransportStatusCallback(int operationTimeout) {
+        mOperationTimeout = operationTimeout;
+    }
+
+    @Override
+    public synchronized void onOperationCompleteWithStatus(int status) throws RemoteException {
+        mHasCompletedOperation = true;
+        mOperationStatus = status;
+
+        notifyAll();
+    }
+
+    @Override
+    public synchronized void onOperationComplete() throws RemoteException {
+        onOperationCompleteWithStatus(OPERATION_STATUS_DEFAULT);
+    }
+
+    synchronized int getOperationStatus() {
+        if (mHasCompletedOperation) {
+            return mOperationStatus;
+        }
+
+        long timeoutLeft = mOperationTimeout;
+        try {
+            while (!mHasCompletedOperation && timeoutLeft > 0) {
+                long waitStartTime = System.currentTimeMillis();
+                wait(timeoutLeft);
+                if (mHasCompletedOperation) {
+                    return mOperationStatus;
+                }
+                timeoutLeft -= System.currentTimeMillis() - waitStartTime;
+            }
+
+            Slog.w(TAG, "Couldn't get operation status from transport");
+            return BackupTransport.TRANSPORT_ERROR;
+        } catch (InterruptedException e) {
+            Slog.w(TAG, "Couldn't get operation status from transport: ", e);
+            return BackupTransport.TRANSPORT_ERROR;
+        } finally {
+            reset();
+        }
+    }
+
+    synchronized void reset() {
+        mHasCompletedOperation = false;
+        mOperationStatus = OPERATION_STATUS_DEFAULT;
+    }
+}
diff --git a/services/cloudsearch/OWNERS b/services/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/services/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 3a8ee73..b981ff1 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -84,18 +84,6 @@
             throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
         }
 
-        if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
-            // TODO: remove, when properly supporting this profile.
-            throw new UnsupportedOperationException(
-                    "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
-        }
-
-        if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
-            // TODO: remove, when properly supporting this profile.
-            throw new UnsupportedOperationException(
-                    "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet.");
-        }
-
         final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
         if (context.checkPermission(permission, getCallingPid(), packageUid)
                 != PERMISSION_GRANTED) {
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index a6a8793..e98b63e 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -29,6 +29,7 @@
 import android.os.UserHandle;
 import android.window.DisplayWindowPolicyController;
 
+import java.util.HashSet;
 import java.util.List;
 
 
@@ -45,6 +46,8 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
 
+    @NonNull final HashSet<Integer> mRunningUids = new HashSet<>();
+
     GenericWindowPolicyController(int windowFlags, int systemWindowFlags) {
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
     }
@@ -89,6 +92,17 @@
 
     @Override
     public void onRunningAppsChanged(int[] runningUids) {
+        mRunningUids.clear();
+        for (int i = 0; i < runningUids.length; i++) {
+            mRunningUids.add(runningUids[i]);
+        }
+    }
 
+    /**
+     * Returns true if an app with the given UID has an activity running on the virtual display for
+     * this controller.
+     */
+    boolean containsUid(int uid) {
+        return mRunningUids.contains(uid);
     }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 022da43..36edf4f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -32,6 +32,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.SparseArray;
 import android.window.DisplayWindowPolicyController;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -50,12 +51,18 @@
     private final Context mContext;
     private final AssociationInfo mAssociationInfo;
     private final int mOwnerUid;
-    private final GenericWindowPolicyController mGenericWindowPolicyController;
     private final InputController mInputController;
     @VisibleForTesting
     final List<Integer> mVirtualDisplayIds = new ArrayList<>();
     private final OnDeviceCloseListener mListener;
 
+    /**
+     * A mapping from the virtual display ID to its corresponding
+     * {@link GenericWindowPolicyController}.
+     */
+    private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers =
+            new SparseArray<>();
+
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
             IBinder token, int ownerUid, OnDeviceCloseListener listener) {
         this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener);
@@ -66,8 +73,6 @@
             int ownerUid, InputController inputController, OnDeviceCloseListener listener) {
         mContext = context;
         mAssociationInfo = associationInfo;
-        mGenericWindowPolicyController = new GenericWindowPolicyController(FLAG_SECURE,
-                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
         mOwnerUid = ownerUid;
         if (inputController == null) {
             mInputController = new InputController(mVirtualDeviceLock);
@@ -257,7 +262,11 @@
                     "Virtual device already have a virtual display with ID " + displayId);
         }
         mVirtualDisplayIds.add(displayId);
-        return mGenericWindowPolicyController;
+        final GenericWindowPolicyController dwpc =
+                new GenericWindowPolicyController(FLAG_SECURE,
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        mWindowPolicyControllers.put(displayId, dwpc);
+        return dwpc;
     }
 
     void onVirtualDisplayRemovedLocked(int displayId) {
@@ -266,12 +275,27 @@
                     "Virtual device doesn't have a virtual display with ID " + displayId);
         }
         mVirtualDisplayIds.remove(displayId);
+        mWindowPolicyControllers.remove(displayId);
     }
 
     int getOwnerUid() {
         return mOwnerUid;
     }
 
+    /**
+     * Returns true if an app with the given {@code uid} is currently running on this virtual
+     * device.
+     */
+    boolean isAppRunningOnVirtualDevice(int uid) {
+        final int size = mWindowPolicyControllers.size();
+        for (int i = 0; i < size; i++) {
+            if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     interface OnDeviceCloseListener {
         void onClose(int associationId);
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 46e75f7..0db670e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -252,7 +252,14 @@
 
         @Override
         public boolean isAppRunningOnAnyVirtualDevice(int uid) {
-            // TODO(yukl): Implement this using DWPC.onRunningAppsChanged
+            synchronized (mVirtualDeviceManagerLock) {
+                int size = mVirtualDevices.size();
+                for (int i = 0; i < size; i++) {
+                    if (mVirtualDevices.valueAt(i).isAppRunningOnVirtualDevice(uid)) {
+                        return true;
+                    }
+                }
+            }
             return false;
         }
     }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index d7c1cfb..811f2f5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -91,6 +91,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.ITelephonyRegistry;
@@ -106,6 +107,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -149,6 +151,7 @@
         IPhoneStateListener callback;
         IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
         IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
+        ICarrierPrivilegesListener carrierPrivilegesListener;
 
         int callerUid;
         int callerPid;
@@ -173,6 +176,10 @@
             return (onOpportunisticSubscriptionsChangedListenerCallback != null);
         }
 
+        boolean matchCarrierPrivilegesListener() {
+            return carrierPrivilegesListener != null;
+        }
+
         boolean canReadCallLog() {
             try {
                 return TelephonyPermissions.checkReadCallLog(
@@ -189,8 +196,9 @@
                     + " onSubscriptionsChangedListenererCallback="
                     + onSubscriptionsChangedListenerCallback
                     + " onOpportunisticSubscriptionsChangedListenererCallback="
-                    + onOpportunisticSubscriptionsChangedListenerCallback + " subId=" + subId
-                    + " phoneId=" + phoneId + " events=" + eventList + "}";
+                    + onOpportunisticSubscriptionsChangedListenerCallback
+                    + " carrierPrivilegesListener=" + carrierPrivilegesListener
+                    + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
         }
     }
 
@@ -402,6 +410,10 @@
      */
     private List<Map<Pair<Integer, ApnSetting>, PreciseDataConnectionState>>
             mPreciseDataConnectionStates;
+
+    /** Per-phoneId snapshot of privileged packages (names + UIDs). */
+    private List<Pair<List<String>, int[]>> mCarrierPrivilegeStates;
+
     /**
      * Support backward compatibility for {@link android.telephony.TelephonyDisplayInfo}.
      */
@@ -689,6 +701,7 @@
             cutListToSize(mBarringInfo, mNumPhones);
             cutListToSize(mPhysicalChannelConfigs, mNumPhones);
             cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
+            cutListToSize(mCarrierPrivilegeStates, mNumPhones);
             return;
         }
 
@@ -729,6 +742,7 @@
             mAllowedNetworkTypeReason[i] = -1;
             mAllowedNetworkTypeValue[i] = -1;
             mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST);
+            mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
         }
     }
 
@@ -794,6 +808,7 @@
         mIsDataEnabled = new boolean[numPhones];
         mDataEnabledReason = new int[numPhones];
         mLinkCapacityEstimateLists = new ArrayList<>();
+        mCarrierPrivilegeStates = new ArrayList<>();
 
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
@@ -831,6 +846,7 @@
             mAllowedNetworkTypeReason[i] = -1;
             mAllowedNetworkTypeValue[i] = -1;
             mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST);
+            mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
         }
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -2766,6 +2782,104 @@
     }
 
     @Override
+    public void addCarrierPrivilegesListener(
+            int phoneId,
+            ICarrierPrivilegesListener callback,
+            String callingPackage,
+            String callingFeatureId) {
+        int callerUserId = UserHandle.getCallingUserId();
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                "addCarrierPrivilegesListener");
+        if (VDBG) {
+            log(
+                    "listen carrier privs: E pkg=" + pii(callingPackage) + " phoneId=" + phoneId
+                            + " uid=" + Binder.getCallingUid()
+                            + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId
+                            + " callback=" + callback
+                            + " callback.asBinder=" + callback.asBinder());
+        }
+        if (!validatePhoneId(phoneId)) {
+            throw new IllegalArgumentException("Invalid slot index: " + phoneId);
+        }
+
+        synchronized (mRecords) {
+            Record r = add(
+                    callback.asBinder(), Binder.getCallingUid(), Binder.getCallingPid(), false);
+
+            if (r == null) return;
+
+            r.context = mContext;
+            r.carrierPrivilegesListener = callback;
+            r.callingPackage = callingPackage;
+            r.callingFeatureId = callingFeatureId;
+            r.callerUid = Binder.getCallingUid();
+            r.callerPid = Binder.getCallingPid();
+            r.phoneId = phoneId;
+            r.eventList = new ArraySet<>();
+            if (DBG) {
+                log("listen carrier privs: Register r=" + r);
+            }
+
+            Pair<List<String>, int[]> state = mCarrierPrivilegeStates.get(phoneId);
+            try {
+                r.carrierPrivilegesListener.onCarrierPrivilegesChanged(
+                        Collections.unmodifiableList(state.first),
+                        Arrays.copyOf(state.second, state.second.length));
+            } catch (RemoteException ex) {
+                remove(r.binder);
+            }
+        }
+    }
+
+    @Override
+    public void removeCarrierPrivilegesListener(
+            ICarrierPrivilegesListener callback, String callingPackage) {
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                "removeCarrierPrivilegesListener");
+        remove(callback.asBinder());
+    }
+
+    @Override
+    public void notifyCarrierPrivilegesChanged(
+            int phoneId, List<String> privilegedPackageNames, int[] privilegedUids) {
+        if (!checkNotifyPermission("notifyCarrierPrivilegesChanged")) {
+            return;
+        }
+        if (!validatePhoneId(phoneId)) return;
+        if (VDBG) {
+            log(
+                    "notifyCarrierPrivilegesChanged: phoneId=" + phoneId
+                            + ", <packages=" + pii(privilegedPackageNames)
+                            + ", uids=" + Arrays.toString(privilegedUids) + ">");
+        }
+        synchronized (mRecords) {
+            mCarrierPrivilegeStates.set(
+                    phoneId, new Pair<>(privilegedPackageNames, privilegedUids));
+            for (Record r : mRecords) {
+                // Listeners are per-slot, not per-subscription. This is to provide a stable
+                // view across SIM profile switches.
+                if (!r.matchCarrierPrivilegesListener()
+                        || !idMatch(r, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phoneId)) {
+                    continue;
+                }
+                try {
+                    // Make sure even in-process listeners can't modify the values.
+                    r.carrierPrivilegesListener.onCarrierPrivilegesChanged(
+                            Collections.unmodifiableList(privilegedPackageNames),
+                            Arrays.copyOf(privilegedUids, privilegedUids.length));
+                } catch (RemoteException ex) {
+                    mRemoveList.add(r.binder);
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
 
@@ -2814,6 +2928,11 @@
                 pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]);
                 pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i));
                 pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i));
+                // We need to obfuscate package names, and primitive arrays' native toString is ugly
+                Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
+                pw.println(
+                        "mCarrierPrivilegeState=<packages=" + pii(carrierPrivilegeState.first)
+                                + ", uids=" + Arrays.toString(carrierPrivilegeState.second) + ">");
                 pw.decreaseIndent();
             }
 
@@ -3540,4 +3659,10 @@
     private static String pii(String packageName) {
         return Build.IS_DEBUGGABLE ? packageName : "***";
     }
+
+    /** Redacts an entire list of package names if necessary. */
+    private static String pii(List<String> packageNames) {
+        if (packageNames.isEmpty() || Build.IS_DEBUGGABLE) return packageNames.toString();
+        return "[***, size=" + packageNames.size() + "]";
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ea345a7..2593e51 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5915,6 +5915,10 @@
             if (targetPkg == null) {
                 throw new IllegalArgumentException("null target");
             }
+            final int callingUserId = UserHandle.getUserId(r.uid);
+            if (mPackageManagerInt.filterAppAccess(targetPkg, r.uid, callingUserId)) {
+                return;
+            }
 
             Preconditions.checkFlagsArgument(modeFlags, Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@@ -5926,7 +5930,7 @@
             intent.setFlags(modeFlags);
 
             final NeededUriGrants needed = mUgmInternal.checkGrantUriPermissionFromIntent(intent,
-                    r.uid, targetPkg, UserHandle.getUserId(r.uid));
+                    r.uid, targetPkg, callingUserId);
             mUgmInternal.grantUriPermissionUncheckedFromIntent(needed, null);
         }
     }
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 9180ef8..336572f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -130,6 +130,9 @@
     @GuardedBy("this")
     private Future<?> mBatteryLevelSync;
 
+    @GuardedBy("this")
+    private Future<?> mProcessStateSync;
+
     // If both mStats and mWorkerLock need to be synchronized, mWorkerLock must be acquired first.
     private final Object mWorkerLock = new Object();
 
@@ -316,6 +319,25 @@
     }
 
     @Override
+    public Future<?> scheduleSyncDueToProcessStateChange(long delayMillis) {
+        synchronized (BatteryExternalStatsWorker.this) {
+            mProcessStateSync = scheduleDelayedSyncLocked(mProcessStateSync,
+                    () -> scheduleSync("procstate-change", UPDATE_ON_PROC_STATE_CHANGE),
+                    delayMillis);
+            return mProcessStateSync;
+        }
+    }
+
+    public void cancelSyncDueToProcessStateChange() {
+        synchronized (BatteryExternalStatsWorker.this) {
+            if (mProcessStateSync != null) {
+                mProcessStateSync.cancel(false);
+                mProcessStateSync = null;
+            }
+        }
+    }
+
+    @Override
     public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
         synchronized (BatteryExternalStatsWorker.this) {
             return mExecutorService.schedule(() -> {
@@ -434,6 +456,9 @@
                 if ((updateFlags & UPDATE_CPU) != 0) {
                     cancelCpuSyncDueToWakelockChange();
                 }
+                if ((updateFlags & UPDATE_ON_PROC_STATE_CHANGE) == UPDATE_ON_PROC_STATE_CHANGE) {
+                    cancelSyncDueToProcessStateChange();
+                }
             }
 
             try {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5a54332..af4ff58 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2782,29 +2782,18 @@
     void setAttachingSchedGroupLSP(ProcessRecord app) {
         int initialSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
         final ProcessStateRecord state = app.mState;
-        // If the process has been marked as foreground via Zygote.START_FLAG_USE_TOP_APP_PRIORITY,
-        // then verify that the top priority is actually is applied.
+        // If the process has been marked as foreground, it is starting as the top app (with
+        // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
         if (state.hasForegroundActivities()) {
-            String fallbackReason = null;
             try {
                 // The priority must be the same as how does {@link #applyOomAdjLSP} set for
                 // {@link ProcessList.SCHED_GROUP_TOP_APP}. We don't check render thread because it
                 // is not ready when attaching.
-                if (Process.getProcessGroup(app.getPid()) == THREAD_GROUP_TOP_APP) {
-                    app.getWindowProcessController().onTopProcChanged();
-                    setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
-                } else {
-                    fallbackReason = "not expected top priority";
-                }
-            } catch (Exception e) {
-                fallbackReason = e.toString();
-            }
-            if (fallbackReason == null) {
+                app.getWindowProcessController().onTopProcChanged();
+                setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
                 initialSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;
-            } else {
-                // The real scheduling group will depend on if there is any component of the process
-                // did something during attaching.
-                Slog.w(TAG, "Fallback pre-set sched group to default: " + fallbackReason);
+            } catch (Exception e) {
+                Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e);
             }
         }
 
diff --git a/services/core/java/com/android/server/backup/AppSpecificLocalesBackupHelper.java b/services/core/java/com/android/server/backup/AppSpecificLocalesBackupHelper.java
new file mode 100644
index 0000000..1726da2
--- /dev/null
+++ b/services/core/java/com/android/server/backup/AppSpecificLocalesBackupHelper.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.backup.BlobBackupHelper;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.locales.LocaleManagerInternal;
+
+/**
+ * Helper for backing up app-specific locales.
+ * <p>
+ * This helper is used in {@link com.android.server.backup.SystemBackupAgent}
+ */
+public class AppSpecificLocalesBackupHelper extends BlobBackupHelper {
+    private static final String TAG = "AppLocalesBackupHelper";   // must be < 23 chars
+    private static final boolean DEBUG = false;
+
+    // Current version of the blob schema
+    private static final int BLOB_VERSION = 1;
+
+    // Key under which the payload blob is stored
+    private static final String KEY_APP_LOCALES = "app_locales";
+
+    private final @UserIdInt int mUserId;
+
+    private final @NonNull LocaleManagerInternal mLocaleManagerInternal;
+
+    public AppSpecificLocalesBackupHelper(int userId) {
+        super(BLOB_VERSION, KEY_APP_LOCALES);
+        mUserId = userId;
+        mLocaleManagerInternal = LocalServices.getService(LocaleManagerInternal.class);
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        if (DEBUG) {
+            Slog.d(TAG, "Handling backup of " + key);
+        }
+
+        byte[] newPayload = null;
+        if (KEY_APP_LOCALES.equals(key)) {
+            try {
+                newPayload = mLocaleManagerInternal.getBackupPayload(mUserId);
+            } catch (Exception e) {
+                // Treat as no data
+                Slog.e(TAG, "Couldn't communicate with locale manager", e);
+                newPayload = null;
+            }
+        } else {
+            Slog.w(TAG, "Unexpected backup key " + key);
+        }
+        return newPayload;
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        if (DEBUG) {
+            Slog.d(TAG, "Handling restore of " + key);
+        }
+
+        if (KEY_APP_LOCALES.equals(key)) {
+            try {
+                mLocaleManagerInternal.stageAndApplyRestoredPayload(payload, mUserId);
+            } catch (Exception e) {
+                Slog.e(TAG, "Couldn't communicate with locale manager", e);
+            }
+        } else {
+            Slog.w(TAG, "Unexpected restore key " + key);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index fa18204..d39d2d1 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -57,6 +57,7 @@
     private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
     private static final String SLICES_HELPER = "slices";
     private static final String PEOPLE_HELPER = "people";
+    private static final String APP_LOCALES_HELPER = "app_locales";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -83,7 +84,7 @@
     private static final String WALLPAPER_IMAGE_KEY = WallpaperBackupHelper.WALLPAPER_IMAGE_KEY;
 
     private static final Set<String> sEligibleForMultiUser = Sets.newArraySet(
-            PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER);
+            PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER, APP_LOCALES_HELPER);
 
     private int mUserId = UserHandle.USER_SYSTEM;
 
@@ -102,6 +103,7 @@
         addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
         addHelper(SLICES_HELPER, new SliceBackupHelper(this));
         addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
+        addHelper(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 093ecd5..e120343 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -50,6 +50,8 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IUserManager;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -74,6 +76,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
@@ -99,6 +103,22 @@
     private static final boolean IS_EMULATOR =
             SystemProperties.getBoolean("ro.boot.qemu", false);
 
+    @VisibleForTesting
+    public static final long DEFAULT_CLIPBOARD_TIMEOUT_MILLIS = 3600000;
+
+    /**
+     * Device config property for whether clipboard auto clear is enabled on the device
+     **/
+    public static final String PROPERTY_AUTO_CLEAR_ENABLED =
+            "auto_clear_enabled";
+
+    /**
+     * Device config property for time period in milliseconds after which clipboard is auto
+     * cleared
+     **/
+    public static final String PROPERTY_AUTO_CLEAR_TIMEOUT =
+            "auto_clear_timeout";
+
     // DeviceConfig properties
     private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
     private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;
@@ -312,6 +332,10 @@
      * 'intendingUserId' and the uid is called 'intendingUid'.
      */
     private class ClipboardImpl extends IClipboard.Stub {
+
+        private final Handler mClipboardClearHandler = new ClipboardClearHandler(
+                mWorkerHandler.getLooper());
+
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
@@ -352,10 +376,34 @@
             }
             checkDataOwner(clip, intendingUid);
             synchronized (mLock) {
+                scheduleAutoClear(userId);
                 setPrimaryClipInternalLocked(clip, intendingUid, sourcePackage);
             }
         }
 
+        private void scheduleAutoClear(@UserIdInt int userId) {
+            final long oldIdentity = Binder.clearCallingIdentity();
+            try {
+                if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
+                        PROPERTY_AUTO_CLEAR_ENABLED, false)) {
+                    mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
+                            userId);
+                    Message clearMessage = Message.obtain(mClipboardClearHandler,
+                            ClipboardClearHandler.MSG_CLEAR, userId, 0, userId);
+                    mClipboardClearHandler.sendMessageDelayed(clearMessage,
+                            getTimeoutForAutoClear());
+                }
+            } finally {
+                Binder.restoreCallingIdentity(oldIdentity);
+            }
+        }
+
+        private long getTimeoutForAutoClear() {
+            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_CLIPBOARD,
+                    PROPERTY_AUTO_CLEAR_TIMEOUT,
+                    DEFAULT_CLIPBOARD_TIMEOUT_MILLIS);
+        }
+
         @Override
         public void clearPrimaryClip(String callingPackage, @UserIdInt int userId) {
             final int intendingUid = getIntendingUid(callingPackage, userId);
@@ -365,6 +413,8 @@
                 return;
             }
             synchronized (mLock) {
+                mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
+                        userId);
                 setPrimaryClipInternalLocked(null, intendingUid, callingPackage);
             }
         }
@@ -391,6 +441,9 @@
                 PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                 showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
                 notifyTextClassifierLocked(clipboard, pkg, intendingUid);
+                if (clipboard.primaryClip != null) {
+                    scheduleAutoClear(userId);
+                }
                 return clipboard.primaryClip;
             }
         }
@@ -484,6 +537,32 @@
                 return null;
             }
         }
+
+        private class ClipboardClearHandler extends Handler {
+
+            public static final int MSG_CLEAR = 101;
+
+            ClipboardClearHandler(Looper looper) {
+                super(looper);
+            }
+
+            public void handleMessage(@NonNull Message msg) {
+                switch (msg.what) {
+                    case MSG_CLEAR:
+                        final int userId = msg.arg1;
+                        synchronized (mLock) {
+                            if (getClipboardLocked(userId).primaryClip != null) {
+                                FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
+                                        FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
+                                setPrimaryClipInternalLocked(null, Binder.getCallingUid(), null);
+                            }
+                        }
+                        break;
+                    default:
+                        Slog.wtf(TAG, "ClipboardClearHandler received unknown message " + msg.what);
+                }
+            }
+        }
     };
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/display/DensityMap.java b/services/core/java/com/android/server/display/DensityMap.java
new file mode 100644
index 0000000..4aafd14
--- /dev/null
+++ b/services/core/java/com/android/server/display/DensityMap.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Class which can compute the logical density for a display resolution. It holds a collection
+ * of pre-configured densities, which are used for look-up and interpolation.
+ */
+public class DensityMap {
+
+    // Instead of resolutions we store the squared diagonal size. Diagonals make the map
+    // keys invariant to rotations and are useful for interpolation because they're scalars.
+    // Squared diagonals have the same properties as diagonals (the square function is monotonic)
+    // but also allow us to use integer types and avoid floating point arithmetics.
+    private final Entry[] mSortedDensityMapEntries;
+
+    /**
+     * Creates a density map. The newly created object takes ownership of the passed array.
+     */
+    static DensityMap createByOwning(Entry[] densityMapEntries) {
+        return new DensityMap(densityMapEntries);
+    }
+
+    private DensityMap(Entry[] densityMapEntries) {
+        Arrays.sort(densityMapEntries, Comparator.comparingInt(entry -> entry.squaredDiagonal));
+        mSortedDensityMapEntries = densityMapEntries;
+        verifyDensityMap(mSortedDensityMapEntries);
+    }
+
+    /**
+     * Returns the logical density for the given resolution.
+     *
+     * If the resolution matches one of the entries in the map, the corresponding density is
+     * returned. Otherwise the return value is interpolated using the closest entries in the map.
+     */
+    public int getDensityForResolution(int width, int height) {
+        int squaredDiagonal = width * width + height * height;
+
+        // Search for two pre-configured entries "left" and "right" with the following criteria
+        //  * left <= squaredDiagonal
+        //  * squaredDiagonal - left is minimal
+        //  * right > squaredDiagonal
+        //  * right - squaredDiagonal is minimal
+        Entry left = Entry.ZEROES;
+        Entry right = null;
+
+        for (Entry entry : mSortedDensityMapEntries) {
+            if (entry.squaredDiagonal <= squaredDiagonal) {
+                left = entry;
+            } else {
+                right = entry;
+                break;
+            }
+        }
+
+        // Check if we found an exact match.
+        if (left.squaredDiagonal == squaredDiagonal) {
+            return left.density;
+        }
+
+        // If no configured resolution is higher than the specified resolution, interpolate
+        // between (0,0) and (maxConfiguredDiagonal, maxConfiguredDensity).
+        if (right == null) {
+            right = left;  // largest entry in the sorted array
+            left = Entry.ZEROES;
+        }
+
+        double leftDiagonal = Math.sqrt(left.squaredDiagonal);
+        double rightDiagonal = Math.sqrt(right.squaredDiagonal);
+        double diagonal = Math.sqrt(squaredDiagonal);
+
+        return (int) Math.round((diagonal - leftDiagonal) * (right.density - left.density)
+                / (rightDiagonal - leftDiagonal) + left.density);
+    }
+
+    private static void verifyDensityMap(Entry[] sortedEntries) {
+        for (int i = 1; i < sortedEntries.length; i++) {
+            Entry prev = sortedEntries[i - 1];
+            Entry curr = sortedEntries[i];
+
+            if (prev.squaredDiagonal == curr.squaredDiagonal) {
+                // This will most often happen because there are two entries with the same
+                // resolution (AxB and AxB) or rotated resolution (AxB and BxA), but it can also
+                // happen in the very rare cases when two different resolutions happen to have
+                // the same diagonal (e.g. 100x700 and 500x500).
+                throw new IllegalStateException("Found two entries in the density map with"
+                        + " the same diagonal: " + prev + ", " + curr);
+            } else if (prev.density > curr.density) {
+                throw new IllegalStateException("Found two entries in the density map with"
+                        + " increasing diagonal but decreasing density: " + prev + ", " + curr);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "DensityMap{"
+                + "mDensityMapEntries=" + Arrays.toString(mSortedDensityMapEntries)
+                + '}';
+    }
+
+    static class Entry {
+        public static final Entry ZEROES = new Entry(0, 0, 0);
+
+        public final int squaredDiagonal;
+        public final int density;
+
+        Entry(int width, int height, int density) {
+            this.squaredDiagonal = width * width + height * height;
+            this.density = density;
+        }
+
+        @Override
+        public String toString() {
+            return "DensityMapEntry{"
+                    + "squaredDiagonal=" + squaredDiagonal
+                    + ", density=" + density + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2ae5cbb..a9e1647 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
@@ -31,6 +32,7 @@
 
 import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.config.Density;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.HbmTiming;
@@ -52,6 +54,7 @@
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 
 import javax.xml.datatype.DatatypeConfigurationException;
@@ -70,6 +73,8 @@
     private static final String ETC_DIR = "etc";
     private static final String DISPLAY_CONFIG_DIR = "displayconfig";
     private static final String CONFIG_FILE_FORMAT = "display_%s.xml";
+    private static final String DEFAULT_CONFIG_FILE = "default.xml";
+    private static final String DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT = "default_%s.xml";
     private static final String PORT_SUFFIX_FORMAT = "port_%d";
     private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
     private static final String NO_SUFFIX_FORMAT = "%d";
@@ -121,6 +126,7 @@
     private List<String> mQuirks;
     private boolean mIsHighBrightnessModeEnabled = false;
     private HighBrightnessModeData mHbmData;
+    private DensityMap mDensityMap;
     private String mLoadedFrom = null;
 
     private DisplayDeviceConfig(Context context) {
@@ -141,6 +147,33 @@
      */
     public static DisplayDeviceConfig create(Context context, long physicalDisplayId,
             boolean isDefaultDisplay) {
+        final DisplayDeviceConfig config = createWithoutDefaultValues(context, physicalDisplayId,
+                isDefaultDisplay);
+
+        config.copyUninitializedValuesFromSecondaryConfig(loadDefaultConfigurationXml(context));
+        return config;
+    }
+
+    /**
+     * Creates an instance using global values since no display device config xml exists.
+     * Uses values from config or PowerManager.
+     *
+     * @param context
+     * @param useConfigXml
+     * @return A configuration instance.
+     */
+    public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
+        final DisplayDeviceConfig config;
+        if (useConfigXml) {
+            config = getConfigFromGlobalXml(context);
+        } else {
+            config = getConfigFromPmValues(context);
+        }
+        return config;
+    }
+
+    private static DisplayDeviceConfig createWithoutDefaultValues(Context context,
+            long physicalDisplayId, boolean isDefaultDisplay) {
         DisplayDeviceConfig config;
 
         config = loadConfigFromDirectory(context, Environment.getProductDirectory(),
@@ -161,22 +194,53 @@
         return create(context, isDefaultDisplay);
     }
 
-    /**
-     * Creates an instance using global values since no display device config xml exists.
-     * Uses values from config or PowerManager.
-     *
-     * @param context
-     * @param useConfigXml
-     * @return A configuration instance.
-     */
-    public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
-        DisplayDeviceConfig config;
-        if (useConfigXml) {
-            config = getConfigFromGlobalXml(context);
-        } else {
-            config = getConfigFromPmValues(context);
+    private static DisplayConfiguration loadDefaultConfigurationXml(Context context) {
+        List<File> defaultXmlLocations = new ArrayList<>();
+        defaultXmlLocations.add(Environment.buildPath(Environment.getProductDirectory(),
+                ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE));
+        defaultXmlLocations.add(Environment.buildPath(Environment.getVendorDirectory(),
+                ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE));
+
+        // Read config_defaultUiModeType directly because UiModeManager hasn't started yet.
+        final int uiModeType = context.getResources()
+                .getInteger(com.android.internal.R.integer.config_defaultUiModeType);
+        final String uiModeTypeStr = Configuration.getUiModeTypeString(uiModeType);
+        if (uiModeTypeStr != null) {
+            defaultXmlLocations.add(Environment.buildPath(Environment.getRootDirectory(),
+                    ETC_DIR, DISPLAY_CONFIG_DIR,
+                    String.format(DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT, uiModeTypeStr)));
         }
-        return config;
+        defaultXmlLocations.add(Environment.buildPath(Environment.getRootDirectory(),
+                ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE));
+
+        final File configFile = getFirstExistingFile(defaultXmlLocations);
+        if (configFile == null) {
+            // Display configuration files aren't required to exist.
+            return null;
+        }
+
+        DisplayConfiguration defaultConfig = null;
+
+        try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
+            defaultConfig = XmlParser.read(in);
+            if (defaultConfig == null) {
+                Slog.i(TAG, "Default DisplayDeviceConfig file is null");
+            }
+        } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+            Slog.e(TAG, "Encountered an error while reading/parsing display config file: "
+                    + configFile, e);
+        }
+
+        return defaultConfig;
+    }
+
+    private static File getFirstExistingFile(Collection<File> files) {
+        for (File file : files) {
+            if (file.exists() && file.isFile()) {
+                return file;
+            }
+        }
+        return null;
     }
 
     private static DisplayDeviceConfig loadConfigFromDirectory(Context context,
@@ -316,9 +380,13 @@
         return mRefreshRateLimitations;
     }
 
+    public DensityMap getDensityMap() {
+        return mDensityMap;
+    }
+
     @Override
     public String toString() {
-        String str = "DisplayDeviceConfig{"
+        return "DisplayDeviceConfig{"
                 + "mLoadedFrom=" + mLoadedFrom
                 + ", mBacklight=" + Arrays.toString(mBacklight)
                 + ", mNits=" + Arrays.toString(mNits)
@@ -340,8 +408,8 @@
                 + ", mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
+                + ", mDensityMap= " + mDensityMap
                 + "}";
-        return str;
     }
 
     private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory,
@@ -384,6 +452,7 @@
         try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
             final DisplayConfiguration config = XmlParser.read(in);
             if (config != null) {
+                loadDensityMap(config);
                 loadBrightnessDefaultFromDdcXml(config);
                 loadBrightnessConstraintsFromConfigXml();
                 loadBrightnessMap(config);
@@ -429,6 +498,35 @@
         setProxSensorUnspecified();
     }
 
+    private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) {
+        if (defaultConfig == null) {
+            return;
+        }
+
+        if (mDensityMap == null) {
+            loadDensityMap(defaultConfig);
+        }
+    }
+
+    private void loadDensityMap(DisplayConfiguration config) {
+        if (config.getDensityMap() == null) {
+            return;
+        }
+
+        final List<Density> entriesFromXml = config.getDensityMap().getDensity();
+
+        final DensityMap.Entry[] entries =
+                new DensityMap.Entry[entriesFromXml.size()];
+        for (int i = 0; i < entriesFromXml.size(); i++) {
+            final Density density = entriesFromXml.get(i);
+            entries[i] = new DensityMap.Entry(
+                    density.getWidth().intValue(),
+                    density.getHeight().intValue(),
+                    density.getDensity().intValue());
+        }
+        mDensityMap = DensityMap.createByOwning(entries);
+    }
+
     private void loadBrightnessDefaultFromDdcXml(DisplayConfiguration config) {
         // Default brightness values are stored in the displayDeviceConfig file,
         // Or we fallback standard values if not.
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index b6d13e0..300f59e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -426,6 +426,15 @@
                     : mDefaultModeId;
         }
 
+        private int getLogicalDensity() {
+            DensityMap densityMap = getDisplayDeviceConfig().getDensityMap();
+            if (densityMap == null) {
+                return (int) (mStaticDisplayInfo.density * 160 + 0.5);
+            }
+
+            return densityMap.getDensityForResolution(mInfo.width, mInfo.height);
+        }
+
         private void loadDisplayDeviceConfig() {
             // Load display device config
             final Context context = getOverlayContext();
@@ -591,7 +600,7 @@
                 final DisplayAddress.Physical physicalAddress =
                         DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId);
                 mInfo.address = physicalAddress;
-                mInfo.densityDpi = (int) (mStaticDisplayInfo.density * 160 + 0.5f);
+                mInfo.densityDpi = getLogicalDensity();
                 mInfo.xDpi = mActiveSfDisplayMode.xDpi;
                 mInfo.yDpi = mActiveSfDisplayMode.yDpi;
                 mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo;
@@ -1029,7 +1038,7 @@
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 pw.println("  " + mSupportedModes.valueAt(i));
             }
-            pw.println("mSupportedColorModes=" + mSupportedColorModes.toString());
+            pw.println("mSupportedColorModes=" + mSupportedColorModes);
             pw.println("mDisplayDeviceConfig=" + mDisplayDeviceConfig);
         }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 3d91fee..c5dc23e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -82,6 +82,7 @@
     private IBinder mCurToken;
     private int mCurSeq;
     private boolean mVisibleBound;
+    private boolean mSupportsStylusHw;
 
     /**
      * Binding flags for establishing connection to the {@link InputMethodService}.
@@ -295,6 +296,10 @@
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
                 }
+                mSupportsStylusHw = mMethodMap.get(mSelectedMethodId).supportsStylusHandwriting();
+                if (mSupportsStylusHw) {
+                    // TODO init Handwriting spy.
+                }
             }
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
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 c1d8e78..e7cc7b6 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -179,7 +179,7 @@
     protected interface LocationTransport {
 
         void deliverOnLocationChanged(LocationResult locationResult,
-                @Nullable IRemoteCallback onCompleteCallback) throws Exception;
+                @Nullable Runnable onCompleteCallback) throws Exception;
         void deliverOnFlushComplete(int requestCode) throws Exception;
     }
 
@@ -199,8 +199,9 @@
 
         @Override
         public void deliverOnLocationChanged(LocationResult locationResult,
-                @Nullable IRemoteCallback onCompleteCallback) throws RemoteException {
-            mListener.onLocationChanged(locationResult.asList(), onCompleteCallback);
+                @Nullable Runnable onCompleteCallback) throws RemoteException {
+            mListener.onLocationChanged(locationResult.asList(),
+                    SingleUseCallback.wrap(onCompleteCallback));
         }
 
         @Override
@@ -228,7 +229,7 @@
 
         @Override
         public void deliverOnLocationChanged(LocationResult locationResult,
-                @Nullable IRemoteCallback onCompleteCallback)
+                @Nullable Runnable onCompleteCallback)
                 throws PendingIntent.CanceledException {
             BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setDontSendToRestrictedApps(true);
@@ -244,34 +245,20 @@
                 intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
             }
 
-            PendingIntent.OnFinished onFinished = null;
-
             // send() SHOULD only run the completion callback if it completes successfully. however,
-            // b/201299281 (which could not be fixed in the S timeframe) means that it's possible
+            // b/199464864 (which could not be fixed in the S timeframe) means that it's possible
             // for send() to throw an exception AND run the completion callback. if this happens, we
             // would over-release the wakelock... we take matters into our own hands to ensure that
             // the completion callback can only be run if send() completes successfully. this means
             // the completion callback may be run inline - but as we've never specified what thread
             // the callback is run on, this is fine.
-            GatedCallback gatedCallback;
-            if (onCompleteCallback != null) {
-                gatedCallback = new GatedCallback(() -> {
-                    try {
-                        onCompleteCallback.sendResult(null);
-                    } catch (RemoteException e) {
-                        throw e.rethrowFromSystemServer();
-                    }
-                });
-                onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run();
-            } else {
-                gatedCallback = new GatedCallback(null);
-            }
+            GatedCallback gatedCallback = new GatedCallback(onCompleteCallback);
 
             mPendingIntent.send(
                     mContext,
                     0,
                     intent,
-                    onFinished,
+                    (pI, i, rC, rD, rE) -> gatedCallback.run(),
                     null,
                     null,
                     options.toBundle());
@@ -308,7 +295,7 @@
 
         @Override
         public void deliverOnLocationChanged(@Nullable LocationResult locationResult,
-                @Nullable IRemoteCallback onCompleteCallback)
+                @Nullable Runnable onCompleteCallback)
                 throws RemoteException {
             // ILocationCallback doesn't currently support completion callbacks
             Preconditions.checkState(onCompleteCallback == null);
@@ -727,13 +714,6 @@
 
         final PowerManager.WakeLock mWakeLock;
 
-        // b/206340085 - if we allocate a new wakelock releaser object for every delivery we
-        // increase the risk of resource starvation. if a client stops processing deliveries the
-        // system server binder allocation pool will be starved as we continue to queue up
-        // deliveries, each with a new allocation. in order to mitigate this, we use a single
-        // releaser object per registration rather than per delivery.
-        final ExternalWakeLockReleaser mWakeLockReleaser;
-
         private volatile ProviderTransport mProviderTransport;
         private int mNumLocationsDelivered = 0;
         private long mExpirationRealtimeMs = Long.MAX_VALUE;
@@ -747,7 +727,6 @@
                     .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
             mWakeLock.setReferenceCounted(true);
             mWakeLock.setWorkSource(request.getWorkSource());
-            mWakeLockReleaser = new ExternalWakeLockReleaser(identity, mWakeLock);
         }
 
         @Override
@@ -964,7 +943,7 @@
                     }
 
                     listener.deliverOnLocationChanged(deliverLocationResult,
-                            mUseWakeLock ? mWakeLockReleaser : null);
+                            mUseWakeLock ? mWakeLock::release : null);
                     EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
                             getIdentity());
                 }
@@ -2782,7 +2761,7 @@
         @GuardedBy("this")
         private boolean mRun;
 
-        GatedCallback(@Nullable Runnable callback) {
+        GatedCallback(Runnable callback) {
             mCallback = callback;
         }
 
@@ -2817,24 +2796,4 @@
             }
         }
     }
-
-    private static class ExternalWakeLockReleaser extends IRemoteCallback.Stub {
-
-        private final CallerIdentity mIdentity;
-        private final PowerManager.WakeLock mWakeLock;
-
-        ExternalWakeLockReleaser(CallerIdentity identity, PowerManager.WakeLock wakeLock) {
-            mIdentity = identity;
-            mWakeLock = Objects.requireNonNull(wakeLock);
-        }
-
-        @Override
-        public void sendResult(Bundle data) {
-            try {
-                mWakeLock.release();
-            } catch (RuntimeException e) {
-                Log.e(TAG, "wakelock over-released by " + mIdentity, e);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 7d4877c..258ae8c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2151,6 +2151,10 @@
                 final PackagePreferences r = mPackagePreferences.valueAt(i);
                 event.writeInt(r.uid);
                 event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
+
+                // collect whether this package's importance info was user-set for later, if needed
+                // before the migration is enabled, this will simply default to false in all cases.
+                boolean importanceIsUserSet = false;
                 if (mPermissionHelper.isMigrationEnabled()) {
                     // Even if this package's data is not present, we need to write something;
                     // so default to IMPORTANCE_NONE, since if PM doesn't know about the package
@@ -2158,8 +2162,12 @@
                     int importance = IMPORTANCE_NONE;
                     Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
                     if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
-                        importance = pkgPermissions.get(key).first
+                        Pair<Boolean, Boolean> permissionPair = pkgPermissions.get(key);
+                        importance = permissionPair.first
                                 ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
+                        // cache the second value for writing later
+                        importanceIsUserSet = permissionPair.second;
+
                         pkgsWithPermissionsToHandle.remove(key);
                     }
                     event.writeInt(importance);
@@ -2168,6 +2176,7 @@
                 }
                 event.writeInt(r.visibility);
                 event.writeInt(r.lockedAppFields);
+                event.writeBoolean(importanceIsUserSet);  // optional bool user_set_importance = 5;
                 events.add(event.build());
             }
         }
@@ -2189,6 +2198,7 @@
                 // builder
                 event.writeInt(DEFAULT_VISIBILITY);
                 event.writeInt(DEFAULT_LOCKED_APP_FIELDS);
+                event.writeBoolean(pkgPermissions.get(p).second); // user_set_importance field
                 events.add(event.build());
             }
         }
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index e99512d..bedb8b9 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -74,13 +74,6 @@
         mArtManagerService = mInjector.getArtManagerService();
     }
 
-    AppDataHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
-        mPm = pm;
-        mInjector = injector;
-        mInstaller = injector.getInstaller();
-        mArtManagerService = injector.getArtManagerService();
-    }
-
     /**
      * Prepare app data for the given app just after it was installed or
      * upgraded. This method carefully only touches users that it's installed
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 8fda109..e84d990 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -236,7 +236,9 @@
         if (res) {
             final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
             info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
-            info.sendSystemPackageUpdatedBroadcasts();
+            if (disabledSystemPs != null) {
+                info.sendSystemPackageUpdatedBroadcasts(disabledSystemPs.getAppId());
+            }
         }
         // Force a gc here.
         Runtime.getRuntime().gc();
@@ -591,6 +593,7 @@
         if (outInfo != null) {
             // Delete the updated package
             outInfo.mIsRemovedPackageSystemUpdate = true;
+            outInfo.mAppIdChanging = disabledPs.getAppId() != deletedPs.getAppId();
         }
 
         if (disabledPs.getVersionCode() < deletedPs.getVersionCode()
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index c8594eb..8e6746d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -32,8 +32,6 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -47,7 +45,6 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
-import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
@@ -56,6 +53,8 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
 import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
+import static com.android.server.pm.PackageManagerService.DEBUG_UPGRADE;
+import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
 import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.POST_INSTALL;
@@ -73,13 +72,16 @@
 import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
 import static com.android.server.pm.PackageManagerService.SCAN_IGNORE_FROZEN;
 import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
 import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
 import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
 import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
+import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
 import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
 import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
@@ -89,6 +91,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
 import android.app.backup.IBackupManager;
 import android.content.ContentResolver;
@@ -125,7 +128,6 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -145,14 +147,15 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
-import com.android.server.Watchdog;
+import com.android.server.pm.dex.ArtManagerService;
+import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.dex.ViewCompiler;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -190,271 +193,45 @@
 final class InstallPackageHelper {
     private final PackageManagerService mPm;
     private final AppDataHelper mAppDataHelper;
-    private final PackageManagerServiceInjector mInjector;
     private final BroadcastHelper mBroadcastHelper;
     private final RemovePackageHelper mRemovePackageHelper;
-    private final ScanPackageHelper mScanPackageHelper;
+    private final StorageManager mStorageManager;
+    private final RollbackManagerInternal mRollbackManager;
+    private final IncrementalManager mIncrementalManager;
+    private final ApexManager mApexManager;
+    private final DexManager mDexManager;
+    private final ArtManagerService mArtManagerService;
+    private final AppOpsManager mAppOpsManager;
+    private final Context mContext;
+    private final PackageDexOptimizer mPackageDexOptimizer;
+    private final PackageAbiHelper mPackageAbiHelper;
+    private final ViewCompiler mViewCompiler;
+    private final IBackupManager mIBackupManager;
 
     // TODO(b/198166813): remove PMS dependency
     InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
         mPm = pm;
-        mInjector = pm.mInjector;
         mAppDataHelper = appDataHelper;
-        mBroadcastHelper = new BroadcastHelper(mInjector);
+        mBroadcastHelper = new BroadcastHelper(pm.mInjector);
         mRemovePackageHelper = new RemovePackageHelper(pm);
-        mScanPackageHelper = new ScanPackageHelper(pm);
+        mStorageManager = pm.mInjector.getSystemService(StorageManager.class);
+        mRollbackManager = pm.mInjector.getLocalService(RollbackManagerInternal.class);
+        mIncrementalManager = pm.mInjector.getIncrementalManager();
+        mApexManager = pm.mInjector.getApexManager();
+        mDexManager = pm.mInjector.getDexManager();
+        mArtManagerService = pm.mInjector.getArtManagerService();
+        mAppOpsManager = pm.mInjector.getSystemService(AppOpsManager.class);
+        mContext = pm.mInjector.getContext();
+        mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
+        mPackageAbiHelper = pm.mInjector.getAbiHelper();
+        mViewCompiler = pm.mInjector.getViewCompiler();
+        mIBackupManager = pm.mInjector.getIBackupManager();
     }
 
     InstallPackageHelper(PackageManagerService pm) {
         this(pm, new AppDataHelper(pm));
     }
 
-    InstallPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
-        mPm = pm;
-        mInjector = injector;
-        mAppDataHelper = new AppDataHelper(pm, mInjector);
-        mBroadcastHelper = new BroadcastHelper(injector);
-        mRemovePackageHelper = new RemovePackageHelper(pm);
-        mScanPackageHelper = new ScanPackageHelper(pm);
-    }
-
-    @GuardedBy("mPm.mLock")
-    public Map<String, ReconciledPackage> reconcilePackagesLocked(
-            final ReconcileRequest request, KeySetManagerService ksms,
-            PackageManagerServiceInjector injector)
-            throws ReconcileFailure {
-        final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
-
-        final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
-
-        // make a copy of the existing set of packages so we can combine them with incoming packages
-        final ArrayMap<String, AndroidPackage> combinedPackages =
-                new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
-
-        combinedPackages.putAll(request.mAllPackages);
-
-        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
-                new ArrayMap<>();
-
-        for (String installPackageName : scannedPackages.keySet()) {
-            final ScanResult scanResult = scannedPackages.get(installPackageName);
-
-            // add / replace existing with incoming packages
-            combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
-                    scanResult.mRequest.mParsedPackage);
-
-            // in the first pass, we'll build up the set of incoming shared libraries
-            final List<SharedLibraryInfo> allowedSharedLibInfos =
-                    SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
-                            request.mSharedLibrarySource);
-            if (allowedSharedLibInfos != null) {
-                for (SharedLibraryInfo info : allowedSharedLibInfos) {
-                    if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
-                            incomingSharedLibraries, info)) {
-                        throw new ReconcileFailure("Shared Library " + info.getName()
-                                + " is being installed twice in this set!");
-                    }
-                }
-            }
-
-            // the following may be null if we're just reconciling on boot (and not during install)
-            final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
-            final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
-            final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
-            final boolean isInstall = installArgs != null;
-            if (isInstall && (res == null || prepareResult == null)) {
-                throw new ReconcileFailure("Reconcile arguments are not balanced for "
-                        + installPackageName + "!");
-            }
-
-            final DeletePackageAction deletePackageAction;
-            // we only want to try to delete for non system apps
-            if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
-                final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
-                final int deleteFlags = PackageManager.DELETE_KEEP_DATA
-                        | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
-                deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
-                        prepareResult.mOriginalPs, prepareResult.mDisabledPs,
-                        deleteFlags, null /* all users */);
-                if (deletePackageAction == null) {
-                    throw new ReconcileFailure(
-                            PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
-                            "May not delete " + installPackageName + " to replace");
-                }
-            } else {
-                deletePackageAction = null;
-            }
-
-            final int scanFlags = scanResult.mRequest.mScanFlags;
-            final int parseFlags = scanResult.mRequest.mParseFlags;
-            final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
-
-            final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
-            final PackageSetting lastStaticSharedLibSetting =
-                    request.mLastStaticSharedLibSettings.get(installPackageName);
-            final PackageSetting signatureCheckPs =
-                    (prepareResult != null && lastStaticSharedLibSetting != null)
-                            ? lastStaticSharedLibSetting
-                            : scanResult.mPkgSetting;
-            boolean removeAppKeySetData = false;
-            boolean sharedUserSignaturesChanged = false;
-            SigningDetails signingDetails = null;
-            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
-                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
-                    // We just determined the app is signed correctly, so bring
-                    // over the latest parsed certs.
-                } else {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                        throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                "Package " + parsedPackage.getPackageName()
-                                        + " upgrade keys do not match the previously installed"
-                                        + " version");
-                    } else {
-                        String msg = "System package " + parsedPackage.getPackageName()
-                                + " signature changed; retaining data.";
-                        PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-                    }
-                }
-                signingDetails = parsedPackage.getSigningDetails();
-            } else {
-                try {
-                    final Settings.VersionInfo versionInfo =
-                            request.mVersionInfos.get(installPackageName);
-                    final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
-                    final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
-                    final boolean isRollback = installArgs != null
-                            && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
-                    final boolean compatMatch = verifySignatures(signatureCheckPs,
-                            disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
-                            compareRecover, isRollback);
-                    // The new KeySets will be re-added later in the scanning process.
-                    if (compatMatch) {
-                        removeAppKeySetData = true;
-                    }
-                    // We just determined the app is signed correctly, so bring
-                    // over the latest parsed certs.
-                    signingDetails = parsedPackage.getSigningDetails();
-
-                    // if this is is a sharedUser, check to see if the new package is signed by a
-                    // newer
-                    // signing certificate than the existing one, and if so, copy over the new
-                    // details
-                    if (signatureCheckPs.getSharedUser() != null) {
-                        // Attempt to merge the existing lineage for the shared SigningDetails with
-                        // the lineage of the new package; if the shared SigningDetails are not
-                        // returned this indicates the new package added new signers to the lineage
-                        // and/or changed the capabilities of existing signers in the lineage.
-                        SigningDetails sharedSigningDetails =
-                                signatureCheckPs.getSharedUser().signatures.mSigningDetails;
-                        SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
-                                signingDetails);
-                        if (mergedDetails != sharedSigningDetails) {
-                            signatureCheckPs.getSharedUser().signatures.mSigningDetails =
-                                    mergedDetails;
-                        }
-                        if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
-                            signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
-                        }
-                    }
-                } catch (PackageManagerException e) {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                        throw new ReconcileFailure(e);
-                    }
-                    signingDetails = parsedPackage.getSigningDetails();
-
-                    // If the system app is part of a shared user we allow that shared user to
-                    // change
-                    // signatures as well as part of an OTA. We still need to verify that the
-                    // signatures
-                    // are consistent within the shared user for a given boot, so only allow
-                    // updating
-                    // the signatures on the first package scanned for the shared user (i.e. if the
-                    // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
-                    if (signatureCheckPs.getSharedUser() != null) {
-                        final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
-                                .signatures.mSigningDetails.getSignatures();
-                        if (signatureCheckPs.getSharedUser().signaturesChanged != null
-                                && compareSignatures(sharedUserSignatures,
-                                parsedPackage.getSigningDetails().getSignatures())
-                                != PackageManager.SIGNATURE_MATCH) {
-                            if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
-                                // Mismatched signatures is an error and silently skipping system
-                                // packages will likely break the device in unforeseen ways.
-                                // However, we allow the device to boot anyway because, prior to Q,
-                                // vendors were not expecting the platform to crash in this
-                                // situation.
-                                // This WILL be a hard failure on any new API levels after Q.
-                                throw new ReconcileFailure(
-                                        INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
-                                        "Signature mismatch for shared user: "
-                                                + scanResult.mPkgSetting.getSharedUser());
-                            } else {
-                                // Treat mismatched signatures on system packages using a shared
-                                // UID as
-                                // fatal for the system overall, rather than just failing to install
-                                // whichever package happened to be scanned later.
-                                throw new IllegalStateException(
-                                        "Signature mismatch on system package "
-                                                + parsedPackage.getPackageName()
-                                                + " for shared user "
-                                                + scanResult.mPkgSetting.getSharedUser());
-                            }
-                        }
-
-                        sharedUserSignaturesChanged = true;
-                        signatureCheckPs.getSharedUser().signatures.mSigningDetails =
-                                parsedPackage.getSigningDetails();
-                        signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE;
-                    }
-                    // File a report about this.
-                    String msg = "System package " + parsedPackage.getPackageName()
-                            + " signature changed; retaining data.";
-                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-                } catch (IllegalArgumentException e) {
-                    // should never happen: certs matched when checking, but not when comparing
-                    // old to new for sharedUser
-                    throw new RuntimeException(
-                            "Signing certificates comparison made on incomparable signing details"
-                                    + " but somehow passed verifySignatures!", e);
-                }
-            }
-
-            result.put(installPackageName,
-                    new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
-                            res, request.mPreparedPackages.get(installPackageName), scanResult,
-                            deletePackageAction, allowedSharedLibInfos, signingDetails,
-                            sharedUserSignaturesChanged, removeAppKeySetData));
-        }
-
-        for (String installPackageName : scannedPackages.keySet()) {
-            // Check all shared libraries and map to their actual file path.
-            // We only do this here for apps not on a system dir, because those
-            // are the only ones that can fail an install due to this.  We
-            // will take care of the system apps by updating all of their
-            // library paths after the scan is done. Also during the initial
-            // scan don't update any libs as we do this wholesale after all
-            // apps are scanned to avoid dependency based scanning.
-            final ScanResult scanResult = scannedPackages.get(installPackageName);
-            if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
-                    || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
-                    != 0) {
-                continue;
-            }
-            try {
-                result.get(installPackageName).mCollectedSharedLibraryInfos =
-                        SharedLibraryHelper.collectSharedLibraryInfos(
-                                scanResult.mRequest.mParsedPackage,
-                                combinedPackages, request.mSharedLibrarySource,
-                                incomingSharedLibraries, injector.getCompatibility());
-
-            } catch (PackageManagerException e) {
-                throw new ReconcileFailure(e.error, e.getMessage());
-            }
-        }
-
-        return result;
-    }
-
     /**
      * Commits the package scan and modifies system state.
      * <p><em>WARNING:</em> The method may throw an exception in the middle
@@ -671,7 +448,7 @@
             // Add the new setting to mPackages
             mPm.mPackages.put(pkg.getPackageName(), pkg);
             if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
-                mPm.mApexManager.registerApkInApex(pkg);
+                mApexManager.registerApkInApex(pkg);
             }
 
             // Add the package's KeySets to the global KeySetManagerService
@@ -722,27 +499,6 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
-    /**
-     * If the database version for this type of package (internal storage or
-     * external storage) is less than the version where package signatures
-     * were updated, return true.
-     */
-    public boolean isCompatSignatureUpdateNeeded(AndroidPackage pkg) {
-        return isCompatSignatureUpdateNeeded(mPm.getSettingsVersionForPackage(pkg));
-    }
-
-    public static boolean isCompatSignatureUpdateNeeded(Settings.VersionInfo ver) {
-        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_END_ENTITY;
-    }
-
-    public boolean isRecoverSignatureUpdateNeeded(AndroidPackage pkg) {
-        return isRecoverSignatureUpdateNeeded(mPm.getSettingsVersionForPackage(pkg));
-    }
-
-    public static boolean isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver) {
-        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
-    }
-
     public int installExistingPackageAsUser(@Nullable String packageName, @UserIdInt int userId,
             @PackageManager.InstallFlags int installFlags,
             @PackageManager.InstallReason int installReason,
@@ -755,9 +511,9 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        if (mPm.mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES)
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED
-                && mPm.mContext.checkCallingOrSelfPermission(
+                && mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.INSTALL_EXISTING_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Neither user " + callingUid + " nor current process has "
@@ -810,7 +566,8 @@
                     // upgrade app from instant to full; we don't allow app downgrade
                     installed = true;
                 }
-                mPm.setInstantAppForUser(mInjector, pkgSetting, userId, instantApp, fullApp);
+                ScanPackageUtils.setInstantAppForUser(mPm.mInjector, pkgSetting, userId, instantApp,
+                        fullApp);
             }
 
             if (installed) {
@@ -847,7 +604,7 @@
                             mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
                                     userId);
                             if (intentSender != null) {
-                                onRestoreComplete(res.mReturnCode, mPm.mContext, intentSender);
+                                onRestoreComplete(res.mReturnCode, mContext, intentSender);
                             }
                         });
                 restoreAndPostInstall(userId, res, postInstallData);
@@ -933,9 +690,7 @@
      * Returns whether the restore successfully completed.
      */
     private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
-        IBackupManager bm = IBackupManager.Stub.asInterface(
-                ServiceManager.getService(Context.BACKUP_SERVICE));
-        if (bm != null) {
+        if (mIBackupManager != null) {
             // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
             // in the BackupManager. USER_ALL is used in compatibility tests.
             if (userId == UserHandle.USER_ALL) {
@@ -946,8 +701,8 @@
             }
             Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
             try {
-                if (bm.isUserReadyForBackup(userId)) {
-                    bm.restoreAtInstallForUser(
+                if (mIBackupManager.isUserReadyForBackup(userId)) {
+                    mIBackupManager.restoreAtInstallForUser(
                             userId, res.mPkg.getPackageName(), token);
                 } else {
                     Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
@@ -973,8 +728,6 @@
      */
     private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res,
             PostInstallData data) {
-        RollbackManagerInternal rm = mInjector.getLocalService(RollbackManagerInternal.class);
-
         final String packageName = res.mPkg.getPackageName();
         final int[] allUsers = mPm.mUserManager.getUserIds();
         final int[] installedUsers;
@@ -1000,8 +753,8 @@
 
         if (ps != null && doSnapshotOrRestore) {
             final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
-            rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
-                    appId, ceDataInode, seInfo, token);
+            mRollbackManager.snapshotAndRestoreUserData(packageName,
+                    UserHandle.toUserHandles(installedUsers), appId, ceDataInode, seInfo, token);
             return true;
         }
         return false;
@@ -1093,7 +846,7 @@
                                 + " got: " + apexes.length);
             }
             try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
-                mPm.mApexManager.installPackage(apexes[0], packageParser);
+                mApexManager.installPackage(apexes[0], packageParser);
             }
         } catch (PackageManagerException e) {
             request.mInstallResult.setError("APEX installation failed", e);
@@ -1170,7 +923,7 @@
                 installResults.put(packageName, request.mInstallResult);
                 installArgs.put(packageName, request.mArgs);
                 try {
-                    final ScanResult result = mScanPackageHelper.scanPackageTracedLI(
+                    final ScanResult result = scanPackageTracedLI(
                             prepareResult.mPackageToScan, prepareResult.mParseFlags,
                             prepareResult.mScanFlags, System.currentTimeMillis(),
                             request.mArgs.mUser, request.mArgs.mAbiOverride);
@@ -1183,6 +936,9 @@
                                         + " in multi-package install request.");
                         return;
                     }
+                    if (result.needsNewAppId()) {
+                        request.mInstallResult.mRemovedInfo.mAppIdChanging = true;
+                    }
                     createdAppId.put(packageName, optimisticallyRegisterAppId(result));
                     versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
                             mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
@@ -1213,7 +969,7 @@
                 Map<String, ReconciledPackage> reconciledPackages;
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
-                    reconciledPackages = reconcilePackagesLocked(
+                    reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                             reconcileRequest, mPm.mSettings.getKeySetManagerService(),
                             mPm.mInjector);
                 } catch (ReconcileFailure e) {
@@ -1256,7 +1012,7 @@
                             .buildVerificationRootHashString(baseCodePath, splitCodePaths);
                     VerificationUtils.broadcastPackageVerified(verificationId, originUri,
                             PackageManager.VERIFICATION_ALLOW, rootHashString,
-                            args.mDataLoaderType, args.getUser(), mPm.mContext);
+                            args.mDataLoaderType, args.getUser(), mContext);
                 }
             } else {
                 for (ScanResult result : preparedScans.values()) {
@@ -1472,9 +1228,11 @@
                 } else {
                     try {
                         final boolean compareCompat =
-                                isCompatSignatureUpdateNeeded(parsedPackage);
+                                ReconcilePackageUtils.isCompatSignatureUpdateNeeded(
+                                        mPm.getSettingsVersionForPackage(parsedPackage));
                         final boolean compareRecover =
-                                isRecoverSignatureUpdateNeeded(parsedPackage);
+                                ReconcilePackageUtils.isRecoverSignatureUpdateNeeded(
+                                        mPm.getSettingsVersionForPackage(parsedPackage));
                         // We don't care about disabledPkgSetting on install for now.
                         final boolean compatMatch = verifySignatures(signatureCheckPs, null,
                                 parsedPackage.getSigningDetails(), compareCompat, compareRecover,
@@ -1676,9 +1434,9 @@
                 final String abiOverride = deriveAbiOverride(args.mAbiOverride);
                 boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
                 final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
-                        derivedAbi = mPm.mInjector.getAbiHelper().derivePackageAbi(parsedPackage,
+                        derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
                         isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
-                        abiOverride, mPm.mAppLib32InstallDir);
+                        abiOverride, ScanPackageUtils.getAppLib32InstallDir());
                 derivedAbi.first.applyTo(parsedPackage);
                 derivedAbi.second.applyTo(parsedPackage);
             } catch (PackageManagerException pme) {
@@ -2376,8 +2134,8 @@
                 // that can be used for all the packages.
                 final String codePath = ps.getPathString();
                 if (IncrementalManager.isIncrementalPath(codePath)
-                        && mPm.mIncrementalManager != null) {
-                    mPm.mIncrementalManager.registerLoadingProgressCallback(codePath,
+                        && mIncrementalManager != null) {
+                    mIncrementalManager.registerLoadingProgressCallback(codePath,
                             new IncrementalProgressListener(ps.getPackageName(), mPm));
                 }
 
@@ -2438,17 +2196,16 @@
      */
     private void executePostCommitSteps(CommitRequest commitRequest) {
         final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
-        final AppDataHelper appDataHelper = new AppDataHelper(mPm);
         for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) {
             final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags
                     & SCAN_AS_INSTANT_APP) != 0);
             final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg();
             final String packageName = pkg.getPackageName();
             final String codePath = pkg.getPath();
-            final boolean onIncremental = mPm.mIncrementalManager != null
+            final boolean onIncremental = mIncrementalManager != null
                     && isIncrementalPath(codePath);
             if (onIncremental) {
-                IncrementalStorage storage = mPm.mIncrementalManager.openStorage(codePath);
+                IncrementalStorage storage = mIncrementalManager.openStorage(codePath);
                 if (storage == null) {
                     throw new IllegalArgumentException(
                             "Install: null storage for incremental package " + packageName);
@@ -2460,28 +2217,28 @@
                 // Only set previousAppId if the app is migrating out of shared UID
                 previousAppId = reconciledPkg.mScanResult.mPreviousAppId;
             }
-            appDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId);
+            mAppDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId);
             if (reconciledPkg.mPrepareResult.mClearCodeCache) {
-                appDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
+                mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
                         FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                                 | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
             }
             if (reconciledPkg.mPrepareResult.mReplace) {
-                mPm.getDexManager().notifyPackageUpdated(pkg.getPackageName(),
+                mDexManager.notifyPackageUpdated(pkg.getPackageName(),
                         pkg.getBaseApkPath(), pkg.getSplitCodePaths());
             }
 
             // Prepare the application profiles for the new code paths.
             // This needs to be done before invoking dexopt so that any install-time profile
             // can be used for optimizations.
-            mPm.mArtManagerService.prepareAppProfiles(
+            mArtManagerService.prepareAppProfiles(
                     pkg,
                     mPm.resolveUserIds(reconciledPkg.mInstallArgs.mUser.getIdentifier()),
                     /* updateReferenceProfileContent= */ true);
 
             // Compute the compilation reason from the installation scenario.
             final int compilationReason =
-                    mPm.getDexManager().getCompilationReasonForInstallScenario(
+                    mDexManager.getCompilationReasonForInstallScenario(
                             reconciledPkg.mInstallArgs.mInstallScenario);
 
             // Construct the DexoptOptions early to see if we should skip running dexopt.
@@ -2529,7 +2286,7 @@
             //       path moved to SCENARIO_FAST.
             final boolean performDexopt =
                     (!instantApp || android.provider.Settings.Global.getInt(
-                            mPm.mContext.getContentResolver(),
+                            mContext.getContentResolver(),
                             android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
                             && !pkg.isDebuggable()
                             && (!onIncremental)
@@ -2539,7 +2296,7 @@
                 // Compile the layout resources.
                 if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
-                    mPm.mViewCompiler.compileLayouts(pkg);
+                    mViewCompiler.compileLayouts(pkg);
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
 
@@ -2564,10 +2321,10 @@
 
                 realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
 
-                mPm.mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
+                mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
                         null /* instructionSets */,
                         mPm.getOrCreateCompilerPackageStats(pkg),
-                        mPm.getDexManager().getPackageUseInfoOrDefault(packageName),
+                        mDexManager.getPackageUseInfoOrDefault(packageName),
                         dexoptOptions);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
@@ -2580,7 +2337,8 @@
 
             notifyPackageChangeObserversOnUpdate(reconciledPkg);
         }
-        waitForNativeBinariesExtraction(incrementalStorages);
+        PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
+                incrementalStorages);
     }
 
     private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) {
@@ -2599,27 +2357,6 @@
         mPm.notifyPackageChangeObservers(pkgChangeEvent);
     }
 
-    private static void waitForNativeBinariesExtraction(
-            ArraySet<IncrementalStorage> incrementalStorages) {
-        if (incrementalStorages.isEmpty()) {
-            return;
-        }
-        try {
-            // Native library extraction may take very long time: each page could potentially
-            // wait for either 10s or 100ms (adb vs non-adb data loader), and that easily adds
-            // up to a full watchdog timeout of 1 min, killing the system after that. It doesn't
-            // make much sense as blocking here doesn't lock up the framework, but only blocks
-            // the installation session and the following ones.
-            Watchdog.getInstance().pauseWatchingCurrentThread("native_lib_extract");
-            for (int i = 0; i < incrementalStorages.size(); ++i) {
-                IncrementalStorage storage = incrementalStorages.valueAtUnchecked(i);
-                storage.waitForNativeBinariesExtraction();
-            }
-        } finally {
-            Watchdog.getInstance().resumeWatchingCurrentThread("native_lib_extract");
-        }
-    }
-
     public int installLocationPolicy(PackageInfoLite pkgLite, int installFlags) {
         String packageName = pkgLite.packageName;
         int installLocation = pkgLite.installLocation;
@@ -2705,7 +2442,7 @@
                 if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                         dataOwnerPkg.isDebuggable())) {
                     try {
-                        checkDowngrade(dataOwnerPkg, pkgLite);
+                        PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
                     } catch (PackageManagerException e) {
                         String errorMsg = "Downgrade detected: " + e.getMessage();
                         Slog.w(TAG, errorMsg);
@@ -2722,7 +2459,7 @@
             long requiredInstalledVersionCode, int installFlags) {
         String packageName = pkgLite.packageName;
 
-        final PackageInfo activePackage = mPm.mApexManager.getPackageInfo(packageName,
+        final PackageInfo activePackage = mApexManager.getPackageInfo(packageName,
                 ApexManager.MATCH_ACTIVE_PACKAGE);
         if (activePackage == null) {
             String errorMsg = "Attempting to install new APEX package " + packageName;
@@ -2755,41 +2492,6 @@
         return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
     }
 
-    /**
-     * Check and throw if the given before/after packages would be considered a
-     * downgrade.
-     */
-    private static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
-            throws PackageManagerException {
-        if (after.getLongVersionCode() < before.getLongVersionCode()) {
-            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                    "Update version code " + after.versionCode + " is older than current "
-                            + before.getLongVersionCode());
-        } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
-            if (after.baseRevisionCode < before.getBaseRevisionCode()) {
-                throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                        "Update base revision code " + after.baseRevisionCode
-                                + " is older than current " + before.getBaseRevisionCode());
-            }
-
-            if (!ArrayUtils.isEmpty(after.splitNames)) {
-                for (int i = 0; i < after.splitNames.length; i++) {
-                    final String splitName = after.splitNames[i];
-                    final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
-                    if (j != -1) {
-                        if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
-                            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                                    "Update split " + splitName + " revision code "
-                                            + after.splitRevisionCodes[i]
-                                            + " is older than current "
-                                            + before.getSplitRevisionCodes()[j]);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
     int getUidForVerifier(VerifierInfo verifierInfo) {
         synchronized (mPm.mLock) {
             final AndroidPackage pkg = mPm.mPackages.get(verifierInfo.packageName);
@@ -2883,6 +2585,8 @@
         final int dataLoaderType = installArgs.mDataLoaderType;
         final boolean succeeded = res.mReturnCode == PackageManager.INSTALL_SUCCEEDED;
         final boolean update = res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null;
+        final int previousAppId = (res.mRemovedInfo != null && res.mRemovedInfo.mAppIdChanging)
+                ? res.mRemovedInfo.mUid : Process.INVALID_UID;
         final String packageName = res.mName;
         final PackageStateInternal pkgSetting =
                 succeeded ? mPm.getPackageStateInternal(packageName) : null;
@@ -2979,9 +2683,12 @@
                         dataLoaderType);
 
                 // Send added for users that don't see the package for the first time
-                Bundle extras = new Bundle(1);
+                Bundle extras = new Bundle();
                 extras.putInt(Intent.EXTRA_UID, res.mUid);
-                if (update) {
+                if (previousAppId != Process.INVALID_UID) {
+                    extras.putBoolean(Intent.EXTRA_UID_CHANGING, true);
+                    extras.putInt(Intent.EXTRA_PREVIOUS_UID, previousAppId);
+                } else if (update) {
                     extras.putBoolean(Intent.EXTRA_REPLACING, true);
                 }
                 extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
@@ -3024,22 +2731,27 @@
 
                 // Send replaced for users that don't see the package for the first time
                 if (update) {
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                            packageName, extras, 0 /*flags*/,
-                            null /*targetPackage*/, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList,
-                            null);
-                    if (installerPackageName != null) {
-                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                                extras, 0 /*flags*/,
-                                installerPackageName, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);
-                    }
-                    if (notifyVerifier) {
-                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                                extras, 0 /*flags*/,
-                                mPm.mRequiredVerifierPackage, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);
+                    // Only send PACKAGE_REPLACED if appId has not changed
+                    if (previousAppId == Process.INVALID_UID) {
+                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+                                packageName, extras, 0 /*flags*/,
+                                null /*targetPackage*/, null /*finishedReceiver*/,
+                                updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList,
+                                null);
+                        if (installerPackageName != null) {
+                            mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+                                    extras, 0 /*flags*/,
+                                    installerPackageName, null /*finishedReceiver*/,
+                                    updateUserIds, instantUserIds, null /*broadcastAllowList*/,
+                                    null);
+                        }
+                        if (notifyVerifier) {
+                            mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+                                    extras, 0 /*flags*/,
+                                    mPm.mRequiredVerifierPackage, null /*finishedReceiver*/,
+                                    updateUserIds, instantUserIds, null /*broadcastAllowList*/,
+                                    null);
+                        }
                     }
                     mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                             null /*package*/, null /*extras*/, 0 /*flags*/,
@@ -3062,10 +2774,8 @@
                 // Send broadcast package appeared if external for all users
                 if (res.mPkg.isExternalStorage()) {
                     if (!update) {
-                        final StorageManager storage = mPm.mInjector.getSystemService(
-                                StorageManager.class);
                         VolumeInfo volume =
-                                storage.findVolumeByUuid(
+                                mStorageManager.findVolumeByUuid(
                                         StorageManager.convert(
                                                 res.mPkg.getVolumeUuid()).toString());
                         int packageExternalStorageType =
@@ -3147,7 +2857,7 @@
                 // There's a race currently where some install events may interleave with an
                 // uninstall. This can lead to package info being null (b/36642664).
                 if (info != null) {
-                    mPm.getDexManager().notifyPackageInstalled(info, userId);
+                    mDexManager.notifyPackageInstalled(info, userId);
                 }
             }
         }
@@ -3175,7 +2885,7 @@
      * @return the current "allow unknown sources" setting
      */
     private int getUnknownSourcesSettings() {
-        return android.provider.Settings.Secure.getIntForUser(mPm.mContext.getContentResolver(),
+        return android.provider.Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS,
                 -1, UserHandle.USER_SYSTEM);
     }
@@ -3304,7 +3014,7 @@
             mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
                     FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                             | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-            mPm.getDexManager().notifyPackageUpdated(pkg.getPackageName(),
+            mDexManager.notifyPackageUpdated(pkg.getPackageName(),
                     pkg.getBaseApkPath(), pkg.getSplitCodePaths());
         }
         return true;
@@ -3358,7 +3068,7 @@
                         packageName);
         int ret = PackageManagerServiceUtils.decompressFiles(codePath, dstCodePath, packageName);
         if (ret == PackageManager.INSTALL_SUCCEEDED) {
-            ret = extractNativeBinaries(dstCodePath, packageName);
+            ret = PackageManagerServiceUtils.extractNativeBinaries(dstCodePath, packageName);
         }
         if (ret == PackageManager.INSTALL_SUCCEEDED) {
             // NOTE: During boot, we have to delay releasing cblocks for no other reason than
@@ -3371,7 +3081,7 @@
                 }
                 mPm.mReleaseOnSystemReady.add(dstCodePath);
             } else {
-                final ContentResolver resolver = mPm.mContext.getContentResolver();
+                final ContentResolver resolver = mContext.getContentResolver();
                 F2fsUtils.releaseCompressedBlocks(resolver, dstCodePath);
             }
         } else {
@@ -3385,22 +3095,6 @@
         return dstCodePath;
     }
 
-    private static int extractNativeBinaries(File dstCodePath, String packageName) {
-        final File libraryRoot = new File(dstCodePath, LIB_DIR_NAME);
-        NativeLibraryHelper.Handle handle = null;
-        try {
-            handle = NativeLibraryHelper.Handle.create(dstCodePath);
-            return NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                    null /*abiOverride*/, false /*isIncremental*/);
-        } catch (IOException e) {
-            logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
-                    + "; pkg: " + packageName);
-            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-        } finally {
-            IoUtils.closeQuietly(handle);
-        }
-    }
-
     /**
      * Tries to restore the disabled system package after an update has been deleted.
      */
@@ -3418,7 +3112,7 @@
             // Reinstate the old system package
             mPm.mSettings.enableSystemPackageLPw(disabledPs.getPkg().getPackageName());
             // Remove any native libraries from the upgraded package.
-            removeNativeBinariesLI(deletedPs);
+            PackageManagerServiceUtils.removeNativeBinariesLI(deletedPs);
 
             // Install the system package
             if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
@@ -3461,12 +3155,6 @@
         }
     }
 
-    private static void removeNativeBinariesLI(PackageSetting ps) {
-        if (ps != null) {
-            NativeLibraryHelper.removeNativeBinariesLI(ps.getLegacyNativeLibraryPath());
-        }
-    }
-
     /**
      * Installs a package that's already on the system partition.
      */
@@ -3737,7 +3425,7 @@
             }
 
             if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {
-                mPm.mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg);
+                mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg);
             }
 
             // Delete invalid userdata apps
@@ -3820,7 +3508,7 @@
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
         final ParsedPackage parsedPackage;
-        try (PackageParser2 pp = mInjector.getScanningPackageParser()) {
+        try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) {
             parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -3853,7 +3541,7 @@
             @PackageManagerService.ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user) throws PackageManagerException {
 
-        final Pair<ScanResult, Boolean> scanResultPair = mScanPackageHelper.scanSystemPackageLI(
+        final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI(
                 parsedPackage, parseFlags, scanFlags, currentTime, user);
         final ScanResult scanResult = scanResultPair.first;
         boolean shouldHideSystemApp = scanResultPair.second;
@@ -3863,7 +3551,7 @@
                 try {
                     final String pkgName = scanResult.mPkgSetting.getPackageName();
                     final Map<String, ReconciledPackage> reconcileResult =
-                            reconcilePackagesLocked(
+                            ReconcilePackageUtils.reconcilePackages(
                                     new ReconcileRequest(
                                             Collections.singletonMap(pkgName, scanResult),
                                             mPm.mSharedLibraries,
@@ -3875,7 +3563,7 @@
                                             Collections.singletonMap(pkgName,
                                                     mPm.getSharedLibLatestVersionSetting(
                                                             scanResult))),
-                                    mPm.mSettings.getKeySetManagerService(), mInjector);
+                                    mPm.mSettings.getKeySetManagerService(), mPm.mInjector);
                     appIdCreated = optimisticallyRegisterAppId(scanResult);
                     commitReconciledScanResultLocked(
                             reconcileResult.get(pkgName), mPm.mUserManager.getUserIds());
@@ -3893,10 +3581,10 @@
                 mPm.mSettings.disableSystemPackageLPw(parsedPackage.getPackageName(), true);
             }
         }
-        if (mPm.mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
+        if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
             if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) {
                 // Continue monitoring loading progress of active incremental packages
-                mPm.mIncrementalManager.registerLoadingProgressCallback(parsedPackage.getPath(),
+                mIncrementalManager.registerLoadingProgressCallback(parsedPackage.getPath(),
                         new IncrementalProgressListener(parsedPackage.getPackageName(), mPm));
             }
         }
@@ -3934,5 +3622,743 @@
         }
     }
 
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private ScanResult scanPackageTracedLI(ParsedPackage parsedPackage,
+            final @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
+        try {
+            return scanPackageNewLI(parsedPackage, parseFlags, scanFlags, currentTime, user,
+                    cpuAbiOverride);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
 
+    private ScanRequest prepareInitialScanRequest(@NonNull ParsedPackage parsedPackage,
+            @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags,
+            @Nullable UserHandle user, String cpuAbiOverride)
+            throws PackageManagerException {
+        final AndroidPackage platformPackage;
+        final String realPkgName;
+        final PackageSetting disabledPkgSetting;
+        final PackageSetting installedPkgSetting;
+        final PackageSetting originalPkgSetting;
+        final SharedUserSetting sharedUserSetting;
+
+        synchronized (mPm.mLock) {
+            platformPackage = mPm.getPlatformPackage();
+            final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
+                    AndroidPackageUtils.getRealPackageOrNull(parsedPackage));
+            realPkgName = ScanPackageUtils.getRealPackageName(parsedPackage, renamedPkgName);
+            if (realPkgName != null) {
+                ScanPackageUtils.ensurePackageRenamed(parsedPackage, renamedPkgName);
+            }
+            originalPkgSetting = getOriginalPackageLocked(parsedPackage, renamedPkgName);
+            installedPkgSetting = mPm.mSettings.getPackageLPr(parsedPackage.getPackageName());
+            if (mPm.mTransferredPackages.contains(parsedPackage.getPackageName())) {
+                Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+                        + " was transferred to another, but its .apk remains");
+            }
+            disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(
+                    parsedPackage.getPackageName());
+            sharedUserSetting = (parsedPackage.getSharedUserId() != null)
+                    ? mPm.mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
+                    0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
+                    : null;
+            if (DEBUG_PACKAGE_SCANNING
+                    && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0
+                    && sharedUserSetting != null) {
+                Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
+                        + " (uid=" + sharedUserSetting.userId + "):"
+                        + " packages=" + sharedUserSetting.packages);
+            }
+        }
+
+        final boolean isPlatformPackage = platformPackage != null
+                && platformPackage.getPackageName().equals(parsedPackage.getPackageName());
+
+        return new ScanRequest(parsedPackage, sharedUserSetting,
+                installedPkgSetting == null ? null : installedPkgSetting.getPkg() /* oldPkg */,
+                installedPkgSetting /* packageSetting */,
+                disabledPkgSetting /* disabledPackageSetting */,
+                originalPkgSetting  /* originalPkgSetting */,
+                realPkgName, parseFlags, scanFlags, isPlatformPackage, user, cpuAbiOverride);
+    }
+
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
+            final @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
+
+        final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags,
+                scanFlags, user, cpuAbiOverride);
+        final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting;
+        final PackageSetting disabledPkgSetting = initialScanRequest.mDisabledPkgSetting;
+
+        boolean isUpdatedSystemApp;
+        if (installedPkgSetting != null) {
+            isUpdatedSystemApp = installedPkgSetting.getPkgState().isUpdatedSystemApp();
+        } else {
+            isUpdatedSystemApp = disabledPkgSetting != null;
+        }
+
+        final int newScanFlags = adjustScanFlags(scanFlags, installedPkgSetting, disabledPkgSetting,
+                user, parsedPackage);
+        ScanPackageUtils.applyPolicy(parsedPackage, newScanFlags,
+                mPm.getPlatformPackage(), isUpdatedSystemApp);
+
+        synchronized (mPm.mLock) {
+            assertPackageIsValid(parsedPackage, parseFlags, newScanFlags);
+            final ScanRequest request = new ScanRequest(parsedPackage,
+                    initialScanRequest.mSharedUserSetting,
+                    initialScanRequest.mOldPkg, installedPkgSetting, disabledPkgSetting,
+                    initialScanRequest.mOriginalPkgSetting, initialScanRequest.mRealPkgName,
+                    parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user,
+                    cpuAbiOverride);
+            return ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest,
+                    currentTime);
+        }
+    }
+
+    private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
+            @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user) throws PackageManagerException {
+        final boolean scanSystemPartition =
+                (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
+        final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags,
+                scanFlags, user, null);
+        final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting;
+        final PackageSetting originalPkgSetting = initialScanRequest.mOriginalPkgSetting;
+        final PackageSetting pkgSetting =
+                originalPkgSetting == null ? installedPkgSetting : originalPkgSetting;
+        final boolean pkgAlreadyExists = pkgSetting != null;
+        final String disabledPkgName = pkgAlreadyExists
+                ? pkgSetting.getPackageName() : parsedPackage.getPackageName();
+        final boolean isSystemPkgUpdated;
+        final boolean isUpgrade;
+        synchronized (mPm.mLock) {
+            isUpgrade = mPm.isDeviceUpgrading();
+            if (scanSystemPartition && !pkgAlreadyExists
+                    && mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName) != null) {
+                // The updated-package data for /system apk remains inconsistently
+                // after the package data for /data apk is lost accidentally.
+                // To recover it, enable /system apk and install it as non-updated system app.
+                Slog.w(TAG, "Inconsistent package setting of updated system app for "
+                        + disabledPkgName + ". To recover it, enable the system app "
+                        + "and install it as non-updated system app.");
+                mPm.mSettings.removeDisabledSystemPackageLPw(disabledPkgName);
+            }
+            final PackageSetting disabledPkgSetting =
+                    mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName);
+            isSystemPkgUpdated = disabledPkgSetting != null;
+
+            if (DEBUG_INSTALL && isSystemPkgUpdated) {
+                Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
+            }
+
+            if (scanSystemPartition && isSystemPkgUpdated) {
+                // we're updating the disabled package, so, scan it as the package setting
+                final ScanRequest request = new ScanRequest(parsedPackage,
+                        initialScanRequest.mSharedUserSetting,
+                        null, disabledPkgSetting /* pkgSetting */,
+                        null /* disabledPkgSetting */, null /* originalPkgSetting */,
+                        null, parseFlags, scanFlags,
+                        initialScanRequest.mIsPlatformPackage, user, null);
+                ScanPackageUtils.applyPolicy(parsedPackage, scanFlags,
+                        mPm.getPlatformPackage(), true);
+                final ScanResult scanResult =
+                        ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector,
+                                mPm.mFactoryTest, -1L);
+                if (scanResult.mExistingSettingCopied
+                        && scanResult.mRequest.mPkgSetting != null) {
+                    scanResult.mRequest.mPkgSetting.updateFrom(scanResult.mPkgSetting);
+                }
+            }
+        } // End of mLock
+
+        final boolean newPkgChangedPaths = pkgAlreadyExists
+                && !pkgSetting.getPathString().equals(parsedPackage.getPath());
+        final boolean newPkgVersionGreater = pkgAlreadyExists
+                && parsedPackage.getLongVersionCode() > pkgSetting.getVersionCode();
+        final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
+                && newPkgChangedPaths && newPkgVersionGreater;
+        if (isSystemPkgBetter) {
+            // The version of the application on /system is greater than the version on
+            // /data. Switch back to the application on /system.
+            // It's safe to assume the application on /system will correctly scan. If not,
+            // there won't be a working copy of the application.
+            synchronized (mPm.mLock) {
+                // just remove the loaded entries from package lists
+                mPm.mPackages.remove(pkgSetting.getPackageName());
+            }
+
+            logCriticalInfo(Log.WARN,
+                    "System package updated;"
+                            + " name: " + pkgSetting.getPackageName()
+                            + "; " + pkgSetting.getVersionCode() + " --> "
+                            + parsedPackage.getLongVersionCode()
+                            + "; " + pkgSetting.getPathString()
+                            + " --> " + parsedPackage.getPath());
+
+            final InstallArgs args = new FileInstallArgs(
+                    pkgSetting.getPathString(), getAppDexInstructionSets(
+                    pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()), mPm);
+            args.cleanUpResourcesLI();
+            synchronized (mPm.mLock) {
+                mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
+            }
+        }
+
+        // The version of the application on the /system partition is less than or
+        // equal to the version on the /data partition. Throw an exception and use
+        // the application already installed on the /data partition.
+        if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
+            // In the case of a skipped package, commitReconciledScanResultLocked is not called to
+            // add the object to the "live" data structures, so this is the final mutation step
+            // for the package. Which means it needs to be finalized here to cache derived fields.
+            // This is relevant for cases where the disabled system package is used for flags or
+            // other metadata.
+            parsedPackage.hideAsFinal();
+            throw new PackageManagerException(Log.WARN, "Package " + parsedPackage.getPackageName()
+                    + " at " + parsedPackage.getPath() + " ignored: updated version "
+                    + (pkgAlreadyExists ? String.valueOf(pkgSetting.getVersionCode()) : "unknown")
+                    + " better than this " + parsedPackage.getLongVersionCode());
+        }
+
+        // Verify certificates against what was last scanned. Force re-collecting certificate in two
+        // special cases:
+        // 1) when scanning system, force re-collect only if system is upgrading.
+        // 2) when scanning /data, force re-collect only if the app is privileged (updated from
+        // preinstall, or treated as privileged, e.g. due to shared user ID).
+        final boolean forceCollect = scanSystemPartition ? isUpgrade
+                : PackageManagerServiceUtils.isApkVerificationForced(pkgSetting);
+        if (DEBUG_VERIFY && forceCollect) {
+            Slog.d(TAG, "Force collect certificate of " + parsedPackage.getPackageName());
+        }
+
+        // Full APK verification can be skipped during certificate collection, only if the file is
+        // in verified partition, or can be verified on access (when apk verity is enabled). In both
+        // cases, only data in Signing Block is verified instead of the whole file.
+        final boolean skipVerify = scanSystemPartition
+                || (forceCollect && canSkipForcedPackageVerification(parsedPackage));
+        ScanPackageUtils.collectCertificatesLI(pkgSetting, parsedPackage,
+                mPm.getSettingsVersionForPackage(parsedPackage), forceCollect, skipVerify,
+                mPm.isPreNMR1Upgrade());
+
+        // Reset profile if the application version is changed
+        maybeClearProfilesForUpgradesLI(pkgSetting, parsedPackage);
+
+        /*
+         * A new system app appeared, but we already had a non-system one of the
+         * same name installed earlier.
+         */
+        boolean shouldHideSystemApp = false;
+        // A new application appeared on /system, but, we already have a copy of
+        // the application installed on /data.
+        if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
+                && !pkgSetting.isSystem()) {
+
+            if (!parsedPackage.getSigningDetails()
+                    .checkCapability(pkgSetting.getSigningDetails(),
+                            SigningDetails.CertCapabilities.INSTALLED_DATA)
+                    && !pkgSetting.getSigningDetails().checkCapability(
+                    parsedPackage.getSigningDetails(),
+                    SigningDetails.CertCapabilities.ROLLBACK)) {
+                logCriticalInfo(Log.WARN,
+                        "System package signature mismatch;"
+                                + " name: " + pkgSetting.getPackageName());
+                try (@SuppressWarnings("unused") PackageFreezer freezer = mPm.freezePackage(
+                        parsedPackage.getPackageName(),
+                        "scanPackageInternalLI")) {
+                    DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
+                    deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
+                            mPm.mUserManager.getUserIds(), 0, null, false);
+                }
+            } else if (newPkgVersionGreater) {
+                // 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.
+                logCriticalInfo(Log.WARN,
+                        "System package enabled;"
+                                + " name: " + pkgSetting.getPackageName()
+                                + "; " + pkgSetting.getVersionCode() + " --> "
+                                + parsedPackage.getLongVersionCode()
+                                + "; " + pkgSetting.getPathString() + " --> "
+                                + parsedPackage.getPath());
+                InstallArgs args = new FileInstallArgs(
+                        pkgSetting.getPathString(), getAppDexInstructionSets(
+                        pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()),
+                        mPm);
+                synchronized (mPm.mInstallLock) {
+                    args.cleanUpResourcesLI();
+                }
+            } else {
+                // The application on /system is older than the application on /data. Hide
+                // the application on /system and the version on /data will be scanned later
+                // and re-added like an update.
+                shouldHideSystemApp = true;
+                logCriticalInfo(Log.INFO,
+                        "System package disabled;"
+                                + " name: " + pkgSetting.getPackageName()
+                                + "; old: " + pkgSetting.getPathString() + " @ "
+                                + pkgSetting.getVersionCode()
+                                + "; new: " + parsedPackage.getPath() + " @ "
+                                + parsedPackage.getPath());
+            }
+        }
+
+        final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
+                scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null);
+        return new Pair<>(scanResult, shouldHideSystemApp);
+    }
+
+    /**
+     * Returns if forced apk verification can be skipped for the whole package, including splits.
+     */
+    private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
+        final String packageName = pkg.getPackageName();
+        if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
+            return false;
+        }
+        // TODO: Allow base and splits to be verified individually.
+        String[] splitCodePaths = pkg.getSplitCodePaths();
+        if (!ArrayUtils.isEmpty(splitCodePaths)) {
+            for (int i = 0; i < splitCodePaths.length; i++) {
+                if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns if forced apk verification can be skipped, depending on current FSVerity setup and
+     * whether the apk contains signed root hash.  Note that the signer's certificate still needs to
+     * match one in a trusted source, and should be done separately.
+     */
+    private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
+        if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
+            return VerityUtils.hasFsverity(apkPath);
+        }
+
+        try {
+            final byte[] rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
+            if (rootHashObserved == null) {
+                return false;  // APK does not contain Merkle tree root hash.
+            }
+            synchronized (mPm.mInstallLock) {
+                // Returns whether the observed root hash matches what kernel has.
+                mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
+                        rootHashObserved);
+                return true;
+            }
+        } catch (Installer.InstallerException | IOException | DigestException
+                | NoSuchAlgorithmException e) {
+            Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Clear the package profile if this was an upgrade and the package
+     * version was updated.
+     */
+    private void maybeClearProfilesForUpgradesLI(
+            @Nullable PackageSetting originalPkgSetting,
+            @NonNull AndroidPackage pkg) {
+        if (originalPkgSetting == null || !mPm.isDeviceUpgrading()) {
+            return;
+        }
+        if (originalPkgSetting.getVersionCode() == pkg.getLongVersionCode()) {
+            return;
+        }
+
+        mAppDataHelper.clearAppProfilesLIF(pkg);
+        if (DEBUG_INSTALL) {
+            Slog.d(TAG, originalPkgSetting.getPackageName()
+                    + " clear profile due to version change "
+                    + originalPkgSetting.getVersionCode() + " != "
+                    + pkg.getLongVersionCode());
+        }
+    }
+
+    /**
+     * Returns the original package setting.
+     * <p>A package can migrate its name during an update. In this scenario, a package
+     * designates a set of names that it considers as one of its original names.
+     * <p>An original package must be signed identically and it must have the same
+     * shared user [if any].
+     */
+    @GuardedBy("mPm.mLock")
+    @Nullable
+    private PackageSetting getOriginalPackageLocked(@NonNull AndroidPackage pkg,
+            @Nullable String renamedPkgName) {
+        if (ScanPackageUtils.isPackageRenamed(pkg, renamedPkgName)) {
+            return null;
+        }
+        for (int i = ArrayUtils.size(pkg.getOriginalPackages()) - 1; i >= 0; --i) {
+            final PackageSetting originalPs =
+                    mPm.mSettings.getPackageLPr(pkg.getOriginalPackages().get(i));
+            if (originalPs != null) {
+                // the package is already installed under its original name...
+                // but, should we use it?
+                if (!verifyPackageUpdateLPr(originalPs, pkg)) {
+                    // the new package is incompatible with the original
+                    continue;
+                } else if (originalPs.getSharedUser() != null) {
+                    if (!originalPs.getSharedUser().name.equals(pkg.getSharedUserId())) {
+                        // the shared user id is incompatible with the original
+                        Slog.w(TAG, "Unable to migrate data from " + originalPs.getPackageName()
+                                + " to " + pkg.getPackageName() + ": old uid "
+                                + originalPs.getSharedUser().name
+                                + " differs from " + pkg.getSharedUserId());
+                        continue;
+                    }
+                    // TODO: Add case when shared user id is added [b/28144775]
+                } else {
+                    if (DEBUG_UPGRADE) {
+                        Log.v(TAG, "Renaming new package "
+                                + pkg.getPackageName() + " to old name "
+                                + originalPs.getPackageName());
+                    }
+                }
+                return originalPs;
+            }
+        }
+        return null;
+    }
+
+    @GuardedBy("mPm.mLock")
+    private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, AndroidPackage newPkg) {
+        if ((oldPkg.getFlags() & ApplicationInfo.FLAG_SYSTEM) == 0) {
+            Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName()
+                    + " to " + newPkg.getPackageName()
+                    + ": old package not in system partition");
+            return false;
+        } else if (mPm.mPackages.get(oldPkg.getPackageName()) != null) {
+            Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName()
+                    + " to " + newPkg.getPackageName()
+                    + ": old package still exists");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Asserts the parsed package is valid according to the given policy. If the
+     * package is invalid, for whatever reason, throws {@link PackageManagerException}.
+     * <p>
+     * Implementation detail: This method must NOT have any side effects. It would
+     * ideally be static, but, it requires locks to read system state.
+     *
+     * @throws PackageManagerException If the package fails any of the validation checks
+     */
+    private void assertPackageIsValid(AndroidPackage pkg,
+            final @ParsingPackageUtils.ParseFlags int parseFlags,
+            final @PackageManagerService.ScanFlags int scanFlags)
+            throws PackageManagerException {
+        if ((parseFlags & ParsingPackageUtils.PARSE_ENFORCE_CODE) != 0) {
+            ScanPackageUtils.assertCodePolicy(pkg);
+        }
+
+        if (pkg.getPath() == null) {
+            // Bail out. The resource and code paths haven't been set.
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                    "Code and resource paths haven't been set correctly");
+        }
+
+        // Check that there is an APEX package with the same name only during install/first boot
+        // after OTA.
+        final boolean isUserInstall = (scanFlags & SCAN_BOOTING) == 0;
+        final boolean isFirstBootOrUpgrade = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
+        if ((isUserInstall || isFirstBootOrUpgrade)
+                && mApexManager.isApexPackage(pkg.getPackageName())) {
+            throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                    pkg.getPackageName()
+                            + " is an APEX package and can't be installed as an APK.");
+        }
+
+        // Make sure we're not adding any bogus keyset info
+        final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
+        ksms.assertScannedPackageValid(pkg);
+
+        synchronized (mPm.mLock) {
+            // The special "android" package can only be defined once
+            if (pkg.getPackageName().equals("android")) {
+                if (mPm.getCoreAndroidApplication() != null) {
+                    Slog.w(TAG, "*************************************************");
+                    Slog.w(TAG, "Core android package being redefined.  Skipping.");
+                    Slog.w(TAG, " codePath=" + pkg.getPath());
+                    Slog.w(TAG, "*************************************************");
+                    throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                            "Core android package being redefined.  Skipping.");
+                }
+            }
+
+            // A package name must be unique; don't allow duplicates
+            if ((scanFlags & SCAN_NEW_INSTALL) == 0
+                    && mPm.mPackages.containsKey(pkg.getPackageName())) {
+                throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                        "Application package " + pkg.getPackageName()
+                                + " already installed.  Skipping duplicate.");
+            }
+
+            if (pkg.isStaticSharedLibrary()) {
+                // Static libs have a synthetic package name containing the version
+                // but we still want the base name to be unique.
+                if ((scanFlags & SCAN_NEW_INSTALL) == 0
+                        && mPm.mPackages.containsKey(pkg.getManifestPackageName())) {
+                    throw new PackageManagerException(
+                            "Duplicate static shared lib provider package");
+                }
+                ScanPackageUtils.assertStaticSharedLibraryIsValid(pkg, scanFlags);
+                assertStaticSharedLibraryVersionCodeIsValid(pkg);
+            }
+
+            // If we're only installing presumed-existing packages, require that the
+            // scanned APK is both already known and at the path previously established
+            // for it.  Previously unknown packages we pick up normally, but if we have an
+            // a priori expectation about this package's install presence, enforce it.
+            // With a singular exception for new system packages. When an OTA contains
+            // a new system package, we allow the codepath to change from a system location
+            // to the user-installed location. If we don't allow this change, any newer,
+            // user-installed version of the application will be ignored.
+            if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
+                if (mPm.isExpectingBetter(pkg.getPackageName())) {
+                    Slog.w(TAG, "Relax SCAN_REQUIRE_KNOWN requirement for package "
+                            + pkg.getPackageName());
+                } else {
+                    PackageSetting known = mPm.mSettings.getPackageLPr(pkg.getPackageName());
+                    if (known != null) {
+                        if (DEBUG_PACKAGE_SCANNING) {
+                            Log.d(TAG, "Examining " + pkg.getPath()
+                                    + " and requiring known path " + known.getPathString());
+                        }
+                        if (!pkg.getPath().equals(known.getPathString())) {
+                            throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
+                                    "Application package " + pkg.getPackageName()
+                                            + " found at " + pkg.getPath()
+                                            + " but expected at " + known.getPathString()
+                                            + "; ignoring.");
+                        }
+                    } else {
+                        throw new PackageManagerException(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+                                "Application package " + pkg.getPackageName()
+                                        + " not found; ignoring.");
+                    }
+                }
+            }
+
+            // Verify that this new package doesn't have any content providers
+            // that conflict with existing packages.  Only do this if the
+            // package isn't already installed, since we don't want to break
+            // things that are installed.
+            if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
+                mPm.mComponentResolver.assertProvidersNotDefined(pkg);
+            }
+
+            // If this package has defined explicit processes, then ensure that these are
+            // the only processes used by its components.
+            ScanPackageUtils.assertProcessesAreValid(pkg);
+
+            // Verify that packages sharing a user with a privileged app are marked as privileged.
+            assertPackageWithSharedUserIdIsPrivileged(pkg);
+
+            // Apply policies specific for runtime resource overlays (RROs).
+            if (pkg.getOverlayTarget() != null) {
+                assertOverlayIsValid(pkg, parseFlags, scanFlags);
+            }
+
+            // If the package is not on a system partition ensure it is signed with at least the
+            // minimum signature scheme version required for its target SDK.
+            ScanPackageUtils.assertMinSignatureSchemeIsValid(pkg, parseFlags);
+        }
+    }
+
+    private void assertStaticSharedLibraryVersionCodeIsValid(AndroidPackage pkg)
+            throws PackageManagerException {
+        // The version codes must be ordered as lib versions
+        long minVersionCode = Long.MIN_VALUE;
+        long maxVersionCode = Long.MAX_VALUE;
+
+        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mPm.mSharedLibraries.get(
+                pkg.getStaticSharedLibName());
+        if (versionedLib != null) {
+            final int versionCount = versionedLib.size();
+            for (int i = 0; i < versionCount; i++) {
+                SharedLibraryInfo libInfo = versionedLib.valueAt(i);
+                final long libVersionCode = libInfo.getDeclaringPackage()
+                        .getLongVersionCode();
+                if (libInfo.getLongVersion() < pkg.getStaticSharedLibVersion()) {
+                    minVersionCode = Math.max(minVersionCode, libVersionCode + 1);
+                } else if (libInfo.getLongVersion()
+                        > pkg.getStaticSharedLibVersion()) {
+                    maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1);
+                } else {
+                    minVersionCode = maxVersionCode = libVersionCode;
+                    break;
+                }
+            }
+        }
+        if (pkg.getLongVersionCode() < minVersionCode
+                || pkg.getLongVersionCode() > maxVersionCode) {
+            throw new PackageManagerException("Static shared"
+                    + " lib version codes must be ordered as lib versions");
+        }
+    }
+
+    private void assertOverlayIsValid(AndroidPackage pkg,
+            @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags) throws PackageManagerException {
+        // System overlays have some restrictions on their use of the 'static' state.
+        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
+            // We are scanning a system overlay. This can be the first scan of the
+            // system/vendor/oem partition, or an update to the system overlay.
+            if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                // This must be an update to a system overlay. Immutable overlays cannot be
+                // upgraded.
+                if (!mPm.isOverlayMutable(pkg.getPackageName())) {
+                    throw new PackageManagerException("Overlay "
+                            + pkg.getPackageName()
+                            + " is static and cannot be upgraded.");
+                }
+            } else {
+                if ((scanFlags & SCAN_AS_VENDOR) != 0) {
+                    if (pkg.getTargetSdkVersion() < ScanPackageUtils.getVendorPartitionVersion()) {
+                        Slog.w(TAG, "System overlay " + pkg.getPackageName()
+                                + " targets an SDK below the required SDK level of vendor"
+                                + " overlays ("
+                                + ScanPackageUtils.getVendorPartitionVersion()
+                                + ")."
+                                + " This will become an install error in a future release");
+                    }
+                } else if (pkg.getTargetSdkVersion() < Build.VERSION.SDK_INT) {
+                    Slog.w(TAG, "System overlay " + pkg.getPackageName()
+                            + " targets an SDK below the required SDK level of system"
+                            + " overlays (" + Build.VERSION.SDK_INT + ")."
+                            + " This will become an install error in a future release");
+                }
+            }
+        } else {
+            // A non-preloaded overlay packages must have targetSdkVersion >= Q, or be
+            // signed with the platform certificate. Check this in increasing order of
+            // computational cost.
+            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.Q) {
+                final PackageSetting platformPkgSetting =
+                        mPm.mSettings.getPackageLPr("android");
+                if (!comparePackageSignatures(platformPkgSetting,
+                        pkg.getSigningDetails().getSignatures())) {
+                    throw new PackageManagerException("Overlay "
+                            + pkg.getPackageName()
+                            + " must target Q or later, "
+                            + "or be signed with the platform certificate");
+                }
+            }
+
+            // A non-preloaded overlay package, without <overlay android:targetName>, will
+            // only be used if it is signed with the same certificate as its target OR if
+            // it is signed with the same certificate as a reference package declared
+            // in 'overlay-config-signature' tag of SystemConfig.
+            // If the target is already installed or 'overlay-config-signature' tag in
+            // SystemConfig is set, check this here to augment the last line of defense
+            // which is OMS.
+            if (pkg.getOverlayTargetOverlayableName() == null) {
+                final PackageSetting targetPkgSetting =
+                        mPm.mSettings.getPackageLPr(pkg.getOverlayTarget());
+                if (targetPkgSetting != null) {
+                    if (!comparePackageSignatures(targetPkgSetting,
+                            pkg.getSigningDetails().getSignatures())) {
+                        // check reference signature
+                        if (mPm.mOverlayConfigSignaturePackage == null) {
+                            throw new PackageManagerException("Overlay "
+                                    + pkg.getPackageName() + " and target "
+                                    + pkg.getOverlayTarget() + " signed with"
+                                    + " different certificates, and the overlay lacks"
+                                    + " <overlay android:targetName>");
+                        }
+                        final PackageSetting refPkgSetting =
+                                mPm.mSettings.getPackageLPr(
+                                        mPm.mOverlayConfigSignaturePackage);
+                        if (!comparePackageSignatures(refPkgSetting,
+                                pkg.getSigningDetails().getSignatures())) {
+                            throw new PackageManagerException("Overlay "
+                                    + pkg.getPackageName() + " signed with a different "
+                                    + "certificate than both the reference package and "
+                                    + "target " + pkg.getOverlayTarget() + ", and the "
+                                    + "overlay lacks <overlay android:targetName>");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void assertPackageWithSharedUserIdIsPrivileged(AndroidPackage pkg)
+            throws PackageManagerException {
+        if (!pkg.isPrivileged() && (pkg.getSharedUserId() != null)) {
+            SharedUserSetting sharedUserSetting = null;
+            try {
+                sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(),
+                        0, 0, false);
+            } catch (PackageManagerException ignore) {
+            }
+            if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
+                // Exempt SharedUsers signed with the platform key.
+                PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
+                if (!comparePackageSignatures(platformPkgSetting,
+                        pkg.getSigningDetails().getSignatures())) {
+                    throw new PackageManagerException("Apps that share a user with a "
+                            + "privileged app must themselves be marked as privileged. "
+                            + pkg.getPackageName() + " shares privileged user "
+                            + pkg.getSharedUserId() + ".");
+                }
+            }
+        }
+    }
+
+    private @PackageManagerService.ScanFlags int adjustScanFlags(
+            @PackageManagerService.ScanFlags int scanFlags,
+            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user,
+            AndroidPackage pkg) {
+        scanFlags = ScanPackageUtils.adjustScanFlagsWithPackageSetting(scanFlags, pkgSetting,
+                disabledPkgSetting, user);
+
+        // Exception for privileged apps that share a user with a priv-app.
+        final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0)
+                && ScanPackageUtils.getVendorPartitionVersion() < 28;
+        if (((scanFlags & SCAN_AS_PRIVILEGED) == 0)
+                && !pkg.isPrivileged()
+                && (pkg.getSharedUserId() != null)
+                && !skipVendorPrivilegeScan) {
+            SharedUserSetting sharedUserSetting = null;
+            synchronized (mPm.mLock) {
+                try {
+                    sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(), 0,
+                            0, false);
+                } catch (PackageManagerException ignore) {
+                }
+                if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
+                    // Exempt SharedUsers signed with the platform key.
+                    // TODO(b/72378145) Fix this exemption. Force signature apps
+                    // to allowlist their privileged permissions just like other
+                    // priv-apps.
+                    PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
+                    if ((compareSignatures(
+                            platformPkgSetting.getSigningDetails().getSignatures(),
+                            pkg.getSigningDetails().getSignatures())
+                            != PackageManager.SIGNATURE_MATCH)) {
+                        scanFlags |= SCAN_AS_PRIVILEGED;
+                    }
+                }
+            }
+        }
+
+        return scanFlags;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8f6ac07..6f8703b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -57,6 +57,7 @@
 import android.app.IActivityManager;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SecurityLog;
+import android.app.backup.IBackupManager;
 import android.app.role.RoleManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -582,13 +583,6 @@
 
     /** Directory where installed applications are stored */
     private final File mAppInstallDir;
-    /** Directory where installed application's 32-bit native libraries are copied. */
-    @VisibleForTesting
-    final File mAppLib32InstallDir;
-
-    static File getAppLib32InstallDir() {
-        return new File(Environment.getDataDirectory(), "app-lib");
-    }
 
     // ----------------------------------------------------------------
 
@@ -975,6 +969,7 @@
     private final DeletePackageHelper mDeletePackageHelper;
     private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
     private final AppDataHelper mAppDataHelper;
+    private final InstallPackageHelper mInstallPackageHelper;
     private final PreferredActivityHelper mPreferredActivityHelper;
     private final ResolveIntentHelper mResolveIntentHelper;
     private final DexOptHelper mDexOptHelper;
@@ -1525,7 +1520,9 @@
                 new DefaultSystemWrapper(),
                 LocalServices::getService,
                 context::getSystemService,
-                (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm));
+                (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm),
+                (i, pm) -> IBackupManager.Stub.asInterface(ServiceManager.getService(
+                        Context.BACKUP_SERVICE)));
 
         if (Build.VERSION.SDK_INT <= 0) {
             Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1697,7 +1694,6 @@
         mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
         mSdkVersion = testParams.sdkVersion;
         mAppInstallDir = testParams.appInstallDir;
-        mAppLib32InstallDir = testParams.appLib32InstallDir;
         mIsEngBuild = testParams.isEngBuild;
         mIsUserDebugBuild = testParams.isUserDebugBuild;
         mIncrementalVersion = testParams.incrementalVersion;
@@ -1705,6 +1701,7 @@
 
         mBroadcastHelper = testParams.broadcastHelper;
         mAppDataHelper = testParams.appDataHelper;
+        mInstallPackageHelper = testParams.installPackageHelper;
         mRemovePackageHelper = testParams.removePackageHelper;
         mInitAndSystemPackageHelper = testParams.initAndSystemPackageHelper;
         mDeletePackageHelper = testParams.deletePackageHelper;
@@ -1837,7 +1834,6 @@
         mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager, mPmInternal);
 
         mAppInstallDir = new File(Environment.getDataDirectory(), "app");
-        mAppLib32InstallDir = getAppLib32InstallDir();
 
         mDomainVerificationConnection = new DomainVerificationConnection(this);
         mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
@@ -1845,6 +1841,7 @@
 
         mBroadcastHelper = new BroadcastHelper(mInjector);
         mAppDataHelper = new AppDataHelper(this);
+        mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
         mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
         mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this);
         mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
@@ -2006,7 +2003,7 @@
                 // the rest of the commands above) because there's precious little we
                 // can do about it. A settings error is reported, though.
                 final List<String> changedAbiCodePath =
-                        ScanPackageHelper.applyAdjustedAbiToSharedUser(
+                        ScanPackageUtils.applyAdjustedAbiToSharedUser(
                                 setting, null /*scannedPackage*/,
                                 mInjector.getAbiHelper().getAdjustedAbiForSharedUser(
                                 setting.packages, null /*scannedPackage*/));
@@ -4698,35 +4695,10 @@
     @Override
     public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
             int installReason, List<String> whiteListedPermissions) {
-        final InstallPackageHelper installPackageHelper = new InstallPackageHelper(
-                this, mAppDataHelper);
-        return installPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
+        return mInstallPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
                 installReason, whiteListedPermissions, null);
     }
 
-    static void setInstantAppForUser(PackageManagerServiceInjector injector,
-            PackageSetting pkgSetting, int userId, boolean instantApp, boolean fullApp) {
-        // no state specified; do nothing
-        if (!instantApp && !fullApp) {
-            return;
-        }
-        if (userId != UserHandle.USER_ALL) {
-            if (instantApp && !pkgSetting.getInstantApp(userId)) {
-                pkgSetting.setInstantApp(true /*instantApp*/, userId);
-            } else if (fullApp && pkgSetting.getInstantApp(userId)) {
-                pkgSetting.setInstantApp(false /*instantApp*/, userId);
-            }
-        } else {
-            for (int currentUserId : injector.getUserManagerInternal().getUserIds()) {
-                if (instantApp && !pkgSetting.getInstantApp(currentUserId)) {
-                    pkgSetting.setInstantApp(true /*instantApp*/, currentUserId);
-                } else if (fullApp && pkgSetting.getInstantApp(currentUserId)) {
-                    pkgSetting.setInstantApp(false /*instantApp*/, currentUserId);
-                }
-            }
-        }
-    }
-
     boolean isUserRestricted(int userId, String restrictionKey) {
         Bundle restrictions = mUserManager.getUserRestrictions(userId);
         if (restrictions.getBoolean(restrictionKey, false)) {
@@ -6733,8 +6705,7 @@
             if (isSystemStub
                     && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                     || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
-                if (!new InstallPackageHelper(this).enableCompressedPackage(deletedPkg,
-                        pkgSetting)) {
+                if (!mInstallPackageHelper.enableCompressedPackage(deletedPkg, pkgSetting)) {
                     Slog.w(TAG, "Failed setApplicationEnabledSetting: failed to enable "
                             + "commpressed package " + setting.getPackageName());
                     updateAllowed[i] = false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 97a09ff..d14cc1f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import android.app.ActivityManagerInternal;
+import android.app.backup.IBackupManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Handler;
@@ -135,6 +136,7 @@
             mDomainVerificationManagerInternalProducer;
     private final Singleton<Handler> mHandlerProducer;
     private final Singleton<BackgroundDexOptService> mBackgroundDexOptService;
+    private final Singleton<IBackupManager> mIBackupManager;
 
     PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
             Installer installer, Object installLock, PackageAbiHelper abiHelper,
@@ -170,7 +172,8 @@
             SystemWrapper systemWrapper,
             ServiceProducer getLocalServiceProducer,
             ServiceProducer getSystemServiceProducer,
-            Producer<BackgroundDexOptService> backgroundDexOptService) {
+            Producer<BackgroundDexOptService> backgroundDexOptService,
+            Producer<IBackupManager> iBackupManager) {
         mContext = context;
         mLock = lock;
         mInstaller = installer;
@@ -220,6 +223,7 @@
                         domainVerificationManagerInternalProducer);
         mHandlerProducer = new Singleton<>(handlerProducer);
         mBackgroundDexOptService = new Singleton<>(backgroundDexOptService);
+        mIBackupManager = new Singleton<>(iBackupManager);
     }
 
     /**
@@ -384,6 +388,10 @@
         return mBackgroundDexOptService.get(this, mPackageManager);
     }
 
+    public IBackupManager getIBackupManager() {
+        return mIBackupManager.get(this, mPackageManager);
+    }
+
     /** Provides an abstraction to static access to system state. */
     public interface SystemWrapper {
         void disablePackageCaches();
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 9327c5f..a1acc38 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -104,6 +104,7 @@
     public final String incrementalVersion = Build.VERSION.INCREMENTAL;
     public BroadcastHelper broadcastHelper;
     public AppDataHelper appDataHelper;
+    public InstallPackageHelper installPackageHelper;
     public RemovePackageHelper removePackageHelper;
     public InitAndSystemPackageHelper initAndSystemPackageHelper;
     public DeletePackageHelper deletePackageHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index bcd0708..898f673 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -18,9 +18,11 @@
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDWR;
 
+import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
 import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
@@ -60,6 +62,7 @@
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.incremental.IncrementalManager;
+import android.os.incremental.IncrementalStorage;
 import android.os.incremental.V4Signature;
 import android.os.incremental.V4Signature.HashingInfo;
 import android.os.storage.DiskInfo;
@@ -84,6 +87,7 @@
 import com.android.internal.util.HexDump;
 import com.android.server.EventLogTags;
 import com.android.server.IntentResolver;
+import com.android.server.Watchdog;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -649,6 +653,58 @@
     }
 
     /**
+     * Extract native libraries to a target path
+     */
+    public static int extractNativeBinaries(File dstCodePath, String packageName) {
+        final File libraryRoot = new File(dstCodePath, LIB_DIR_NAME);
+        NativeLibraryHelper.Handle handle = null;
+        try {
+            handle = NativeLibraryHelper.Handle.create(dstCodePath);
+            return NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
+                    null /*abiOverride*/, false /*isIncremental*/);
+        } catch (IOException e) {
+            logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
+                    + "; pkg: " + packageName);
+            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+        } finally {
+            IoUtils.closeQuietly(handle);
+        }
+    }
+
+    /**
+     * Remove native libraries of a given package
+     */
+    public static void removeNativeBinariesLI(PackageSetting ps) {
+        if (ps != null) {
+            NativeLibraryHelper.removeNativeBinariesLI(ps.getLegacyNativeLibraryPath());
+        }
+    }
+
+    /**
+     * Wait for native library extraction to be done in IncrementalService
+     */
+    public static void waitForNativeBinariesExtractionForIncremental(
+            ArraySet<IncrementalStorage> incrementalStorages) {
+        if (incrementalStorages.isEmpty()) {
+            return;
+        }
+        try {
+            // Native library extraction may take very long time: each page could potentially
+            // wait for either 10s or 100ms (adb vs non-adb data loader), and that easily adds
+            // up to a full watchdog timeout of 1 min, killing the system after that. It doesn't
+            // make much sense as blocking here doesn't lock up the framework, but only blocks
+            // the installation session and the following ones.
+            Watchdog.getInstance().pauseWatchingCurrentThread("native_lib_extract");
+            for (int i = 0; i < incrementalStorages.size(); ++i) {
+                IncrementalStorage storage = incrementalStorages.valueAtUnchecked(i);
+                storage.waitForNativeBinariesExtraction();
+            }
+        } finally {
+            Watchdog.getInstance().resumeWatchingCurrentThread("native_lib_extract");
+        }
+    }
+
+    /**
      * Decompress files stored in codePath to dstCodePath for a certain package.
      */
     public static int decompressFiles(String codePath, File dstCodePath, String packageName) {
@@ -1280,4 +1336,39 @@
 
         return cacheDir;
     }
+
+    /**
+     * Check and throw if the given before/after packages would be considered a
+     * downgrade.
+     */
+    public static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
+            throws PackageManagerException {
+        if (after.getLongVersionCode() < before.getLongVersionCode()) {
+            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                    "Update version code " + after.versionCode + " is older than current "
+                            + before.getLongVersionCode());
+        } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
+            if (after.baseRevisionCode < before.getBaseRevisionCode()) {
+                throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                        "Update base revision code " + after.baseRevisionCode
+                                + " is older than current " + before.getBaseRevisionCode());
+            }
+
+            if (!ArrayUtils.isEmpty(after.splitNames)) {
+                for (int i = 0; i < after.splitNames.length; i++) {
+                    final String splitName = after.splitNames[i];
+                    final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
+                    if (j != -1) {
+                        if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
+                            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                                    "Update split " + splitName + " revision code "
+                                            + after.splitRevisionCodes[i]
+                                            + " is older than current "
+                                            + before.getSplitRevisionCodes()[j]);
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index a60d2c8..48dc3cb 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -49,6 +49,7 @@
     boolean mDataRemoved;
     boolean mRemovedForAllUsers;
     boolean mIsStaticSharedLib;
+    boolean mAppIdChanging = false;
     // a two dimensional array mapping userId to the set of appIds that can receive notice
     // of package changes
     SparseArray<int[]> mBroadcastAllowList;
@@ -64,33 +65,43 @@
         sendPackageRemovedBroadcastInternal(killApp, removedBySystem);
     }
 
-    void sendSystemPackageUpdatedBroadcasts() {
+    void sendSystemPackageUpdatedBroadcasts(int newAppId) {
         if (mIsRemovedPackageSystemUpdate) {
-            sendSystemPackageUpdatedBroadcastsInternal();
+            sendSystemPackageUpdatedBroadcastsInternal(newAppId);
         }
     }
 
-    private void sendSystemPackageUpdatedBroadcastsInternal() {
+    private void sendSystemPackageUpdatedBroadcastsInternal(int newAppId) {
         Bundle extras = new Bundle(2);
-        extras.putInt(Intent.EXTRA_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid);
-        extras.putBoolean(Intent.EXTRA_REPLACING, true);
+        extras.putInt(Intent.EXTRA_UID, newAppId);
+        // When appId changes, do not set the replacing extra
+        if (mAppIdChanging) {
+            extras.putBoolean(Intent.EXTRA_UID_CHANGING, true);
+            extras.putInt(Intent.EXTRA_PREVIOUS_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid);
+        } else {
+            extras.putBoolean(Intent.EXTRA_REPLACING, true);
+        }
         mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, mRemovedPackage, extras,
                 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
-        mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage,
-                extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
-        mPackageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
-                mRemovedPackage, null, null, null, null /* broadcastAllowList */,
-                getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle());
         if (mInstallerPackageName != null) {
             mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                     mRemovedPackage, extras, 0 /*flags*/,
                     mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
                     null);
-            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                    mRemovedPackage, extras, 0 /*flags*/,
-                    mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
-                    null);
         }
+        if (!mAppIdChanging) {
+            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage,
+                    extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
+            if (mInstallerPackageName != null) {
+                mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+                        mRemovedPackage, extras, 0 /*flags*/,
+                        mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
+                        null);
+            }
+        }
+        mPackageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
+                mRemovedPackage, null, null, null, null /* broadcastAllowList */,
+                getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle());
     }
 
     private static @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@@ -115,15 +126,20 @@
         if (mIsStaticSharedLib) {
             return;
         }
-        Bundle extras = new Bundle(2);
+        Bundle extras = new Bundle();
         final int removedUid = mRemovedAppId >= 0  ? mRemovedAppId : mUid;
         extras.putInt(Intent.EXTRA_UID, removedUid);
         extras.putBoolean(Intent.EXTRA_DATA_REMOVED, mDataRemoved);
         extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
         extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
-        if (mIsUpdate || mIsRemovedPackageSystemUpdate) {
+
+        // When appId changes, do not set the replacing extra
+        if (mAppIdChanging) {
+            extras.putBoolean(Intent.EXTRA_UID_CHANGING, true);
+        } else if (mIsUpdate || mIsRemovedPackageSystemUpdate) {
             extras.putBoolean(Intent.EXTRA_REPLACING, true);
         }
+
         extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
         if (mRemovedPackage != null) {
             mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
@@ -146,9 +162,9 @@
             }
         }
         if (mRemovedAppId >= 0) {
-            // If a system app's updates are uninstalled the UID is not actually removed. Some
-            // services need to know the package name affected.
-            if (extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
+            // If the package is not actually removed, some services need to know the
+            // package name affected.
+            if (mAppIdChanging || mIsUpdate || mIsRemovedPackageSystemUpdate) {
                 extras.putString(Intent.EXTRA_PACKAGE_NAME, mRemovedPackage);
             }
 
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
new file mode 100644
index 0000000..5a25004
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+
+import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
+
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.utils.WatchedLongSparseArray;
+
+import java.util.List;
+import java.util.Map;
+
+final class ReconcilePackageUtils {
+    public static Map<String, ReconciledPackage> reconcilePackages(
+            final ReconcileRequest request, KeySetManagerService ksms,
+            PackageManagerServiceInjector injector)
+            throws ReconcileFailure {
+        final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
+
+        final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
+
+        // make a copy of the existing set of packages so we can combine them with incoming packages
+        final ArrayMap<String, AndroidPackage> combinedPackages =
+                new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
+
+        combinedPackages.putAll(request.mAllPackages);
+
+        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
+                new ArrayMap<>();
+
+        for (String installPackageName : scannedPackages.keySet()) {
+            final ScanResult scanResult = scannedPackages.get(installPackageName);
+
+            // add / replace existing with incoming packages
+            combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
+                    scanResult.mRequest.mParsedPackage);
+
+            // in the first pass, we'll build up the set of incoming shared libraries
+            final List<SharedLibraryInfo> allowedSharedLibInfos =
+                    SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
+                            request.mSharedLibrarySource);
+            if (allowedSharedLibInfos != null) {
+                for (SharedLibraryInfo info : allowedSharedLibInfos) {
+                    if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
+                            incomingSharedLibraries, info)) {
+                        throw new ReconcileFailure("Shared Library " + info.getName()
+                                + " is being installed twice in this set!");
+                    }
+                }
+            }
+
+            // the following may be null if we're just reconciling on boot (and not during install)
+            final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
+            final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
+            final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
+            final boolean isInstall = installArgs != null;
+            if (isInstall && (res == null || prepareResult == null)) {
+                throw new ReconcileFailure("Reconcile arguments are not balanced for "
+                        + installPackageName + "!");
+            }
+
+            final DeletePackageAction deletePackageAction;
+            // we only want to try to delete for non system apps
+            if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
+                final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
+                final int deleteFlags = PackageManager.DELETE_KEEP_DATA
+                        | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
+                deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
+                        prepareResult.mOriginalPs, prepareResult.mDisabledPs,
+                        deleteFlags, null /* all users */);
+                if (deletePackageAction == null) {
+                    throw new ReconcileFailure(
+                            PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
+                            "May not delete " + installPackageName + " to replace");
+                }
+            } else {
+                deletePackageAction = null;
+            }
+
+            final int scanFlags = scanResult.mRequest.mScanFlags;
+            final int parseFlags = scanResult.mRequest.mParseFlags;
+            final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
+
+            final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
+            final PackageSetting lastStaticSharedLibSetting =
+                    request.mLastStaticSharedLibSettings.get(installPackageName);
+            final PackageSetting signatureCheckPs =
+                    (prepareResult != null && lastStaticSharedLibSetting != null)
+                            ? lastStaticSharedLibSetting
+                            : scanResult.mPkgSetting;
+            boolean removeAppKeySetData = false;
+            boolean sharedUserSignaturesChanged = false;
+            SigningDetails signingDetails = null;
+            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
+                    // We just determined the app is signed correctly, so bring
+                    // over the latest parsed certs.
+                } else {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                        throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                "Package " + parsedPackage.getPackageName()
+                                        + " upgrade keys do not match the previously installed"
+                                        + " version");
+                    } else {
+                        String msg = "System package " + parsedPackage.getPackageName()
+                                + " signature changed; retaining data.";
+                        PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    }
+                }
+                signingDetails = parsedPackage.getSigningDetails();
+            } else {
+                try {
+                    final Settings.VersionInfo versionInfo =
+                            request.mVersionInfos.get(installPackageName);
+                    final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
+                    final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
+                    final boolean isRollback = installArgs != null
+                            && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
+                    final boolean compatMatch = verifySignatures(signatureCheckPs,
+                            disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
+                            compareRecover, isRollback);
+                    // The new KeySets will be re-added later in the scanning process.
+                    if (compatMatch) {
+                        removeAppKeySetData = true;
+                    }
+                    // We just determined the app is signed correctly, so bring
+                    // over the latest parsed certs.
+                    signingDetails = parsedPackage.getSigningDetails();
+
+                    // if this is is a sharedUser, check to see if the new package is signed by a
+                    // newer
+                    // signing certificate than the existing one, and if so, copy over the new
+                    // details
+                    if (signatureCheckPs.getSharedUser() != null) {
+                        // Attempt to merge the existing lineage for the shared SigningDetails with
+                        // the lineage of the new package; if the shared SigningDetails are not
+                        // returned this indicates the new package added new signers to the lineage
+                        // and/or changed the capabilities of existing signers in the lineage.
+                        SigningDetails sharedSigningDetails =
+                                signatureCheckPs.getSharedUser().signatures.mSigningDetails;
+                        SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
+                                signingDetails);
+                        if (mergedDetails != sharedSigningDetails) {
+                            signatureCheckPs.getSharedUser().signatures.mSigningDetails =
+                                    mergedDetails;
+                        }
+                        if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
+                            signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
+                        }
+                    }
+                } catch (PackageManagerException e) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                        throw new ReconcileFailure(e);
+                    }
+                    signingDetails = parsedPackage.getSigningDetails();
+
+                    // If the system app is part of a shared user we allow that shared user to
+                    // change
+                    // signatures as well as part of an OTA. We still need to verify that the
+                    // signatures
+                    // are consistent within the shared user for a given boot, so only allow
+                    // updating
+                    // the signatures on the first package scanned for the shared user (i.e. if the
+                    // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
+                    if (signatureCheckPs.getSharedUser() != null) {
+                        final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
+                                .signatures.mSigningDetails.getSignatures();
+                        if (signatureCheckPs.getSharedUser().signaturesChanged != null
+                                && compareSignatures(sharedUserSignatures,
+                                parsedPackage.getSigningDetails().getSignatures())
+                                != PackageManager.SIGNATURE_MATCH) {
+                            if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
+                                // Mismatched signatures is an error and silently skipping system
+                                // packages will likely break the device in unforeseen ways.
+                                // However, we allow the device to boot anyway because, prior to Q,
+                                // vendors were not expecting the platform to crash in this
+                                // situation.
+                                // This WILL be a hard failure on any new API levels after Q.
+                                throw new ReconcileFailure(
+                                        INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                                        "Signature mismatch for shared user: "
+                                                + scanResult.mPkgSetting.getSharedUser());
+                            } else {
+                                // Treat mismatched signatures on system packages using a shared
+                                // UID as
+                                // fatal for the system overall, rather than just failing to install
+                                // whichever package happened to be scanned later.
+                                throw new IllegalStateException(
+                                        "Signature mismatch on system package "
+                                                + parsedPackage.getPackageName()
+                                                + " for shared user "
+                                                + scanResult.mPkgSetting.getSharedUser());
+                            }
+                        }
+
+                        sharedUserSignaturesChanged = true;
+                        signatureCheckPs.getSharedUser().signatures.mSigningDetails =
+                                parsedPackage.getSigningDetails();
+                        signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE;
+                    }
+                    // File a report about this.
+                    String msg = "System package " + parsedPackage.getPackageName()
+                            + " signature changed; retaining data.";
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                } catch (IllegalArgumentException e) {
+                    // should never happen: certs matched when checking, but not when comparing
+                    // old to new for sharedUser
+                    throw new RuntimeException(
+                            "Signing certificates comparison made on incomparable signing details"
+                                    + " but somehow passed verifySignatures!", e);
+                }
+            }
+
+            result.put(installPackageName,
+                    new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
+                            res, request.mPreparedPackages.get(installPackageName), scanResult,
+                            deletePackageAction, allowedSharedLibInfos, signingDetails,
+                            sharedUserSignaturesChanged, removeAppKeySetData));
+        }
+
+        for (String installPackageName : scannedPackages.keySet()) {
+            // Check all shared libraries and map to their actual file path.
+            // We only do this here for apps not on a system dir, because those
+            // are the only ones that can fail an install due to this.  We
+            // will take care of the system apps by updating all of their
+            // library paths after the scan is done. Also during the initial
+            // scan don't update any libs as we do this wholesale after all
+            // apps are scanned to avoid dependency based scanning.
+            final ScanResult scanResult = scannedPackages.get(installPackageName);
+            if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
+                    || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+                    != 0) {
+                continue;
+            }
+            try {
+                result.get(installPackageName).mCollectedSharedLibraryInfos =
+                        SharedLibraryHelper.collectSharedLibraryInfos(
+                                scanResult.mRequest.mParsedPackage,
+                                combinedPackages, request.mSharedLibrarySource,
+                                incomingSharedLibraries, injector.getCompatibility());
+
+            } catch (PackageManagerException e) {
+                throw new ReconcileFailure(e.error, e.getMessage());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * If the database version for this type of package (internal storage or
+     * external storage) is less than the version where package signatures
+     * were updated, return true.
+     */
+    public static boolean isCompatSignatureUpdateNeeded(Settings.VersionInfo ver) {
+        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_END_ENTITY;
+    }
+
+    public static boolean isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver) {
+        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
deleted file mode 100644
index eafe0d98..0000000
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ /dev/null
@@ -1,1716 +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 com.android.server.pm;
-
-import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
-import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
-import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
-import static android.content.pm.PackageManager.INSTALL_FAILED_PROCESS_NOT_DEFINED;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
-import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
-import static com.android.server.pm.PackageManagerService.DEBUG_ABI_SELECTION;
-import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
-import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
-import static com.android.server.pm.PackageManagerService.DEBUG_UPGRADE;
-import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_ODM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
-import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
-import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
-import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
-import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
-import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
-import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
-import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
-import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_TIME;
-import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
-import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
-import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
-import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
-import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
-import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.SharedLibraryInfo;
-import android.content.pm.SigningDetails;
-import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.component.ComponentMutateUtils;
-import android.content.pm.parsing.component.ParsedActivity;
-import android.content.pm.parsing.component.ParsedMainComponent;
-import android.content.pm.parsing.component.ParsedProcess;
-import android.content.pm.parsing.component.ParsedProvider;
-import android.content.pm.parsing.component.ParsedService;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
-import android.os.Build;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.apk.ApkSignatureVerifier;
-import android.util.jar.StrictJarFile;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.security.VerityUtils;
-import com.android.internal.util.ArrayUtils;
-import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.utils.WatchedLongSparseArray;
-
-import dalvik.system.VMRuntime;
-
-import java.io.File;
-import java.io.IOException;
-import java.security.DigestException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * Helper class that handles package scanning logic
- */
-final class ScanPackageHelper {
-    final PackageManagerService mPm;
-    final PackageManagerServiceInjector mInjector;
-
-    // TODO(b/198166813): remove PMS dependency
-    public ScanPackageHelper(PackageManagerService pm) {
-        mPm = pm;
-        mInjector = pm.mInjector;
-    }
-
-    ScanPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
-        mPm = pm;
-        mInjector = injector;
-    }
-
-    /**
-     *  Similar to the other scanPackageTracedLI but accepting a ParsedPackage instead of a File.
-     */
-    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public ScanResult scanPackageTracedLI(ParsedPackage parsedPackage,
-            final @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
-            @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
-        try {
-            return scanPackageNewLI(parsedPackage, parseFlags, scanFlags, currentTime, user,
-                    cpuAbiOverride);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
-    // TODO(b/199937291): scanPackageNewLI() and scanPackageOnlyLI() should be merged.
-    // But, first, committing the results / removing app data needs to be moved up a level to the
-    // callers of this method. Also, we need to solve the problem of potentially creating a new
-    // shared user setting. That can probably be done later and patch things up after the fact.
-    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
-            final @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
-            @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
-
-        final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
-                AndroidPackageUtils.getRealPackageOrNull(parsedPackage));
-        final String realPkgName = getRealPackageName(parsedPackage, renamedPkgName);
-        if (realPkgName != null) {
-            ensurePackageRenamed(parsedPackage, renamedPkgName);
-        }
-        final PackageSetting originalPkgSetting = getOriginalPackageLocked(parsedPackage,
-                renamedPkgName);
-        final PackageSetting pkgSetting =
-                mPm.mSettings.getPackageLPr(parsedPackage.getPackageName());
-        final PackageSetting disabledPkgSetting =
-                mPm.mSettings.getDisabledSystemPkgLPr(parsedPackage.getPackageName());
-
-        if (mPm.mTransferredPackages.contains(parsedPackage.getPackageName())) {
-            Slog.w(TAG, "Package " + parsedPackage.getPackageName()
-                    + " was transferred to another, but its .apk remains");
-        }
-
-        scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user, parsedPackage);
-        synchronized (mPm.mLock) {
-            boolean isUpdatedSystemApp;
-            if (pkgSetting != null) {
-                isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();
-            } else {
-                isUpdatedSystemApp = disabledPkgSetting != null;
-            }
-            applyPolicy(parsedPackage, scanFlags, mPm.getPlatformPackage(), isUpdatedSystemApp);
-            assertPackageIsValid(parsedPackage, parseFlags, scanFlags);
-
-            SharedUserSetting sharedUserSetting = null;
-            if (parsedPackage.getSharedUserId() != null) {
-                // SIDE EFFECTS; may potentially allocate a new shared user
-                sharedUserSetting = mPm.mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
-                        0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
-                if (DEBUG_PACKAGE_SCANNING) {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
-                        Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
-                                + " (uid=" + sharedUserSetting.userId + "):"
-                                + " packages=" + sharedUserSetting.packages);
-                    }
-                }
-            }
-            String platformPackageName = mPm.getPlatformPackage() == null
-                    ? null : mPm.getPlatformPackage().getPackageName();
-            final ScanRequest request = new ScanRequest(parsedPackage, sharedUserSetting,
-                    pkgSetting == null ? null : pkgSetting.getPkg(), pkgSetting, disabledPkgSetting,
-                    originalPkgSetting, realPkgName, parseFlags, scanFlags,
-                    Objects.equals(parsedPackage.getPackageName(), platformPackageName), user,
-                    cpuAbiOverride);
-            return scanPackageOnlyLI(request, mInjector, mPm.mFactoryTest, currentTime);
-        }
-    }
-
-    /**
-     * Just scans the package without any side effects.
-     * <p>Not entirely true at the moment. There is still one side effect -- this
-     * method potentially modifies a live {@link PackageSetting} object representing
-     * the package being scanned. This will be resolved in the future.
-     *
-     * @param injector injector for acquiring dependencies
-     * @param request Information about the package to be scanned
-     * @param isUnderFactoryTest Whether or not the device is under factory test
-     * @param currentTime The current time, in millis
-     * @return The results of the scan
-     */
-    @GuardedBy("mPm.mInstallLock")
-    @VisibleForTesting
-    @NonNull
-    public ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
-            PackageManagerServiceInjector injector,
-            boolean isUnderFactoryTest, long currentTime)
-            throws PackageManagerException {
-        final PackageAbiHelper packageAbiHelper = injector.getAbiHelper();
-        ParsedPackage parsedPackage = request.mParsedPackage;
-        PackageSetting pkgSetting = request.mPkgSetting;
-        final PackageSetting disabledPkgSetting = request.mDisabledPkgSetting;
-        final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
-        final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
-        final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
-        final String realPkgName = request.mRealPkgName;
-        final SharedUserSetting sharedUserSetting = request.mSharedUserSetting;
-        final UserHandle user = request.mUser;
-        final boolean isPlatformPackage = request.mIsPlatformPackage;
-
-        List<String> changedAbiCodePath = null;
-
-        if (DEBUG_PACKAGE_SCANNING) {
-            if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
-                Log.d(TAG, "Scanning package " + parsedPackage.getPackageName());
-            }
-        }
-
-        // Initialize package source and resource directories
-        final File destCodeFile = new File(parsedPackage.getPath());
-
-        // We keep references to the derived CPU Abis from settings in oder to reuse
-        // them in the case where we're not upgrading or booting for the first time.
-        String primaryCpuAbiFromSettings = null;
-        String secondaryCpuAbiFromSettings = null;
-        boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
-        if (!needToDeriveAbi) {
-            if (pkgSetting != null) {
-                // TODO(b/154610922): if it is not first boot or upgrade, we should directly use
-                // API info from existing package setting. However, stub packages currently do not
-                // preserve ABI info, thus the special condition check here. Remove the special
-                // check after we fix the stub generation.
-                if (pkgSetting.getPkg() != null && pkgSetting.getPkg().isStub()) {
-                    needToDeriveAbi = true;
-                } else {
-                    primaryCpuAbiFromSettings = pkgSetting.getPrimaryCpuAbi();
-                    secondaryCpuAbiFromSettings = pkgSetting.getSecondaryCpuAbi();
-                }
-            } else {
-                // Re-scanning a system package after uninstalling updates; need to derive ABI
-                needToDeriveAbi = true;
-            }
-        }
-
-        int previousAppId = Process.INVALID_UID;
-
-        if (pkgSetting != null && pkgSetting.getSharedUser() != sharedUserSetting) {
-            if (pkgSetting.getSharedUser() != null && sharedUserSetting == null) {
-                previousAppId = pkgSetting.getAppId();
-                // Log that something is leaving shareduid and keep going
-                Slog.i(TAG,
-                        "Package " + parsedPackage.getPackageName() + " shared user changed from "
-                                + pkgSetting.getSharedUser().name + " to " + "<nothing>.");
-            } else {
-                PackageManagerService.reportSettingsProblem(Log.WARN,
-                        "Package " + parsedPackage.getPackageName() + " shared user changed from "
-                                + (pkgSetting.getSharedUser() != null
-                                ? pkgSetting.getSharedUser().name : "<nothing>")
-                                + " to "
-                                + (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>")
-                                + "; replacing with new");
-                pkgSetting = null;
-            }
-        }
-
-        String[] usesSdkLibraries = null;
-        if (!parsedPackage.getUsesSdkLibraries().isEmpty()) {
-            usesSdkLibraries = new String[parsedPackage.getUsesSdkLibraries().size()];
-            parsedPackage.getUsesSdkLibraries().toArray(usesSdkLibraries);
-        }
-
-        String[] usesStaticLibraries = null;
-        if (!parsedPackage.getUsesStaticLibraries().isEmpty()) {
-            usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
-            parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
-        }
-
-        final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();
-
-        // TODO(b/135203078): Remove appInfoFlag usage in favor of individually assigned booleans
-        //  to avoid adding something that's unsupported due to lack of state, since it's called
-        //  with null.
-        final boolean createNewPackage = (pkgSetting == null);
-        if (createNewPackage) {
-            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
-            final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;
-
-            // Flags contain system values stored in the server variant of AndroidPackage,
-            // and so the server-side PackageInfoUtils is still called, even without a
-            // PackageSetting to pass in.
-            int pkgFlags = PackageInfoUtils.appInfoFlags(parsedPackage, null);
-            int pkgPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(parsedPackage, null);
-
-            // REMOVE SharedUserSetting from method; update in a separate call
-            pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName(),
-                    originalPkgSetting, disabledPkgSetting, realPkgName, sharedUserSetting,
-                    destCodeFile, parsedPackage.getNativeLibraryRootDir(),
-                    AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
-                    AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
-                    parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
-                    true /*allowInstall*/, instantApp, virtualPreload,
-                    UserManagerService.getInstance(), usesSdkLibraries,
-                    parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
-                    parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
-                    newDomainSetId);
-        } else {
-            // make a deep copy to avoid modifying any existing system state.
-            pkgSetting = new PackageSetting(pkgSetting);
-            pkgSetting.setPkg(parsedPackage);
-
-            // REMOVE SharedUserSetting from method; update in a separate call.
-            //
-            // TODO(narayan): This update is bogus. nativeLibraryDir & primaryCpuAbi,
-            // secondaryCpuAbi are not known at this point so we always update them
-            // to null here, only to reset them at a later point.
-            Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, sharedUserSetting,
-                    destCodeFile, parsedPackage.getNativeLibraryDir(),
-                    AndroidPackageUtils.getPrimaryCpuAbi(parsedPackage, pkgSetting),
-                    AndroidPackageUtils.getSecondaryCpuAbi(parsedPackage, pkgSetting),
-                    PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
-                    PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
-                    UserManagerService.getInstance(),
-                    usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
-                    usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
-                    parsedPackage.getMimeGroups(), newDomainSetId);
-        }
-        if (createNewPackage && originalPkgSetting != null) {
-            // This is the initial transition from the original package, so,
-            // fix up the new package's name now. We must do this after looking
-            // up the package under its new name, so getPackageLP takes care of
-            // fiddling things correctly.
-            parsedPackage.setPackageName(originalPkgSetting.getPackageName());
-
-            // File a report about this.
-            String msg = "New package " + pkgSetting.getRealName()
-                    + " renamed to replace old package " + pkgSetting.getPackageName();
-            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-        }
-
-        final int userId = (user == null ? UserHandle.USER_SYSTEM : user.getIdentifier());
-        // for existing packages, change the install state; but, only if it's explicitly specified
-        if (!createNewPackage) {
-            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
-            final boolean fullApp = (scanFlags & SCAN_AS_FULL_APP) != 0;
-            PackageManagerService.setInstantAppForUser(
-                    injector, pkgSetting, userId, instantApp, fullApp);
-        }
-        // TODO(patb): see if we can do away with disabled check here.
-        if (disabledPkgSetting != null
-                || (0 != (scanFlags & SCAN_NEW_INSTALL)
-                && pkgSetting != null && pkgSetting.isSystem())) {
-            pkgSetting.getPkgState().setUpdatedSystemApp(true);
-        }
-
-        parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
-                injector.getCompatibility()));
-
-        if (parsedPackage.isSystem()) {
-            configurePackageComponents(parsedPackage);
-        }
-
-        final String cpuAbiOverride = deriveAbiOverride(request.mCpuAbiOverride);
-        final boolean isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();
-
-        final File appLib32InstallDir = PackageManagerService.getAppLib32InstallDir();
-        if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
-            if (needToDeriveAbi) {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
-                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
-                        packageAbiHelper.derivePackageAbi(parsedPackage, isUpdatedSystemApp,
-                                cpuAbiOverride, appLib32InstallDir);
-                derivedAbi.first.applyTo(parsedPackage);
-                derivedAbi.second.applyTo(parsedPackage);
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-
-                // Some system apps still use directory structure for native libraries
-                // in which case we might end up not detecting abi solely based on apk
-                // structure. Try to detect abi based on directory structure.
-
-                String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
-                if (parsedPackage.isSystem() && !isUpdatedSystemApp
-                        && pkgRawPrimaryCpuAbi == null) {
-                    final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
-                            parsedPackage);
-                    abis.applyTo(parsedPackage);
-                    abis.applyTo(pkgSetting);
-                    final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                            packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
-                                    isUpdatedSystemApp, appLib32InstallDir);
-                    nativeLibraryPaths.applyTo(parsedPackage);
-                }
-            } else {
-                // This is not a first boot or an upgrade, don't bother deriving the
-                // ABI during the scan. Instead, trust the value that was stored in the
-                // package setting.
-                parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
-                        .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
-
-                final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                        packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
-                                isUpdatedSystemApp, appLib32InstallDir);
-                nativeLibraryPaths.applyTo(parsedPackage);
-
-                if (DEBUG_ABI_SELECTION) {
-                    Slog.i(TAG, "Using ABIS and native lib paths from settings : "
-                            + parsedPackage.getPackageName() + " "
-                            + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
-                            + ", "
-                            + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
-                }
-            }
-        } else {
-            if ((scanFlags & SCAN_MOVE) != 0) {
-                // We haven't run dex-opt for this move (since we've moved the compiled output too)
-                // but we already have this packages package info in the PackageSetting. We just
-                // use that and derive the native library path based on the new code path.
-                parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbi())
-                        .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbi());
-            }
-
-            // Set native library paths again. For moves, the path will be updated based on the
-            // ABIs we've determined above. For non-moves, the path will be updated based on the
-            // ABIs we determined during compilation, but the path will depend on the final
-            // package path (after the rename away from the stage path).
-            final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                    packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isUpdatedSystemApp,
-                            appLib32InstallDir);
-            nativeLibraryPaths.applyTo(parsedPackage);
-        }
-
-        // This is a special case for the "system" package, where the ABI is
-        // dictated by the zygote configuration (and init.rc). We should keep track
-        // of this ABI so that we can deal with "normal" applications that run under
-        // the same UID correctly.
-        if (isPlatformPackage) {
-            parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
-                    ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
-        }
-
-        // If there's a mismatch between the abi-override in the package setting
-        // and the abiOverride specified for the install. Warn about this because we
-        // would've already compiled the app without taking the package setting into
-        // account.
-        if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {
-            if (cpuAbiOverride == null) {
-                Slog.w(TAG, "Ignoring persisted ABI override for package "
-                        + parsedPackage.getPackageName());
-            }
-        }
-
-        pkgSetting.setPrimaryCpuAbi(AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage))
-                .setSecondaryCpuAbi(AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage))
-                .setCpuAbiOverride(cpuAbiOverride);
-
-        if (DEBUG_ABI_SELECTION) {
-            Slog.d(TAG, "Resolved nativeLibraryRoot for " + parsedPackage.getPackageName()
-                    + " to root=" + parsedPackage.getNativeLibraryRootDir()
-                    + ", to dir=" + parsedPackage.getNativeLibraryDir()
-                    + ", isa=" + parsedPackage.isNativeLibraryRootRequiresIsa());
-        }
-
-        // Push the derived path down into PackageSettings so we know what to
-        // clean up at uninstall time.
-        pkgSetting.setLegacyNativeLibraryPath(parsedPackage.getNativeLibraryRootDir());
-
-        if (DEBUG_ABI_SELECTION) {
-            Log.d(TAG, "Abis for package[" + parsedPackage.getPackageName() + "] are"
-                    + " primary=" + pkgSetting.getPrimaryCpuAbi()
-                    + " secondary=" + pkgSetting.getSecondaryCpuAbi()
-                    + " abiOverride=" + pkgSetting.getCpuAbiOverride());
-        }
-
-        if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.getSharedUser() != null) {
-            // We don't do this here during boot because we can do it all
-            // at once after scanning all existing packages.
-            //
-            // We also do this *before* we perform dexopt on this package, so that
-            // we can avoid redundant dexopts, and also to make sure we've got the
-            // code and package path correct.
-            changedAbiCodePath = applyAdjustedAbiToSharedUser(pkgSetting.getSharedUser(),
-                    parsedPackage, packageAbiHelper.getAdjustedAbiForSharedUser(
-                            pkgSetting.getSharedUser().packages, parsedPackage));
-        }
-
-        parsedPackage.setFactoryTest(isUnderFactoryTest && parsedPackage.getRequestedPermissions()
-                .contains(android.Manifest.permission.FACTORY_TEST));
-
-        if (parsedPackage.isSystem()) {
-            pkgSetting.setIsOrphaned(true);
-        }
-
-        // Take care of first install / last update times.
-        final long scanFileTime = getLastModifiedTime(parsedPackage);
-        if (currentTime != 0) {
-            if (pkgSetting.getFirstInstallTime() == 0) {
-                pkgSetting.setFirstInstallTime(currentTime)
-                        .setLastUpdateTime(currentTime);
-            } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
-                pkgSetting.setLastUpdateTime(currentTime);
-            }
-        } else if (pkgSetting.getFirstInstallTime() == 0) {
-            // We need *something*.  Take time time stamp of the file.
-            pkgSetting.setFirstInstallTime(scanFileTime)
-                    .setLastUpdateTime(scanFileTime);
-        } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) {
-            if (scanFileTime != pkgSetting.getLastModifiedTime()) {
-                // A package on the system image has changed; consider this
-                // to be an update.
-                pkgSetting.setLastUpdateTime(scanFileTime);
-            }
-        }
-        pkgSetting.setLastModifiedTime(scanFileTime);
-        // TODO(b/135203078): Remove, move to constructor
-        pkgSetting.setPkg(parsedPackage)
-                .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting))
-                .setPrivateFlags(
-                        PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
-        if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
-            pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
-        }
-        // Update volume if needed
-        final String volumeUuid = parsedPackage.getVolumeUuid();
-        if (!Objects.equals(volumeUuid, pkgSetting.getVolumeUuid())) {
-            Slog.i(PackageManagerService.TAG,
-                    "Update" + (pkgSetting.isSystem() ? " system" : "")
-                            + " package " + parsedPackage.getPackageName()
-                            + " volume from " + pkgSetting.getVolumeUuid()
-                            + " to " + volumeUuid);
-            pkgSetting.setVolumeUuid(volumeUuid);
-        }
-
-        SharedLibraryInfo sdkLibraryInfo = null;
-        if (!TextUtils.isEmpty(parsedPackage.getSdkLibName())) {
-            sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
-        }
-        SharedLibraryInfo staticSharedLibraryInfo = null;
-        if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibName())) {
-            staticSharedLibraryInfo =
-                    AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
-        }
-        List<SharedLibraryInfo> dynamicSharedLibraryInfos = null;
-        if (!ArrayUtils.isEmpty(parsedPackage.getLibraryNames())) {
-            dynamicSharedLibraryInfos = new ArrayList<>(parsedPackage.getLibraryNames().size());
-            for (String name : parsedPackage.getLibraryNames()) {
-                dynamicSharedLibraryInfos.add(
-                        AndroidPackageUtils.createSharedLibraryForDynamic(parsedPackage, name));
-            }
-        }
-
-        return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
-                !createNewPackage /* existingSettingCopied */,
-                previousAppId, sdkLibraryInfo, staticSharedLibraryInfo,
-                dynamicSharedLibraryInfos);
-    }
-
-    /**
-     * Returns the actual scan flags depending upon the state of the other settings.
-     * <p>Updated system applications will not have the following flags set
-     * by default and need to be adjusted after the fact:
-     * <ul>
-     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_PRIVILEGED}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_OEM}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_VENDOR}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_PRODUCT}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM_EXT}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_INSTANT_APP}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_ODM}</li>
-     * </ul>
-     */
-    private @PackageManagerService.ScanFlags int adjustScanFlags(
-            @PackageManagerService.ScanFlags int scanFlags,
-            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user,
-            AndroidPackage pkg) {
-
-        // TODO(patb): Do away entirely with disabledPkgSetting here. PkgSetting will always contain
-        // the correct isSystem value now that we don't disable system packages before scan.
-        final PackageSetting systemPkgSetting =
-                (scanFlags & SCAN_NEW_INSTALL) != 0 && disabledPkgSetting == null
-                        && pkgSetting != null && pkgSetting.isSystem()
-                        ? pkgSetting
-                        : disabledPkgSetting;
-        if (systemPkgSetting != null)  {
-            // updated system application, must at least have SCAN_AS_SYSTEM
-            scanFlags |= SCAN_AS_SYSTEM;
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
-                scanFlags |= SCAN_AS_PRIVILEGED;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
-                scanFlags |= SCAN_AS_OEM;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
-                scanFlags |= SCAN_AS_VENDOR;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0) {
-                scanFlags |= SCAN_AS_PRODUCT;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
-                scanFlags |= SCAN_AS_SYSTEM_EXT;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
-                scanFlags |= SCAN_AS_ODM;
-            }
-        }
-        if (pkgSetting != null) {
-            final int userId = ((user == null) ? 0 : user.getIdentifier());
-            if (pkgSetting.getInstantApp(userId)) {
-                scanFlags |= SCAN_AS_INSTANT_APP;
-            }
-            if (pkgSetting.getVirtualPreload(userId)) {
-                scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
-            }
-        }
-
-        // Scan as privileged apps that share a user with a priv-app.
-        final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0)
-                && getVendorPartitionVersion() < 28;
-        if (((scanFlags & SCAN_AS_PRIVILEGED) == 0)
-                && !pkg.isPrivileged()
-                && (pkg.getSharedUserId() != null)
-                && !skipVendorPrivilegeScan) {
-            SharedUserSetting sharedUserSetting = null;
-            try {
-                sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(), 0,
-                        0, false);
-            } catch (PackageManagerException ignore) {
-            }
-            if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
-                // Exempt SharedUsers signed with the platform key.
-                // TODO(b/72378145) Fix this exemption. Force signature apps
-                // to allowlist their privileged permissions just like other
-                // priv-apps.
-                synchronized (mPm.mLock) {
-                    PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
-                    if ((compareSignatures(
-                            platformPkgSetting.getSigningDetails().getSignatures(),
-                            pkg.getSigningDetails().getSignatures())
-                            != PackageManager.SIGNATURE_MATCH)) {
-                        scanFlags |= SCAN_AS_PRIVILEGED;
-                    }
-                }
-            }
-        }
-
-        return scanFlags;
-    }
-
-    public Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
-            @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
-            @Nullable UserHandle user) throws PackageManagerException {
-        final boolean scanSystemPartition =
-                (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
-        final String renamedPkgName;
-        final PackageSetting disabledPkgSetting;
-        final boolean isSystemPkgUpdated;
-        final boolean pkgAlreadyExists;
-        PackageSetting pkgSetting;
-        AndroidPackage platformPackage;
-        final boolean isUpgrade = mPm.isDeviceUpgrading();
-
-        synchronized (mPm.mLock) {
-            platformPackage = mPm.getPlatformPackage();
-            renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
-                    AndroidPackageUtils.getRealPackageOrNull(parsedPackage));
-            final String realPkgName = getRealPackageName(parsedPackage,
-                    renamedPkgName);
-            if (realPkgName != null) {
-                ensurePackageRenamed(parsedPackage, renamedPkgName);
-            }
-            final PackageSetting originalPkgSetting = getOriginalPackageLocked(parsedPackage,
-                    renamedPkgName);
-            final PackageSetting installedPkgSetting = mPm.mSettings.getPackageLPr(
-                    parsedPackage.getPackageName());
-            pkgSetting = originalPkgSetting == null ? installedPkgSetting : originalPkgSetting;
-            pkgAlreadyExists = pkgSetting != null;
-            final String disabledPkgName = pkgAlreadyExists
-                    ? pkgSetting.getPackageName() : parsedPackage.getPackageName();
-            if (scanSystemPartition && !pkgAlreadyExists
-                    && mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName) != null) {
-                // The updated-package data for /system apk remains inconsistently
-                // after the package data for /data apk is lost accidentally.
-                // To recover it, enable /system apk and install it as non-updated system app.
-                Slog.w(TAG, "Inconsistent package setting of updated system app for "
-                        + disabledPkgName + ". To recover it, enable the system app"
-                        + "and install it as non-updated system app.");
-                mPm.mSettings.removeDisabledSystemPackageLPw(disabledPkgName);
-            }
-            disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName);
-            isSystemPkgUpdated = disabledPkgSetting != null;
-
-            if (DEBUG_INSTALL && isSystemPkgUpdated) {
-                Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
-            }
-
-            final SharedUserSetting sharedUserSetting = (parsedPackage.getSharedUserId() != null)
-                    ? mPm.mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
-                    0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
-                    : null;
-            if (DEBUG_PACKAGE_SCANNING
-                    && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0
-                    && sharedUserSetting != null) {
-                Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
-                        + " (uid=" + sharedUserSetting.userId + "):"
-                        + " packages=" + sharedUserSetting.packages);
-            }
-
-            if (scanSystemPartition) {
-                if (isSystemPkgUpdated) {
-                    // we're updating the disabled package, so, scan it as the package setting
-                    boolean isPlatformPackage = platformPackage != null
-                            && platformPackage.getPackageName().equals(
-                            parsedPackage.getPackageName());
-                    final ScanRequest request = new ScanRequest(parsedPackage, sharedUserSetting,
-                            null, disabledPkgSetting /* pkgSetting */,
-                            null /* disabledPkgSetting */, null /* originalPkgSetting */,
-                            null, parseFlags, scanFlags, isPlatformPackage, user, null);
-                    applyPolicy(parsedPackage, scanFlags,
-                            platformPackage, true);
-                    final ScanResult scanResult =
-                            scanPackageOnlyLI(request, mInjector,
-                                    mPm.mFactoryTest, -1L);
-                    if (scanResult.mExistingSettingCopied
-                            && scanResult.mRequest.mPkgSetting != null) {
-                        scanResult.mRequest.mPkgSetting.updateFrom(scanResult.mPkgSetting);
-                    }
-                }
-            }
-        }
-
-        final boolean newPkgChangedPaths = pkgAlreadyExists
-                && !pkgSetting.getPathString().equals(parsedPackage.getPath());
-        final boolean newPkgVersionGreater = pkgAlreadyExists
-                && parsedPackage.getLongVersionCode() > pkgSetting.getVersionCode();
-        final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
-                && newPkgChangedPaths && newPkgVersionGreater;
-        if (isSystemPkgBetter) {
-            // The version of the application on /system is greater than the version on
-            // /data. Switch back to the application on /system.
-            // It's safe to assume the application on /system will correctly scan. If not,
-            // there won't be a working copy of the application.
-            synchronized (mPm.mLock) {
-                // just remove the loaded entries from package lists
-                mPm.mPackages.remove(pkgSetting.getPackageName());
-            }
-
-            logCriticalInfo(Log.WARN,
-                    "System package updated;"
-                            + " name: " + pkgSetting.getPackageName()
-                            + "; " + pkgSetting.getVersionCode() + " --> "
-                            + parsedPackage.getLongVersionCode()
-                            + "; " + pkgSetting.getPathString()
-                            + " --> " + parsedPackage.getPath());
-
-            final InstallArgs args = new FileInstallArgs(
-                    pkgSetting.getPathString(), getAppDexInstructionSets(
-                    pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()), mPm);
-            args.cleanUpResourcesLI();
-            synchronized (mPm.mLock) {
-                mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
-            }
-        }
-
-        // The version of the application on the /system partition is less than or
-        // equal to the version on the /data partition. Throw an exception and use
-        // the application already installed on the /data partition.
-        if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
-            // In the case of a skipped package, commitReconciledScanResultLocked is not called to
-            // add the object to the "live" data structures, so this is the final mutation step
-            // for the package. Which means it needs to be finalized here to cache derived fields.
-            // This is relevant for cases where the disabled system package is used for flags or
-            // other metadata.
-            parsedPackage.hideAsFinal();
-            throw new PackageManagerException(Log.WARN, "Package " + parsedPackage.getPackageName()
-                    + " at " + parsedPackage.getPath() + " ignored: updated version "
-                    + (pkgAlreadyExists ? String.valueOf(pkgSetting.getVersionCode()) : "unknown")
-                    + " better than this " + parsedPackage.getLongVersionCode());
-        }
-
-        // Verify certificates against what was last scanned. Force re-collecting certificate in two
-        // special cases:
-        // 1) when scanning system, force re-collect only if system is upgrading.
-        // 2) when scannning /data, force re-collect only if the app is privileged (updated from
-        // preinstall, or treated as privileged, e.g. due to shared user ID).
-        final boolean forceCollect = scanSystemPartition ? isUpgrade
-                : PackageManagerServiceUtils.isApkVerificationForced(pkgSetting);
-        if (DEBUG_VERIFY && forceCollect) {
-            Slog.d(TAG, "Force collect certificate of " + parsedPackage.getPackageName());
-        }
-
-        // Full APK verification can be skipped during certificate collection, only if the file is
-        // in verified partition, or can be verified on access (when apk verity is enabled). In both
-        // cases, only data in Signing Block is verified instead of the whole file.
-        // TODO(b/136132412): skip for Incremental installation
-        final boolean skipVerify = scanSystemPartition
-                || (forceCollect && canSkipForcedPackageVerification(parsedPackage));
-        collectCertificatesLI(pkgSetting, parsedPackage, forceCollect, skipVerify);
-
-        // Reset profile if the application version is changed
-        maybeClearProfilesForUpgradesLI(pkgSetting, parsedPackage);
-
-        /*
-         * A new system app appeared, but we already had a non-system one of the
-         * same name installed earlier.
-         */
-        boolean shouldHideSystemApp = false;
-        // A new application appeared on /system, but, we already have a copy of
-        // the application installed on /data.
-        if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
-                && !pkgSetting.isSystem()) {
-
-            if (!parsedPackage.getSigningDetails()
-                    .checkCapability(pkgSetting.getSigningDetails(),
-                            SigningDetails.CertCapabilities.INSTALLED_DATA)
-                    && !pkgSetting.getSigningDetails().checkCapability(
-                    parsedPackage.getSigningDetails(),
-                    SigningDetails.CertCapabilities.ROLLBACK)) {
-                logCriticalInfo(Log.WARN,
-                        "System package signature mismatch;"
-                                + " name: " + pkgSetting.getPackageName());
-                try (@SuppressWarnings("unused") PackageFreezer freezer = mPm.freezePackage(
-                        parsedPackage.getPackageName(),
-                        "scanPackageInternalLI")) {
-                    DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
-                    deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
-                            mPm.mUserManager.getUserIds(), 0, null, false);
-                }
-                pkgSetting = null;
-            } else if (newPkgVersionGreater) {
-                // 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.
-                logCriticalInfo(Log.WARN,
-                        "System package enabled;"
-                                + " name: " + pkgSetting.getPackageName()
-                                + "; " + pkgSetting.getVersionCode() + " --> "
-                                + parsedPackage.getLongVersionCode()
-                                + "; " + pkgSetting.getPathString() + " --> "
-                                + parsedPackage.getPath());
-                InstallArgs args = new FileInstallArgs(
-                        pkgSetting.getPathString(), getAppDexInstructionSets(
-                        pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()),
-                        mPm);
-                synchronized (mPm.mInstallLock) {
-                    args.cleanUpResourcesLI();
-                }
-            } else {
-                // The application on /system is older than the application on /data. Hide
-                // the application on /system and the version on /data will be scanned later
-                // and re-added like an update.
-                shouldHideSystemApp = true;
-                logCriticalInfo(Log.INFO,
-                        "System package disabled;"
-                                + " name: " + pkgSetting.getPackageName()
-                                + "; old: " + pkgSetting.getPathString() + " @ "
-                                + pkgSetting.getVersionCode()
-                                + "; new: " + parsedPackage.getPath() + " @ "
-                                + parsedPackage.getPath());
-            }
-        }
-
-        final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
-                scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null);
-        return new Pair<>(scanResult, shouldHideSystemApp);
-    }
-
-    /**
-     * Returns if forced apk verification can be skipped for the whole package, including splits.
-     */
-    private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
-        final String packageName = pkg.getPackageName();
-        if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
-            return false;
-        }
-        // TODO: Allow base and splits to be verified individually.
-        String[] splitCodePaths = pkg.getSplitCodePaths();
-        if (!ArrayUtils.isEmpty(splitCodePaths)) {
-            for (int i = 0; i < splitCodePaths.length; i++) {
-                if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Returns if forced apk verification can be skipped, depending on current FSVerity setup and
-     * whether the apk contains signed root hash.  Note that the signer's certificate still needs to
-     * match one in a trusted source, and should be done separately.
-     */
-    private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
-        if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
-            return VerityUtils.hasFsverity(apkPath);
-        }
-
-        try {
-            final byte[] rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
-            if (rootHashObserved == null) {
-                return false;  // APK does not contain Merkle tree root hash.
-            }
-            synchronized (mPm.mInstallLock) {
-                // Returns whether the observed root hash matches what kernel has.
-                mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
-                        rootHashObserved);
-                return true;
-            }
-        } catch (Installer.InstallerException | IOException | DigestException
-                | NoSuchAlgorithmException e) {
-            Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e);
-        }
-        return false;
-    }
-
-    private void collectCertificatesLI(PackageSetting ps, ParsedPackage parsedPackage,
-            boolean forceCollect, boolean skipVerify) throws PackageManagerException {
-        // When upgrading from pre-N MR1, verify the package time stamp using the package
-        // directory and not the APK file.
-        final long lastModifiedTime = mPm.isPreNMR1Upgrade()
-                ? new File(parsedPackage.getPath()).lastModified()
-                : getLastModifiedTime(parsedPackage);
-        final Settings.VersionInfo settingsVersionForPackage =
-                mPm.getSettingsVersionForPackage(parsedPackage);
-        if (ps != null && !forceCollect
-                && ps.getPathString().equals(parsedPackage.getPath())
-                && ps.getLastModifiedTime() == lastModifiedTime
-                && !InstallPackageHelper.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
-                && !InstallPackageHelper.isRecoverSignatureUpdateNeeded(
-                settingsVersionForPackage)) {
-            if (ps.getSigningDetails().getSignatures() != null
-                    && ps.getSigningDetails().getSignatures().length != 0
-                    && ps.getSigningDetails().getSignatureSchemeVersion()
-                    != SigningDetails.SignatureSchemeVersion.UNKNOWN) {
-                // Optimization: reuse the existing cached signing data
-                // if the package appears to be unchanged.
-                parsedPackage.setSigningDetails(
-                        new SigningDetails(ps.getSigningDetails()));
-                return;
-            }
-
-            Slog.w(TAG, "PackageSetting for " + ps.getPackageName()
-                    + " is missing signatures.  Collecting certs again to recover them.");
-        } else {
-            Slog.i(TAG, parsedPackage.getPath() + " changed; collecting certs"
-                    + (forceCollect ? " (forced)" : ""));
-        }
-
-        try {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
-            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-            final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
-                    input, parsedPackage, skipVerify);
-            if (result.isError()) {
-                throw new PackageManagerException(
-                        result.getErrorCode(), result.getErrorMessage(), result.getException());
-            }
-            parsedPackage.setSigningDetails(result.getResult());
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
-    /**
-     * Clear the package profile if this was an upgrade and the package
-     * version was updated.
-     */
-    private void maybeClearProfilesForUpgradesLI(
-            @Nullable PackageSetting originalPkgSetting,
-            @NonNull AndroidPackage pkg) {
-        if (originalPkgSetting == null || !mPm.isDeviceUpgrading()) {
-            return;
-        }
-        if (originalPkgSetting.getVersionCode() == pkg.getLongVersionCode()) {
-            return;
-        }
-
-        final AppDataHelper appDataHelper = new AppDataHelper(mPm);
-        appDataHelper.clearAppProfilesLIF(pkg);
-        if (DEBUG_INSTALL) {
-            Slog.d(TAG, originalPkgSetting.getPackageName()
-                    + " clear profile due to version change "
-                    + originalPkgSetting.getVersionCode() + " != "
-                    + pkg.getLongVersionCode());
-        }
-    }
-
-    /**
-     * Returns the original package setting.
-     * <p>A package can migrate its name during an update. In this scenario, a package
-     * designates a set of names that it considers as one of its original names.
-     * <p>An original package must be signed identically and it must have the same
-     * shared user [if any].
-     */
-    @GuardedBy("mPm.mLock")
-    @Nullable
-    private PackageSetting getOriginalPackageLocked(@NonNull AndroidPackage pkg,
-            @Nullable String renamedPkgName) {
-        if (isPackageRenamed(pkg, renamedPkgName)) {
-            return null;
-        }
-        for (int i = ArrayUtils.size(pkg.getOriginalPackages()) - 1; i >= 0; --i) {
-            final PackageSetting originalPs =
-                    mPm.mSettings.getPackageLPr(pkg.getOriginalPackages().get(i));
-            if (originalPs != null) {
-                // the package is already installed under its original name...
-                // but, should we use it?
-                if (!verifyPackageUpdateLPr(originalPs, pkg)) {
-                    // the new package is incompatible with the original
-                    continue;
-                } else if (originalPs.getSharedUser() != null) {
-                    if (!originalPs.getSharedUser().name.equals(pkg.getSharedUserId())) {
-                        // the shared user id is incompatible with the original
-                        Slog.w(TAG, "Unable to migrate data from " + originalPs.getPackageName()
-                                + " to " + pkg.getPackageName() + ": old uid "
-                                + originalPs.getSharedUser().name
-                                + " differs from " + pkg.getSharedUserId());
-                        continue;
-                    }
-                    // TODO: Add case when shared user id is added [b/28144775]
-                } else {
-                    if (DEBUG_UPGRADE) {
-                        Log.v(TAG, "Renaming new package "
-                                + pkg.getPackageName() + " to old name "
-                                + originalPs.getPackageName());
-                    }
-                }
-                return originalPs;
-            }
-        }
-        return null;
-    }
-
-    @GuardedBy("mPm.mLock")
-    private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, AndroidPackage newPkg) {
-        if ((oldPkg.getFlags() & ApplicationInfo.FLAG_SYSTEM) == 0) {
-            Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName()
-                    + " to " + newPkg.getPackageName()
-                    + ": old package not in system partition");
-            return false;
-        } else if (mPm.mPackages.get(oldPkg.getPackageName()) != null) {
-            Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName()
-                    + " to " + newPkg.getPackageName()
-                    + ": old package still exists");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Asserts the parsed package is valid according to the given policy. If the
-     * package is invalid, for whatever reason, throws {@link PackageManagerException}.
-     * <p>
-     * Implementation detail: This method must NOT have any side effects. It would
-     * ideally be static, but, it requires locks to read system state.
-     *
-     * @throws PackageManagerException If the package fails any of the validation checks
-     */
-    private void assertPackageIsValid(AndroidPackage pkg,
-            final @ParsingPackageUtils.ParseFlags int parseFlags,
-            final @PackageManagerService.ScanFlags int scanFlags)
-            throws PackageManagerException {
-        if ((parseFlags & ParsingPackageUtils.PARSE_ENFORCE_CODE) != 0) {
-            assertCodePolicy(pkg);
-        }
-
-        if (pkg.getPath() == null) {
-            // Bail out. The resource and code paths haven't been set.
-            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                    "Code and resource paths haven't been set correctly");
-        }
-
-        // Check that there is an APEX package with the same name only during install/first boot
-        // after OTA.
-        final boolean isUserInstall = (scanFlags & SCAN_BOOTING) == 0;
-        final boolean isFirstBootOrUpgrade = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
-        if ((isUserInstall || isFirstBootOrUpgrade)
-                && mPm.mApexManager.isApexPackage(pkg.getPackageName())) {
-            throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
-                    pkg.getPackageName()
-                            + " is an APEX package and can't be installed as an APK.");
-        }
-
-        // Make sure we're not adding any bogus keyset info
-        final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
-        ksms.assertScannedPackageValid(pkg);
-
-        synchronized (mPm.mLock) {
-            // The special "android" package can only be defined once
-            if (pkg.getPackageName().equals("android")) {
-                if (mPm.getCoreAndroidApplication() != null) {
-                    Slog.w(TAG, "*************************************************");
-                    Slog.w(TAG, "Core android package being redefined.  Skipping.");
-                    Slog.w(TAG, " codePath=" + pkg.getPath());
-                    Slog.w(TAG, "*************************************************");
-                    throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
-                            "Core android package being redefined.  Skipping.");
-                }
-            }
-
-            // A package name must be unique; don't allow duplicates
-            if ((scanFlags & SCAN_NEW_INSTALL) == 0
-                    && mPm.mPackages.containsKey(pkg.getPackageName())) {
-                throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
-                        "Application package " + pkg.getPackageName()
-                                + " already installed.  Skipping duplicate.");
-            }
-
-            if (pkg.isStaticSharedLibrary()) {
-                // Static libs have a synthetic package name containing the version
-                // but we still want the base name to be unique.
-                if ((scanFlags & SCAN_NEW_INSTALL) == 0
-                        && mPm.mPackages.containsKey(pkg.getManifestPackageName())) {
-                    throw new PackageManagerException(
-                            "Duplicate static shared lib provider package");
-                }
-
-                // Static shared libraries should have at least O target SDK
-                if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.O) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs must target O SDK or higher");
-                }
-
-                // Package declaring static a shared lib cannot be instant apps
-                if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs cannot be instant apps");
-                }
-
-                // Package declaring static a shared lib cannot be renamed since the package
-                // name is synthetic and apps can't code around package manager internals.
-                if (!ArrayUtils.isEmpty(pkg.getOriginalPackages())) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs cannot be renamed");
-                }
-
-                // Package declaring static a shared lib cannot declare dynamic libs
-                if (!ArrayUtils.isEmpty(pkg.getLibraryNames())) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs cannot declare dynamic libs");
-                }
-
-                // Package declaring static a shared lib cannot declare shared users
-                if (pkg.getSharedUserId() != null) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs cannot declare shared users");
-                }
-
-                // Static shared libs cannot declare activities
-                if (!pkg.getActivities().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare activities");
-                }
-
-                // Static shared libs cannot declare services
-                if (!pkg.getServices().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare services");
-                }
-
-                // Static shared libs cannot declare providers
-                if (!pkg.getProviders().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare content providers");
-                }
-
-                // Static shared libs cannot declare receivers
-                if (!pkg.getReceivers().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare broadcast receivers");
-                }
-
-                // Static shared libs cannot declare permission groups
-                if (!pkg.getPermissionGroups().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare permission groups");
-                }
-
-                // Static shared libs cannot declare attributions
-                if (!pkg.getAttributions().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare features");
-                }
-
-                // Static shared libs cannot declare permissions
-                if (!pkg.getPermissions().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare permissions");
-                }
-
-                // Static shared libs cannot declare protected broadcasts
-                if (!pkg.getProtectedBroadcasts().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare protected broadcasts");
-                }
-
-                // Static shared libs cannot be overlay targets
-                if (pkg.getOverlayTarget() != null) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot be overlay targets");
-                }
-
-                // The version codes must be ordered as lib versions
-                long minVersionCode = Long.MIN_VALUE;
-                long maxVersionCode = Long.MAX_VALUE;
-
-                WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mPm.mSharedLibraries.get(
-                        pkg.getStaticSharedLibName());
-                if (versionedLib != null) {
-                    final int versionCount = versionedLib.size();
-                    for (int i = 0; i < versionCount; i++) {
-                        SharedLibraryInfo libInfo = versionedLib.valueAt(i);
-                        final long libVersionCode = libInfo.getDeclaringPackage()
-                                .getLongVersionCode();
-                        if (libInfo.getLongVersion() < pkg.getStaticSharedLibVersion()) {
-                            minVersionCode = Math.max(minVersionCode, libVersionCode + 1);
-                        } else if (libInfo.getLongVersion()
-                                > pkg.getStaticSharedLibVersion()) {
-                            maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1);
-                        } else {
-                            minVersionCode = maxVersionCode = libVersionCode;
-                            break;
-                        }
-                    }
-                }
-                if (pkg.getLongVersionCode() < minVersionCode
-                        || pkg.getLongVersionCode() > maxVersionCode) {
-                    throw new PackageManagerException("Static shared"
-                            + " lib version codes must be ordered as lib versions");
-                }
-            }
-
-            // If we're only installing presumed-existing packages, require that the
-            // scanned APK is both already known and at the path previously established
-            // for it.  Previously unknown packages we pick up normally, but if we have an
-            // a priori expectation about this package's install presence, enforce it.
-            // With a singular exception for new system packages. When an OTA contains
-            // a new system package, we allow the codepath to change from a system location
-            // to the user-installed location. If we don't allow this change, any newer,
-            // user-installed version of the application will be ignored.
-            if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
-                if (mPm.isExpectingBetter(pkg.getPackageName())) {
-                    Slog.w(TAG, "Relax SCAN_REQUIRE_KNOWN requirement for package "
-                            + pkg.getPackageName());
-                } else {
-                    PackageSetting known = mPm.mSettings.getPackageLPr(pkg.getPackageName());
-                    if (known != null) {
-                        if (DEBUG_PACKAGE_SCANNING) {
-                            Log.d(TAG, "Examining " + pkg.getPath()
-                                    + " and requiring known path " + known.getPathString());
-                        }
-                        if (!pkg.getPath().equals(known.getPathString())) {
-                            throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
-                                    "Application package " + pkg.getPackageName()
-                                            + " found at " + pkg.getPath()
-                                            + " but expected at " + known.getPathString()
-                                            + "; ignoring.");
-                        }
-                    } else {
-                        throw new PackageManagerException(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
-                                "Application package " + pkg.getPackageName()
-                                        + " not found; ignoring.");
-                    }
-                }
-            }
-
-            // Verify that this new package doesn't have any content providers
-            // that conflict with existing packages.  Only do this if the
-            // package isn't already installed, since we don't want to break
-            // things that are installed.
-            if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
-                mPm.mComponentResolver.assertProvidersNotDefined(pkg);
-            }
-
-            // If this package has defined explicit processes, then ensure that these are
-            // the only processes used by its components.
-            final Map<String, ParsedProcess> procs = pkg.getProcesses();
-            if (!procs.isEmpty()) {
-                if (!procs.containsKey(pkg.getProcessName())) {
-                    throw new PackageManagerException(
-                            INSTALL_FAILED_PROCESS_NOT_DEFINED,
-                            "Can't install because application tag's process attribute "
-                                    + pkg.getProcessName()
-                                    + " (in package " + pkg.getPackageName()
-                                    + ") is not included in the <processes> list");
-                }
-                assertPackageProcesses(pkg, pkg.getActivities(), procs, "activity");
-                assertPackageProcesses(pkg, pkg.getServices(), procs, "service");
-                assertPackageProcesses(pkg, pkg.getReceivers(), procs, "receiver");
-                assertPackageProcesses(pkg, pkg.getProviders(), procs, "provider");
-            }
-
-            // Verify that packages sharing a user with a privileged app are marked as privileged.
-            if (!pkg.isPrivileged() && (pkg.getSharedUserId() != null)) {
-                SharedUserSetting sharedUserSetting = null;
-                try {
-                    sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(),
-                            0, 0, false);
-                } catch (PackageManagerException ignore) {
-                }
-                if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
-                    // Exempt SharedUsers signed with the platform key.
-                    PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
-                    if (!comparePackageSignatures(platformPkgSetting,
-                            pkg.getSigningDetails().getSignatures())) {
-                        throw new PackageManagerException("Apps that share a user with a "
-                                + "privileged app must themselves be marked as privileged. "
-                                + pkg.getPackageName() + " shares privileged user "
-                                + pkg.getSharedUserId() + ".");
-                    }
-                }
-            }
-
-            // Apply policies specific for runtime resource overlays (RROs).
-            if (pkg.getOverlayTarget() != null) {
-                // System overlays have some restrictions on their use of the 'static' state.
-                if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
-                    // We are scanning a system overlay. This can be the first scan of the
-                    // system/vendor/oem partition, or an update to the system overlay.
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                        // This must be an update to a system overlay. Immutable overlays cannot be
-                        // upgraded.
-                        if (!mPm.isOverlayMutable(pkg.getPackageName())) {
-                            throw new PackageManagerException("Overlay "
-                                    + pkg.getPackageName()
-                                    + " is static and cannot be upgraded.");
-                        }
-                    } else {
-                        if ((scanFlags & SCAN_AS_VENDOR) != 0) {
-                            if (pkg.getTargetSdkVersion() < getVendorPartitionVersion()) {
-                                Slog.w(TAG, "System overlay " + pkg.getPackageName()
-                                        + " targets an SDK below the required SDK level of vendor"
-                                        + " overlays (" + getVendorPartitionVersion() + ")."
-                                        + " This will become an install error in a future release");
-                            }
-                        } else if (pkg.getTargetSdkVersion() < Build.VERSION.SDK_INT) {
-                            Slog.w(TAG, "System overlay " + pkg.getPackageName()
-                                    + " targets an SDK below the required SDK level of system"
-                                    + " overlays (" + Build.VERSION.SDK_INT + ")."
-                                    + " This will become an install error in a future release");
-                        }
-                    }
-                } else {
-                    // A non-preloaded overlay packages must have targetSdkVersion >= Q, or be
-                    // signed with the platform certificate. Check this in increasing order of
-                    // computational cost.
-                    if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.Q) {
-                        final PackageSetting platformPkgSetting =
-                                mPm.mSettings.getPackageLPr("android");
-                        if (!comparePackageSignatures(platformPkgSetting,
-                                pkg.getSigningDetails().getSignatures())) {
-                            throw new PackageManagerException("Overlay "
-                                    + pkg.getPackageName()
-                                    + " must target Q or later, "
-                                    + "or be signed with the platform certificate");
-                        }
-                    }
-
-                    // A non-preloaded overlay package, without <overlay android:targetName>, will
-                    // only be used if it is signed with the same certificate as its target OR if
-                    // it is signed with the same certificate as a reference package declared
-                    // in 'overlay-config-signature' tag of SystemConfig.
-                    // If the target is already installed or 'overlay-config-signature' tag in
-                    // SystemConfig is set, check this here to augment the last line of defense
-                    // which is OMS.
-                    if (pkg.getOverlayTargetOverlayableName() == null) {
-                        final PackageSetting targetPkgSetting =
-                                mPm.mSettings.getPackageLPr(pkg.getOverlayTarget());
-                        if (targetPkgSetting != null) {
-                            if (!comparePackageSignatures(targetPkgSetting,
-                                    pkg.getSigningDetails().getSignatures())) {
-                                // check reference signature
-                                if (mPm.mOverlayConfigSignaturePackage == null) {
-                                    throw new PackageManagerException("Overlay "
-                                            + pkg.getPackageName() + " and target "
-                                            + pkg.getOverlayTarget() + " signed with"
-                                            + " different certificates, and the overlay lacks"
-                                            + " <overlay android:targetName>");
-                                }
-                                final PackageSetting refPkgSetting =
-                                        mPm.mSettings.getPackageLPr(
-                                                mPm.mOverlayConfigSignaturePackage);
-                                if (!comparePackageSignatures(refPkgSetting,
-                                        pkg.getSigningDetails().getSignatures())) {
-                                    throw new PackageManagerException("Overlay "
-                                            + pkg.getPackageName() + " signed with a different "
-                                            + "certificate than both the reference package and "
-                                            + "target " + pkg.getOverlayTarget() + ", and the "
-                                            + "overlay lacks <overlay android:targetName>");
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            // If the package is not on a system partition ensure it is signed with at least the
-            // minimum signature scheme version required for its target SDK.
-            if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                int minSignatureSchemeVersion =
-                        ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
-                                pkg.getTargetSdkVersion());
-                if (pkg.getSigningDetails().getSignatureSchemeVersion()
-                        < minSignatureSchemeVersion) {
-                    throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                            "No signature found in package of version " + minSignatureSchemeVersion
-                                    + " or newer for package " + pkg.getPackageName());
-                }
-            }
-        }
-    }
-
-    private static <T extends ParsedMainComponent> void assertPackageProcesses(AndroidPackage pkg,
-            List<T> components, Map<String, ParsedProcess> procs, String compName)
-            throws PackageManagerException {
-        if (components == null) {
-            return;
-        }
-        for (int i = components.size() - 1; i >= 0; i--) {
-            final ParsedMainComponent component = components.get(i);
-            if (!procs.containsKey(component.getProcessName())) {
-                throw new PackageManagerException(
-                        INSTALL_FAILED_PROCESS_NOT_DEFINED,
-                        "Can't install because " + compName + " " + component.getClassName()
-                                + "'s process attribute " + component.getProcessName()
-                                + " (in package " + pkg.getPackageName()
-                                + ") is not included in the <processes> list");
-            }
-        }
-    }
-
-    /**
-     * Applies the adjusted ABI calculated by
-     * {@link PackageAbiHelper#getAdjustedAbiForSharedUser(Set, AndroidPackage)} to all
-     * relevant packages and settings.
-     * @param sharedUserSetting The {@code SharedUserSetting} to adjust
-     * @param scannedPackage the package being scanned or null
-     * @param adjustedAbi the adjusted ABI calculated by {@link PackageAbiHelper}
-     * @return the list of code paths that belong to packages that had their ABIs adjusted.
-     */
-    public static List<String> applyAdjustedAbiToSharedUser(SharedUserSetting sharedUserSetting,
-            ParsedPackage scannedPackage, String adjustedAbi) {
-        if (scannedPackage != null)  {
-            scannedPackage.setPrimaryCpuAbi(adjustedAbi);
-        }
-        List<String> changedAbiCodePath = null;
-        for (PackageSetting ps : sharedUserSetting.packages) {
-            if (scannedPackage == null
-                    || !scannedPackage.getPackageName().equals(ps.getPackageName())) {
-                if (ps.getPrimaryCpuAbi() != null) {
-                    continue;
-                }
-
-                ps.setPrimaryCpuAbi(adjustedAbi);
-                if (ps.getPkg() != null) {
-                    if (!TextUtils.equals(adjustedAbi,
-                            AndroidPackageUtils.getRawPrimaryCpuAbi(ps.getPkg()))) {
-                        if (DEBUG_ABI_SELECTION) {
-                            Slog.i(TAG,
-                                    "Adjusting ABI for " + ps.getPackageName() + " to "
-                                            + adjustedAbi + " (scannedPackage="
-                                            + (scannedPackage != null ? scannedPackage : "null")
-                                            + ")");
-                        }
-                        if (changedAbiCodePath == null) {
-                            changedAbiCodePath = new ArrayList<>();
-                        }
-                        changedAbiCodePath.add(ps.getPathString());
-                    }
-                }
-            }
-        }
-        return changedAbiCodePath;
-    }
-
-    /**
-     * Applies policy to the parsed package based upon the given policy flags.
-     * Ensures the package is in a good state.
-     * <p>
-     * Implementation detail: This method must NOT have any side effect. It would
-     * ideally be static, but, it requires locks to read system state.
-     */
-    private static void applyPolicy(ParsedPackage parsedPackage,
-            final @PackageManagerService.ScanFlags int scanFlags, AndroidPackage platformPkg,
-            boolean isUpdatedSystemApp) {
-        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
-            parsedPackage.setSystem(true);
-            // TODO(b/135203078): Can this be done in PackageParser? Or just inferred when the flag
-            //  is set during parse.
-            if (parsedPackage.isDirectBootAware()) {
-                parsedPackage.setAllComponentsDirectBootAware(true);
-            }
-            if (compressedFileExists(parsedPackage.getPath())) {
-                parsedPackage.setStub(true);
-            }
-        } else {
-            parsedPackage
-                    // Non system apps cannot mark any broadcast as protected
-                    .clearProtectedBroadcasts()
-                    // non system apps can't be flagged as core
-                    .setCoreApp(false)
-                    // clear flags not applicable to regular apps
-                    .setPersistent(false)
-                    .setDefaultToDeviceProtectedStorage(false)
-                    .setDirectBootAware(false)
-                    // non system apps can't have permission priority
-                    .capPermissionPriorities();
-        }
-        if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
-            parsedPackage
-                    .markNotActivitiesAsNotExportedIfSingleUser();
-        }
-
-        parsedPackage.setPrivileged((scanFlags & SCAN_AS_PRIVILEGED) != 0)
-                .setOem((scanFlags & SCAN_AS_OEM) != 0)
-                .setVendor((scanFlags & SCAN_AS_VENDOR) != 0)
-                .setProduct((scanFlags & SCAN_AS_PRODUCT) != 0)
-                .setSystemExt((scanFlags & SCAN_AS_SYSTEM_EXT) != 0)
-                .setOdm((scanFlags & SCAN_AS_ODM) != 0);
-
-        // Check if the package is signed with the same key as the platform package.
-        parsedPackage.setSignedWithPlatformKey(
-                (PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
-                        || (platformPkg != null && compareSignatures(
-                        platformPkg.getSigningDetails().getSignatures(),
-                        parsedPackage.getSigningDetails().getSignatures()
-                ) == PackageManager.SIGNATURE_MATCH))
-        );
-
-        if (!parsedPackage.isSystem()) {
-            // Only system apps can use these features.
-            parsedPackage.clearOriginalPackages()
-                    .clearAdoptPermissions();
-        }
-
-        PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isUpdatedSystemApp);
-    }
-
-    /**
-     * Enforces code policy for the package. This ensures that if an APK has
-     * declared hasCode="true" in its manifest that the APK actually contains
-     * code.
-     *
-     * @throws PackageManagerException If bytecode could not be found when it should exist
-     */
-    private static void assertCodePolicy(AndroidPackage pkg)
-            throws PackageManagerException {
-        final boolean shouldHaveCode = pkg.isHasCode();
-        if (shouldHaveCode && !apkHasCode(pkg.getBaseApkPath())) {
-            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                    "Package " + pkg.getBaseApkPath() + " code is missing");
-        }
-
-        if (!ArrayUtils.isEmpty(pkg.getSplitCodePaths())) {
-            for (int i = 0; i < pkg.getSplitCodePaths().length; i++) {
-                final boolean splitShouldHaveCode =
-                        (pkg.getSplitFlags()[i] & ApplicationInfo.FLAG_HAS_CODE) != 0;
-                if (splitShouldHaveCode && !apkHasCode(pkg.getSplitCodePaths()[i])) {
-                    throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                            "Package " + pkg.getSplitCodePaths()[i] + " code is missing");
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the "real" name of the package.
-     * <p>This may differ from the package's actual name if the application has already
-     * been installed under one of this package's original names.
-     */
-    private static @Nullable String getRealPackageName(@NonNull AndroidPackage pkg,
-            @Nullable String renamedPkgName) {
-        if (isPackageRenamed(pkg, renamedPkgName)) {
-            return AndroidPackageUtils.getRealPackageOrNull(pkg);
-        }
-        return null;
-    }
-
-    /** Returns {@code true} if the package has been renamed. Otherwise, {@code false}. */
-    private static boolean isPackageRenamed(@NonNull AndroidPackage pkg,
-            @Nullable String renamedPkgName) {
-        return pkg.getOriginalPackages().contains(renamedPkgName);
-    }
-
-    /**
-     * Renames the package if it was installed under a different name.
-     * <p>When we've already installed the package under an original name, update
-     * the new package so we can continue to have the old name.
-     */
-    private static void ensurePackageRenamed(@NonNull ParsedPackage parsedPackage,
-            @NonNull String renamedPackageName) {
-        if (!parsedPackage.getOriginalPackages().contains(renamedPackageName)
-                || parsedPackage.getPackageName().equals(renamedPackageName)) {
-            return;
-        }
-        parsedPackage.setPackageName(renamedPackageName);
-    }
-
-    /**
-     * Returns {@code true} if the given file contains code. Otherwise {@code false}.
-     */
-    private static boolean apkHasCode(String fileName) {
-        StrictJarFile jarFile = null;
-        try {
-            jarFile = new StrictJarFile(fileName,
-                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
-            return jarFile.findEntry("classes.dex") != null;
-        } catch (IOException ignore) {
-        } finally {
-            try {
-                if (jarFile != null) {
-                    jarFile.close();
-                }
-            } catch (IOException ignore) {
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Sets the enabled state of components configured through {@link SystemConfig}.
-     * This modifies the {@link PackageSetting} object.
-     *
-     * TODO(b/135203078): Move this to package parsing
-     **/
-    private static void configurePackageComponents(AndroidPackage pkg) {
-        final ArrayMap<String, Boolean> componentsEnabledStates = SystemConfig.getInstance()
-                .getComponentsEnabledStates(pkg.getPackageName());
-        if (componentsEnabledStates == null) {
-            return;
-        }
-
-        for (int i = ArrayUtils.size(pkg.getActivities()) - 1; i >= 0; i--) {
-            final ParsedActivity component = pkg.getActivities().get(i);
-            final Boolean enabled = componentsEnabledStates.get(component.getName());
-            if (enabled != null) {
-                ComponentMutateUtils.setEnabled(component, enabled);
-            }
-        }
-
-        for (int i = ArrayUtils.size(pkg.getReceivers()) - 1; i >= 0; i--) {
-            final ParsedActivity component = pkg.getReceivers().get(i);
-            final Boolean enabled = componentsEnabledStates.get(component.getName());
-            if (enabled != null) {
-                ComponentMutateUtils.setEnabled(component, enabled);
-            }
-        }
-
-        for (int i = ArrayUtils.size(pkg.getProviders()) - 1; i >= 0; i--) {
-            final ParsedProvider component = pkg.getProviders().get(i);
-            final Boolean enabled = componentsEnabledStates.get(component.getName());
-            if (enabled != null) {
-                ComponentMutateUtils.setEnabled(component, enabled);
-            }
-        }
-
-        for (int i = ArrayUtils.size(pkg.getServices()) - 1; i >= 0; i--) {
-            final ParsedService component = pkg.getServices().get(i);
-            final Boolean enabled = componentsEnabledStates.get(component.getName());
-            if (enabled != null) {
-                ComponentMutateUtils.setEnabled(component, enabled);
-            }
-        }
-    }
-
-    private static int getVendorPartitionVersion() {
-        final String version = SystemProperties.get("ro.vndk.version");
-        if (!version.isEmpty()) {
-            try {
-                return Integer.parseInt(version);
-            } catch (NumberFormatException ignore) {
-                if (ArrayUtils.contains(Build.VERSION.ACTIVE_CODENAMES, version)) {
-                    return Build.VERSION_CODES.CUR_DEVELOPMENT;
-                }
-            }
-        }
-        return Build.VERSION_CODES.P;
-    }
-}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
new file mode 100644
index 0000000..378c9e0
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_PROCESS_NOT_DEFINED;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_ABI_SELECTION;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_ODM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
+import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
+import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
+import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
+import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
+import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_TIME;
+import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
+import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
+import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.component.ComponentMutateUtils;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedMainComponent;
+import android.content.pm.parsing.component.ParsedProcess;
+import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.jar.StrictJarFile;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+
+import dalvik.system.VMRuntime;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Helper class that handles package scanning logic
+ */
+final class ScanPackageUtils {
+    /**
+     * Just scans the package without any side effects.
+     *
+     * @param injector injector for acquiring dependencies
+     * @param request Information about the package to be scanned
+     * @param isUnderFactoryTest Whether or not the device is under factory test
+     * @param currentTime The current time, in millis
+     * @return The results of the scan
+     */
+    @GuardedBy("mPm.mInstallLock")
+    @VisibleForTesting
+    @NonNull
+    public static ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
+            PackageManagerServiceInjector injector,
+            boolean isUnderFactoryTest, long currentTime)
+            throws PackageManagerException {
+        final PackageAbiHelper packageAbiHelper = injector.getAbiHelper();
+        ParsedPackage parsedPackage = request.mParsedPackage;
+        PackageSetting pkgSetting = request.mPkgSetting;
+        final PackageSetting disabledPkgSetting = request.mDisabledPkgSetting;
+        final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
+        final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
+        final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
+        final String realPkgName = request.mRealPkgName;
+        final SharedUserSetting sharedUserSetting = request.mSharedUserSetting;
+        final UserHandle user = request.mUser;
+        final boolean isPlatformPackage = request.mIsPlatformPackage;
+
+        List<String> changedAbiCodePath = null;
+
+        if (DEBUG_PACKAGE_SCANNING) {
+            if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
+                Log.d(TAG, "Scanning package " + parsedPackage.getPackageName());
+            }
+        }
+
+        // Initialize package source and resource directories
+        final File destCodeFile = new File(parsedPackage.getPath());
+
+        // We keep references to the derived CPU Abis from settings in oder to reuse
+        // them in the case where we're not upgrading or booting for the first time.
+        String primaryCpuAbiFromSettings = null;
+        String secondaryCpuAbiFromSettings = null;
+        boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
+        if (!needToDeriveAbi) {
+            if (pkgSetting != null) {
+                // TODO(b/154610922): if it is not first boot or upgrade, we should directly use
+                // API info from existing package setting. However, stub packages currently do not
+                // preserve ABI info, thus the special condition check here. Remove the special
+                // check after we fix the stub generation.
+                if (pkgSetting.getPkg() != null && pkgSetting.getPkg().isStub()) {
+                    needToDeriveAbi = true;
+                } else {
+                    primaryCpuAbiFromSettings = pkgSetting.getPrimaryCpuAbi();
+                    secondaryCpuAbiFromSettings = pkgSetting.getSecondaryCpuAbi();
+                }
+            } else {
+                // Re-scanning a system package after uninstalling updates; need to derive ABI
+                needToDeriveAbi = true;
+            }
+        }
+
+        int previousAppId = Process.INVALID_UID;
+
+        if (pkgSetting != null && pkgSetting.getSharedUser() != sharedUserSetting) {
+            if (pkgSetting.getSharedUser() != null && sharedUserSetting == null) {
+                previousAppId = pkgSetting.getAppId();
+                // Log that something is leaving shareduid and keep going
+                Slog.i(TAG,
+                        "Package " + parsedPackage.getPackageName() + " shared user changed from "
+                                + pkgSetting.getSharedUser().name + " to " + "<nothing>.");
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Package " + parsedPackage.getPackageName() + " shared user changed from "
+                                + (pkgSetting.getSharedUser() != null
+                                ? pkgSetting.getSharedUser().name : "<nothing>")
+                                + " to "
+                                + (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>")
+                                + "; replacing with new");
+                pkgSetting = null;
+            }
+        }
+
+        String[] usesSdkLibraries = null;
+        if (!parsedPackage.getUsesSdkLibraries().isEmpty()) {
+            usesSdkLibraries = new String[parsedPackage.getUsesSdkLibraries().size()];
+            parsedPackage.getUsesSdkLibraries().toArray(usesSdkLibraries);
+        }
+
+        String[] usesStaticLibraries = null;
+        if (!parsedPackage.getUsesStaticLibraries().isEmpty()) {
+            usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
+            parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
+        }
+
+        final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();
+
+        // TODO(b/135203078): Remove appInfoFlag usage in favor of individually assigned booleans
+        //  to avoid adding something that's unsupported due to lack of state, since it's called
+        //  with null.
+        final boolean createNewPackage = (pkgSetting == null);
+        if (createNewPackage) {
+            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
+            final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;
+
+            // Flags contain system values stored in the server variant of AndroidPackage,
+            // and so the server-side PackageInfoUtils is still called, even without a
+            // PackageSetting to pass in.
+            int pkgFlags = PackageInfoUtils.appInfoFlags(parsedPackage, null);
+            int pkgPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(parsedPackage, null);
+
+            // REMOVE SharedUserSetting from method; update in a separate call
+            pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName(),
+                    originalPkgSetting, disabledPkgSetting, realPkgName, sharedUserSetting,
+                    destCodeFile, parsedPackage.getNativeLibraryRootDir(),
+                    AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
+                    AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
+                    parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
+                    true /*allowInstall*/, instantApp, virtualPreload,
+                    UserManagerService.getInstance(), usesSdkLibraries,
+                    parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
+                    parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
+                    newDomainSetId);
+        } else {
+            // make a deep copy to avoid modifying any existing system state.
+            pkgSetting = new PackageSetting(pkgSetting);
+            pkgSetting.setPkg(parsedPackage);
+
+            // REMOVE SharedUserSetting from method; update in a separate call.
+            //
+            // TODO(narayan): This update is bogus. nativeLibraryDir & primaryCpuAbi,
+            // secondaryCpuAbi are not known at this point so we always update them
+            // to null here, only to reset them at a later point.
+            Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, sharedUserSetting,
+                    destCodeFile, parsedPackage.getNativeLibraryDir(),
+                    AndroidPackageUtils.getPrimaryCpuAbi(parsedPackage, pkgSetting),
+                    AndroidPackageUtils.getSecondaryCpuAbi(parsedPackage, pkgSetting),
+                    PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
+                    PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
+                    UserManagerService.getInstance(),
+                    usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
+                    usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
+                    parsedPackage.getMimeGroups(), newDomainSetId);
+        }
+        if (createNewPackage && originalPkgSetting != null) {
+            // This is the initial transition from the original package, so,
+            // fix up the new package's name now. We must do this after looking
+            // up the package under its new name, so getPackageLP takes care of
+            // fiddling things correctly.
+            parsedPackage.setPackageName(originalPkgSetting.getPackageName());
+
+            // File a report about this.
+            String msg = "New package " + pkgSetting.getRealName()
+                    + " renamed to replace old package " + pkgSetting.getPackageName();
+            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+        }
+
+        final int userId = (user == null ? UserHandle.USER_SYSTEM : user.getIdentifier());
+        // for existing packages, change the install state; but, only if it's explicitly specified
+        if (!createNewPackage) {
+            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
+            final boolean fullApp = (scanFlags & SCAN_AS_FULL_APP) != 0;
+            setInstantAppForUser(injector, pkgSetting, userId, instantApp, fullApp);
+        }
+        // TODO(patb): see if we can do away with disabled check here.
+        if (disabledPkgSetting != null
+                || (0 != (scanFlags & SCAN_NEW_INSTALL)
+                && pkgSetting != null && pkgSetting.isSystem())) {
+            pkgSetting.getPkgState().setUpdatedSystemApp(true);
+        }
+
+        parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
+                injector.getCompatibility()));
+
+        if (parsedPackage.isSystem()) {
+            configurePackageComponents(parsedPackage);
+        }
+
+        final String cpuAbiOverride = deriveAbiOverride(request.mCpuAbiOverride);
+        final boolean isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();
+
+        final File appLib32InstallDir = getAppLib32InstallDir();
+        if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
+            if (needToDeriveAbi) {
+                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
+                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
+                        packageAbiHelper.derivePackageAbi(parsedPackage, isUpdatedSystemApp,
+                                cpuAbiOverride, appLib32InstallDir);
+                derivedAbi.first.applyTo(parsedPackage);
+                derivedAbi.second.applyTo(parsedPackage);
+                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+
+                // Some system apps still use directory structure for native libraries
+                // in which case we might end up not detecting abi solely based on apk
+                // structure. Try to detect abi based on directory structure.
+
+                String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
+                if (parsedPackage.isSystem() && !isUpdatedSystemApp
+                        && pkgRawPrimaryCpuAbi == null) {
+                    final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
+                            parsedPackage);
+                    abis.applyTo(parsedPackage);
+                    abis.applyTo(pkgSetting);
+                    final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+                            packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+                                    isUpdatedSystemApp, appLib32InstallDir);
+                    nativeLibraryPaths.applyTo(parsedPackage);
+                }
+            } else {
+                // This is not a first boot or an upgrade, don't bother deriving the
+                // ABI during the scan. Instead, trust the value that was stored in the
+                // package setting.
+                parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
+                        .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
+
+                final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+                        packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+                                isUpdatedSystemApp, appLib32InstallDir);
+                nativeLibraryPaths.applyTo(parsedPackage);
+
+                if (DEBUG_ABI_SELECTION) {
+                    Slog.i(TAG, "Using ABIS and native lib paths from settings : "
+                            + parsedPackage.getPackageName() + " "
+                            + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
+                            + ", "
+                            + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
+                }
+            }
+        } else {
+            if ((scanFlags & SCAN_MOVE) != 0) {
+                // We haven't run dex-opt for this move (since we've moved the compiled output too)
+                // but we already have this packages package info in the PackageSetting. We just
+                // use that and derive the native library path based on the new code path.
+                parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbi())
+                        .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbi());
+            }
+
+            // Set native library paths again. For moves, the path will be updated based on the
+            // ABIs we've determined above. For non-moves, the path will be updated based on the
+            // ABIs we determined during compilation, but the path will depend on the final
+            // package path (after the rename away from the stage path).
+            final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+                    packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isUpdatedSystemApp,
+                            appLib32InstallDir);
+            nativeLibraryPaths.applyTo(parsedPackage);
+        }
+
+        // This is a special case for the "system" package, where the ABI is
+        // dictated by the zygote configuration (and init.rc). We should keep track
+        // of this ABI so that we can deal with "normal" applications that run under
+        // the same UID correctly.
+        if (isPlatformPackage) {
+            parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
+                    ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
+        }
+
+        // If there's a mismatch between the abi-override in the package setting
+        // and the abiOverride specified for the install. Warn about this because we
+        // would've already compiled the app without taking the package setting into
+        // account.
+        if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {
+            if (cpuAbiOverride == null) {
+                Slog.w(TAG, "Ignoring persisted ABI override for package "
+                        + parsedPackage.getPackageName());
+            }
+        }
+
+        pkgSetting.setPrimaryCpuAbi(AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage))
+                .setSecondaryCpuAbi(AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage))
+                .setCpuAbiOverride(cpuAbiOverride);
+
+        if (DEBUG_ABI_SELECTION) {
+            Slog.d(TAG, "Resolved nativeLibraryRoot for " + parsedPackage.getPackageName()
+                    + " to root=" + parsedPackage.getNativeLibraryRootDir()
+                    + ", to dir=" + parsedPackage.getNativeLibraryDir()
+                    + ", isa=" + parsedPackage.isNativeLibraryRootRequiresIsa());
+        }
+
+        // Push the derived path down into PackageSettings so we know what to
+        // clean up at uninstall time.
+        pkgSetting.setLegacyNativeLibraryPath(parsedPackage.getNativeLibraryRootDir());
+
+        if (DEBUG_ABI_SELECTION) {
+            Log.d(TAG, "Abis for package[" + parsedPackage.getPackageName() + "] are"
+                    + " primary=" + pkgSetting.getPrimaryCpuAbi()
+                    + " secondary=" + pkgSetting.getSecondaryCpuAbi()
+                    + " abiOverride=" + pkgSetting.getCpuAbiOverride());
+        }
+
+        if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.getSharedUser() != null) {
+            // We don't do this here during boot because we can do it all
+            // at once after scanning all existing packages.
+            //
+            // We also do this *before* we perform dexopt on this package, so that
+            // we can avoid redundant dexopts, and also to make sure we've got the
+            // code and package path correct.
+            changedAbiCodePath = applyAdjustedAbiToSharedUser(pkgSetting.getSharedUser(),
+                    parsedPackage, packageAbiHelper.getAdjustedAbiForSharedUser(
+                            pkgSetting.getSharedUser().packages, parsedPackage));
+        }
+
+        parsedPackage.setFactoryTest(isUnderFactoryTest && parsedPackage.getRequestedPermissions()
+                .contains(android.Manifest.permission.FACTORY_TEST));
+
+        if (parsedPackage.isSystem()) {
+            pkgSetting.setIsOrphaned(true);
+        }
+
+        // Take care of first install / last update times.
+        final long scanFileTime = getLastModifiedTime(parsedPackage);
+        if (currentTime != 0) {
+            if (pkgSetting.getFirstInstallTime() == 0) {
+                pkgSetting.setFirstInstallTime(currentTime)
+                        .setLastUpdateTime(currentTime);
+            } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
+                pkgSetting.setLastUpdateTime(currentTime);
+            }
+        } else if (pkgSetting.getFirstInstallTime() == 0) {
+            // We need *something*.  Take time time stamp of the file.
+            pkgSetting.setFirstInstallTime(scanFileTime)
+                    .setLastUpdateTime(scanFileTime);
+        } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) {
+            if (scanFileTime != pkgSetting.getLastModifiedTime()) {
+                // A package on the system image has changed; consider this
+                // to be an update.
+                pkgSetting.setLastUpdateTime(scanFileTime);
+            }
+        }
+        pkgSetting.setLastModifiedTime(scanFileTime);
+        // TODO(b/135203078): Remove, move to constructor
+        pkgSetting.setPkg(parsedPackage)
+                .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting))
+                .setPrivateFlags(
+                        PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
+        if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
+            pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
+        }
+        // Update volume if needed
+        final String volumeUuid = parsedPackage.getVolumeUuid();
+        if (!Objects.equals(volumeUuid, pkgSetting.getVolumeUuid())) {
+            Slog.i(PackageManagerService.TAG,
+                    "Update" + (pkgSetting.isSystem() ? " system" : "")
+                            + " package " + parsedPackage.getPackageName()
+                            + " volume from " + pkgSetting.getVolumeUuid()
+                            + " to " + volumeUuid);
+            pkgSetting.setVolumeUuid(volumeUuid);
+        }
+
+        SharedLibraryInfo sdkLibraryInfo = null;
+        if (!TextUtils.isEmpty(parsedPackage.getSdkLibName())) {
+            sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
+        }
+        SharedLibraryInfo staticSharedLibraryInfo = null;
+        if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibName())) {
+            staticSharedLibraryInfo =
+                    AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
+        }
+        List<SharedLibraryInfo> dynamicSharedLibraryInfos = null;
+        if (!ArrayUtils.isEmpty(parsedPackage.getLibraryNames())) {
+            dynamicSharedLibraryInfos = new ArrayList<>(parsedPackage.getLibraryNames().size());
+            for (String name : parsedPackage.getLibraryNames()) {
+                dynamicSharedLibraryInfos.add(
+                        AndroidPackageUtils.createSharedLibraryForDynamic(parsedPackage, name));
+            }
+        }
+
+        return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
+                !createNewPackage /* existingSettingCopied */,
+                previousAppId, sdkLibraryInfo, staticSharedLibraryInfo,
+                dynamicSharedLibraryInfos);
+    }
+
+    /**
+     * Returns the actual scan flags depending upon the state of the other settings.
+     * <p>Updated system applications will not have the following flags set
+     * by default and need to be adjusted after the fact:
+     * <ul>
+     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_PRIVILEGED}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_OEM}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_VENDOR}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_PRODUCT}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM_EXT}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_INSTANT_APP}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_ODM}</li>
+     * </ul>
+     */
+    public static @PackageManagerService.ScanFlags int adjustScanFlagsWithPackageSetting(
+            @PackageManagerService.ScanFlags int scanFlags,
+            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user) {
+
+        // TODO(patb): Do away entirely with disabledPkgSetting here. PkgSetting will always contain
+        // the correct isSystem value now that we don't disable system packages before scan.
+        final PackageSetting systemPkgSetting =
+                (scanFlags & SCAN_NEW_INSTALL) != 0 && disabledPkgSetting == null
+                        && pkgSetting != null && pkgSetting.isSystem()
+                        ? pkgSetting
+                        : disabledPkgSetting;
+        if (systemPkgSetting != null)  {
+            // updated system application, must at least have SCAN_AS_SYSTEM
+            scanFlags |= SCAN_AS_SYSTEM;
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+                scanFlags |= SCAN_AS_PRIVILEGED;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+                scanFlags |= SCAN_AS_OEM;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
+                scanFlags |= SCAN_AS_VENDOR;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0) {
+                scanFlags |= SCAN_AS_PRODUCT;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
+                scanFlags |= SCAN_AS_SYSTEM_EXT;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
+                scanFlags |= SCAN_AS_ODM;
+            }
+        }
+        if (pkgSetting != null) {
+            final int userId = ((user == null) ? 0 : user.getIdentifier());
+            if (pkgSetting.getInstantApp(userId)) {
+                scanFlags |= SCAN_AS_INSTANT_APP;
+            }
+            if (pkgSetting.getVirtualPreload(userId)) {
+                scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
+            }
+        }
+
+        return scanFlags;
+    }
+
+    /**
+     * Enforces code policy for the package. This ensures that if an APK has
+     * declared hasCode="true" in its manifest that the APK actually contains
+     * code.
+     *
+     * @throws PackageManagerException If bytecode could not be found when it should exist
+     */
+    public static void assertCodePolicy(AndroidPackage pkg)
+            throws PackageManagerException {
+        final boolean shouldHaveCode = pkg.isHasCode();
+        if (shouldHaveCode && !apkHasCode(pkg.getBaseApkPath())) {
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                    "Package " + pkg.getBaseApkPath() + " code is missing");
+        }
+
+        if (!ArrayUtils.isEmpty(pkg.getSplitCodePaths())) {
+            for (int i = 0; i < pkg.getSplitCodePaths().length; i++) {
+                final boolean splitShouldHaveCode =
+                        (pkg.getSplitFlags()[i] & ApplicationInfo.FLAG_HAS_CODE) != 0;
+                if (splitShouldHaveCode && !apkHasCode(pkg.getSplitCodePaths()[i])) {
+                    throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                            "Package " + pkg.getSplitCodePaths()[i] + " code is missing");
+                }
+            }
+        }
+    }
+
+    public static void assertStaticSharedLibraryIsValid(AndroidPackage pkg,
+            @PackageManagerService.ScanFlags int scanFlags) throws PackageManagerException {
+        // Static shared libraries should have at least O target SDK
+        if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.O) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs must target O SDK or higher");
+        }
+
+        // Package declaring static a shared lib cannot be instant apps
+        if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs cannot be instant apps");
+        }
+
+        // Package declaring static a shared lib cannot be renamed since the package
+        // name is synthetic and apps can't code around package manager internals.
+        if (!ArrayUtils.isEmpty(pkg.getOriginalPackages())) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs cannot be renamed");
+        }
+
+        // Package declaring static a shared lib cannot declare dynamic libs
+        if (!ArrayUtils.isEmpty(pkg.getLibraryNames())) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs cannot declare dynamic libs");
+        }
+
+        // Package declaring static a shared lib cannot declare shared users
+        if (pkg.getSharedUserId() != null) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs cannot declare shared users");
+        }
+
+        // Static shared libs cannot declare activities
+        if (!pkg.getActivities().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare activities");
+        }
+
+        // Static shared libs cannot declare services
+        if (!pkg.getServices().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare services");
+        }
+
+        // Static shared libs cannot declare providers
+        if (!pkg.getProviders().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare content providers");
+        }
+
+        // Static shared libs cannot declare receivers
+        if (!pkg.getReceivers().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare broadcast receivers");
+        }
+
+        // Static shared libs cannot declare permission groups
+        if (!pkg.getPermissionGroups().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare permission groups");
+        }
+
+        // Static shared libs cannot declare attributions
+        if (!pkg.getAttributions().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare features");
+        }
+
+        // Static shared libs cannot declare permissions
+        if (!pkg.getPermissions().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare permissions");
+        }
+
+        // Static shared libs cannot declare protected broadcasts
+        if (!pkg.getProtectedBroadcasts().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare protected broadcasts");
+        }
+
+        // Static shared libs cannot be overlay targets
+        if (pkg.getOverlayTarget() != null) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot be overlay targets");
+        }
+    }
+
+    public static void assertProcessesAreValid(AndroidPackage pkg) throws PackageManagerException {
+        final Map<String, ParsedProcess> procs = pkg.getProcesses();
+        if (!procs.isEmpty()) {
+            if (!procs.containsKey(pkg.getProcessName())) {
+                throw new PackageManagerException(
+                        INSTALL_FAILED_PROCESS_NOT_DEFINED,
+                        "Can't install because application tag's process attribute "
+                                + pkg.getProcessName()
+                                + " (in package " + pkg.getPackageName()
+                                + ") is not included in the <processes> list");
+            }
+            assertPackageProcesses(pkg, pkg.getActivities(), procs, "activity");
+            assertPackageProcesses(pkg, pkg.getServices(), procs, "service");
+            assertPackageProcesses(pkg, pkg.getReceivers(), procs, "receiver");
+            assertPackageProcesses(pkg, pkg.getProviders(), procs, "provider");
+        }
+    }
+
+    private static <T extends ParsedMainComponent> void assertPackageProcesses(AndroidPackage pkg,
+            List<T> components, Map<String, ParsedProcess> procs, String compName)
+            throws PackageManagerException {
+        if (components == null) {
+            return;
+        }
+        for (int i = components.size() - 1; i >= 0; i--) {
+            final ParsedMainComponent component = components.get(i);
+            if (!procs.containsKey(component.getProcessName())) {
+                throw new PackageManagerException(
+                        INSTALL_FAILED_PROCESS_NOT_DEFINED,
+                        "Can't install because " + compName + " " + component.getClassName()
+                                + "'s process attribute " + component.getProcessName()
+                                + " (in package " + pkg.getPackageName()
+                                + ") is not included in the <processes> list");
+            }
+        }
+    }
+
+    public static void assertMinSignatureSchemeIsValid(AndroidPackage pkg,
+            @ParsingPackageUtils.ParseFlags int parseFlags) throws PackageManagerException {
+        if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+            int minSignatureSchemeVersion =
+                    ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
+                            pkg.getTargetSdkVersion());
+            if (pkg.getSigningDetails().getSignatureSchemeVersion()
+                    < minSignatureSchemeVersion) {
+                throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No signature found in package of version " + minSignatureSchemeVersion
+                                + " or newer for package " + pkg.getPackageName());
+            }
+        }
+    }
+
+    /**
+     * Returns the "real" name of the package.
+     * <p>This may differ from the package's actual name if the application has already
+     * been installed under one of this package's original names.
+     */
+    public static @Nullable String getRealPackageName(@NonNull AndroidPackage pkg,
+            @Nullable String renamedPkgName) {
+        if (isPackageRenamed(pkg, renamedPkgName)) {
+            return AndroidPackageUtils.getRealPackageOrNull(pkg);
+        }
+        return null;
+    }
+
+    /** Returns {@code true} if the package has been renamed. Otherwise, {@code false}. */
+    public static boolean isPackageRenamed(@NonNull AndroidPackage pkg,
+            @Nullable String renamedPkgName) {
+        return pkg.getOriginalPackages().contains(renamedPkgName);
+    }
+
+    /**
+     * Renames the package if it was installed under a different name.
+     * <p>When we've already installed the package under an original name, update
+     * the new package so we can continue to have the old name.
+     */
+    public static void ensurePackageRenamed(@NonNull ParsedPackage parsedPackage,
+            @NonNull String renamedPackageName) {
+        if (!parsedPackage.getOriginalPackages().contains(renamedPackageName)
+                || parsedPackage.getPackageName().equals(renamedPackageName)) {
+            return;
+        }
+        parsedPackage.setPackageName(renamedPackageName);
+    }
+
+    /**
+     * Returns {@code true} if the given file contains code. Otherwise {@code false}.
+     */
+    public static boolean apkHasCode(String fileName) {
+        StrictJarFile jarFile = null;
+        try {
+            jarFile = new StrictJarFile(fileName,
+                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
+            return jarFile.findEntry("classes.dex") != null;
+        } catch (IOException ignore) {
+        } finally {
+            try {
+                if (jarFile != null) {
+                    jarFile.close();
+                }
+            } catch (IOException ignore) {
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sets the enabled state of components configured through {@link SystemConfig}.
+     * This modifies the {@link PackageSetting} object.
+     *
+     * TODO(b/135203078): Move this to package parsing
+     **/
+    public static void configurePackageComponents(AndroidPackage pkg) {
+        final ArrayMap<String, Boolean> componentsEnabledStates = SystemConfig.getInstance()
+                .getComponentsEnabledStates(pkg.getPackageName());
+        if (componentsEnabledStates == null) {
+            return;
+        }
+
+        for (int i = ArrayUtils.size(pkg.getActivities()) - 1; i >= 0; i--) {
+            final ParsedActivity component = pkg.getActivities().get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.getName());
+            if (enabled != null) {
+                ComponentMutateUtils.setEnabled(component, enabled);
+            }
+        }
+
+        for (int i = ArrayUtils.size(pkg.getReceivers()) - 1; i >= 0; i--) {
+            final ParsedActivity component = pkg.getReceivers().get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.getName());
+            if (enabled != null) {
+                ComponentMutateUtils.setEnabled(component, enabled);
+            }
+        }
+
+        for (int i = ArrayUtils.size(pkg.getProviders()) - 1; i >= 0; i--) {
+            final ParsedProvider component = pkg.getProviders().get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.getName());
+            if (enabled != null) {
+                ComponentMutateUtils.setEnabled(component, enabled);
+            }
+        }
+
+        for (int i = ArrayUtils.size(pkg.getServices()) - 1; i >= 0; i--) {
+            final ParsedService component = pkg.getServices().get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.getName());
+            if (enabled != null) {
+                ComponentMutateUtils.setEnabled(component, enabled);
+            }
+        }
+    }
+
+    public static int getVendorPartitionVersion() {
+        final String version = SystemProperties.get("ro.vndk.version");
+        if (!version.isEmpty()) {
+            try {
+                return Integer.parseInt(version);
+            } catch (NumberFormatException ignore) {
+                if (ArrayUtils.contains(Build.VERSION.ACTIVE_CODENAMES, version)) {
+                    return Build.VERSION_CODES.CUR_DEVELOPMENT;
+                }
+            }
+        }
+        return Build.VERSION_CODES.P;
+    }
+
+    /**
+     * Applies policy to the parsed package based upon the given policy flags.
+     * Ensures the package is in a good state.
+     * <p>
+     * Implementation detail: This method must NOT have any side effect. It would
+     * ideally be static, but, it requires locks to read system state.
+     */
+    public static void applyPolicy(ParsedPackage parsedPackage,
+            final @PackageManagerService.ScanFlags int scanFlags, AndroidPackage platformPkg,
+            boolean isUpdatedSystemApp) {
+        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
+            parsedPackage.setSystem(true);
+            // TODO(b/135203078): Can this be done in PackageParser? Or just inferred when the flag
+            //  is set during parse.
+            if (parsedPackage.isDirectBootAware()) {
+                parsedPackage.setAllComponentsDirectBootAware(true);
+            }
+            if (compressedFileExists(parsedPackage.getPath())) {
+                parsedPackage.setStub(true);
+            }
+        } else {
+            parsedPackage
+                    // Non system apps cannot mark any broadcast as protected
+                    .clearProtectedBroadcasts()
+                    // non system apps can't be flagged as core
+                    .setCoreApp(false)
+                    // clear flags not applicable to regular apps
+                    .setPersistent(false)
+                    .setDefaultToDeviceProtectedStorage(false)
+                    .setDirectBootAware(false)
+                    // non system apps can't have permission priority
+                    .capPermissionPriorities();
+        }
+        if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
+            parsedPackage
+                    .markNotActivitiesAsNotExportedIfSingleUser();
+        }
+
+        parsedPackage.setPrivileged((scanFlags & SCAN_AS_PRIVILEGED) != 0)
+                .setOem((scanFlags & SCAN_AS_OEM) != 0)
+                .setVendor((scanFlags & SCAN_AS_VENDOR) != 0)
+                .setProduct((scanFlags & SCAN_AS_PRODUCT) != 0)
+                .setSystemExt((scanFlags & SCAN_AS_SYSTEM_EXT) != 0)
+                .setOdm((scanFlags & SCAN_AS_ODM) != 0);
+
+        // Check if the package is signed with the same key as the platform package.
+        parsedPackage.setSignedWithPlatformKey(
+                (PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
+                        || (platformPkg != null && compareSignatures(
+                        platformPkg.getSigningDetails().getSignatures(),
+                        parsedPackage.getSigningDetails().getSignatures()
+                ) == PackageManager.SIGNATURE_MATCH))
+        );
+
+        if (!parsedPackage.isSystem()) {
+            // Only system apps can use these features.
+            parsedPackage.clearOriginalPackages()
+                    .clearAdoptPermissions();
+        }
+
+        PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isUpdatedSystemApp);
+    }
+
+    /**
+     * Applies the adjusted ABI calculated by
+     * {@link PackageAbiHelper#getAdjustedAbiForSharedUser(Set, AndroidPackage)} to all
+     * relevant packages and settings.
+     * @param sharedUserSetting The {@code SharedUserSetting} to adjust
+     * @param scannedPackage the package being scanned or null
+     * @param adjustedAbi the adjusted ABI calculated by {@link PackageAbiHelper}
+     * @return the list of code paths that belong to packages that had their ABIs adjusted.
+     */
+    public static List<String> applyAdjustedAbiToSharedUser(SharedUserSetting sharedUserSetting,
+            ParsedPackage scannedPackage, String adjustedAbi) {
+        if (scannedPackage != null)  {
+            scannedPackage.setPrimaryCpuAbi(adjustedAbi);
+        }
+        List<String> changedAbiCodePath = null;
+        for (PackageSetting ps : sharedUserSetting.packages) {
+            if (scannedPackage == null
+                    || !scannedPackage.getPackageName().equals(ps.getPackageName())) {
+                if (ps.getPrimaryCpuAbi() != null) {
+                    continue;
+                }
+
+                ps.setPrimaryCpuAbi(adjustedAbi);
+                if (ps.getPkg() != null) {
+                    if (!TextUtils.equals(adjustedAbi,
+                            AndroidPackageUtils.getRawPrimaryCpuAbi(ps.getPkg()))) {
+                        if (DEBUG_ABI_SELECTION) {
+                            Slog.i(TAG,
+                                    "Adjusting ABI for " + ps.getPackageName() + " to "
+                                            + adjustedAbi + " (scannedPackage="
+                                            + (scannedPackage != null ? scannedPackage : "null")
+                                            + ")");
+                        }
+                        if (changedAbiCodePath == null) {
+                            changedAbiCodePath = new ArrayList<>();
+                        }
+                        changedAbiCodePath.add(ps.getPathString());
+                    }
+                }
+            }
+        }
+        return changedAbiCodePath;
+    }
+
+    public static void collectCertificatesLI(PackageSetting ps, ParsedPackage parsedPackage,
+            Settings.VersionInfo settingsVersionForPackage, boolean forceCollect,
+            boolean skipVerify, boolean isPreNMR1Upgrade)
+            throws PackageManagerException {
+        // When upgrading from pre-N MR1, verify the package time stamp using the package
+        // directory and not the APK file.
+        final long lastModifiedTime = isPreNMR1Upgrade
+                ? new File(parsedPackage.getPath()).lastModified()
+                : getLastModifiedTime(parsedPackage);
+        if (ps != null && !forceCollect
+                && ps.getPathString().equals(parsedPackage.getPath())
+                && ps.getLastModifiedTime() == lastModifiedTime
+                && !ReconcilePackageUtils.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
+                && !ReconcilePackageUtils.isRecoverSignatureUpdateNeeded(
+                settingsVersionForPackage)) {
+            if (ps.getSigningDetails().getSignatures() != null
+                    && ps.getSigningDetails().getSignatures().length != 0
+                    && ps.getSigningDetails().getSignatureSchemeVersion()
+                    != SigningDetails.SignatureSchemeVersion.UNKNOWN) {
+                // Optimization: reuse the existing cached signing data
+                // if the package appears to be unchanged.
+                parsedPackage.setSigningDetails(
+                        new SigningDetails(ps.getSigningDetails()));
+                return;
+            }
+
+            Slog.w(TAG, "PackageSetting for " + ps.getPackageName()
+                    + " is missing signatures.  Collecting certs again to recover them.");
+        } else {
+            Slog.i(TAG, parsedPackage.getPath() + " changed; collecting certs"
+                    + (forceCollect ? " (forced)" : ""));
+        }
+
+        try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
+                    input, parsedPackage, skipVerify);
+            if (result.isError()) {
+                throw new PackageManagerException(
+                        result.getErrorCode(), result.getErrorMessage(), result.getException());
+            }
+            parsedPackage.setSigningDetails(result.getResult());
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    public static void setInstantAppForUser(PackageManagerServiceInjector injector,
+            PackageSetting pkgSetting, int userId, boolean instantApp, boolean fullApp) {
+        // no state specified; do nothing
+        if (!instantApp && !fullApp) {
+            return;
+        }
+        if (userId != UserHandle.USER_ALL) {
+            if (instantApp && !pkgSetting.getInstantApp(userId)) {
+                pkgSetting.setInstantApp(true /*instantApp*/, userId);
+            } else if (fullApp && pkgSetting.getInstantApp(userId)) {
+                pkgSetting.setInstantApp(false /*instantApp*/, userId);
+            }
+        } else {
+            for (int currentUserId : injector.getUserManagerInternal().getUserIds()) {
+                if (instantApp && !pkgSetting.getInstantApp(currentUserId)) {
+                    pkgSetting.setInstantApp(true /*instantApp*/, currentUserId);
+                } else if (fullApp && pkgSetting.getInstantApp(currentUserId)) {
+                    pkgSetting.setInstantApp(false /*instantApp*/, currentUserId);
+                }
+            }
+        }
+    }
+
+    /** Directory where installed application's 32-bit native libraries are copied. */
+    public static File getAppLib32InstallDir() {
+        return new File(Environment.getDataDirectory(), "app-lib");
+    }
+}
diff --git a/services/core/java/com/android/server/pm/VerificationParams.java b/services/core/java/com/android/server/pm/VerificationParams.java
index e1442dd..4334cbd 100644
--- a/services/core/java/com/android/server/pm/VerificationParams.java
+++ b/services/core/java/com/android/server/pm/VerificationParams.java
@@ -507,6 +507,13 @@
                 requiredVerifierPackage, verificationTimeout,
                 verifierUserId, false,
                 REASON_PACKAGE_VERIFIER, "package verifier");
+
+        if (streaming) {
+            // For streaming installations, count verification timeout from the broadcast.
+            startVerificationTimeoutCountdown(verificationId, streaming, response,
+                    verificationTimeout);
+        }
+
         mPm.mContext.sendOrderedBroadcastAsUser(verification, verifierUser,
                 android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
                 /* appOp= */ AppOpsManager.OP_NONE,
@@ -514,12 +521,12 @@
                 new BroadcastReceiver() {
                     @Override
                     public void onReceive(Context context, Intent intent) {
-                        final Message msg = mPm.mHandler
-                                .obtainMessage(CHECK_PENDING_VERIFICATION);
-                        msg.arg1 = verificationId;
-                        msg.arg2 = streaming ? 1 : 0;
-                        msg.obj = response;
-                        mPm.mHandler.sendMessageDelayed(msg, verificationTimeout);
+                        if (!streaming) {
+                            // For NON-streaming installations, count verification timeout from
+                            // the broadcast was processed by all receivers.
+                            startVerificationTimeoutCountdown(verificationId, streaming, response,
+                                    verificationTimeout);
+                        }
                     }
                 }, null, 0, null, null);
 
@@ -532,6 +539,15 @@
         mWaitForVerificationToComplete = true;
     }
 
+    private void startVerificationTimeoutCountdown(int verificationId, boolean streaming,
+            PackageVerificationResponse response, long verificationTimeout) {
+        final Message msg = mPm.mHandler.obtainMessage(CHECK_PENDING_VERIFICATION);
+        msg.arg1 = verificationId;
+        msg.arg2 = streaming ? 1 : 0;
+        msg.obj = response;
+        mPm.mHandler.sendMessageDelayed(msg, verificationTimeout);
+    }
+
     /**
      * Get the default verification agent response code.
      *
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8643b5f..6c1ef2e 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -435,7 +435,8 @@
                     || !pm.isGranted(Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                             pkg, UserHandle.of(userId))
                     || !pm.isGranted(Manifest.permission.READ_PHONE_STATE, pkg,
-                            UserHandle.of(userId))) {
+                            UserHandle.of(userId))
+                    || pm.isSysComponentOrPersistentPlatformSignedPrivApp(pkg)) {
                 continue;
             }
 
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 6c9f1e5..7164c6c 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -49,6 +49,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IActivityClientController;
+import android.app.ICompatCameraControlCallback;
 import android.app.IRequestFinishCallback;
 import android.app.PictureInPictureParams;
 import android.app.PictureInPictureUiState;
@@ -766,6 +767,22 @@
         Binder.restoreCallingIdentity(origId);
     }
 
+    @Override
+    public void requestCompatCameraControl(IBinder token, boolean showControl,
+            boolean transformationApplied, ICompatCameraControlCallback callback) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+                if (r != null) {
+                    r.updateCameraCompatState(showControl, transformationApplied, callback);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     /**
      * Checks the state of the system and the activity associated with the given {@param token} to
      * verify that picture-in-picture is supported for that activity.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 447f4be..cde5273 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -232,9 +232,12 @@
 import android.app.Activity;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
+import android.app.ICompatCameraControlCallback;
 import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.app.ResultInfo;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
 import android.app.WaitResult;
 import android.app.WindowConfiguration;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
@@ -712,6 +715,20 @@
     @Nullable
     private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
 
+    // State of the Camera app compat control which is used to correct stretched viewfinder
+    // in apps that don't handle all possible configurations and changes between them correctly.
+    @CameraCompatControlState
+    private int mCameraCompatControlState = TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+
+
+    // The callback that allows to ask the calling View to apply the treatment for stretched
+    // issues affecting camera viewfinders when the user clicks on the camera compat control.
+    @Nullable
+    private ICompatCameraControlCallback mCompatCameraControlCallback;
+
+    private final boolean mCameraCompatControlEnabled;
+    private boolean mCameraCompatControlClickedByUser;
+
     // activity is not displayed?
     // TODO: rename to mNoDisplay
     @VisibleForTesting
@@ -1167,6 +1184,10 @@
         }
 
         mLetterboxUiController.dump(pw, prefix);
+
+        pw.println(prefix + "mCameraCompatControlState="
+                + TaskInfo.cameraCompatControlStateToString(mCameraCompatControlState));
+        pw.println(prefix + "mCameraCompatControlEnabled=" + mCameraCompatControlEnabled);
     }
 
     static boolean dumpActivity(FileDescriptor fd, PrintWriter pw, int index, ActivityRecord r,
@@ -1572,6 +1593,91 @@
         mLetterboxUiController.getLetterboxInnerBounds(outBounds);
     }
 
+    void updateCameraCompatState(boolean showControl, boolean transformationApplied,
+            ICompatCameraControlCallback callback) {
+        if (!isCameraCompatControlEnabled()) {
+            // Feature is disabled by config_isCameraCompatControlForStretchedIssuesEnabled.
+            return;
+        }
+        if (mCameraCompatControlClickedByUser && (showControl
+                || mCameraCompatControlState == TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED)) {
+            // The user already applied treatment on this activity or dismissed control.
+            // Respecting their choice.
+            return;
+        }
+        mCompatCameraControlCallback = callback;
+        int newCameraCompatControlState = !showControl
+                ? TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN
+                : transformationApplied
+                        ? TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED
+                        : TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        boolean changed = setCameraCompatControlState(newCameraCompatControlState);
+        if (!changed) {
+            return;
+        }
+        if (newCameraCompatControlState == TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN) {
+            mCameraCompatControlClickedByUser = false;
+            mCompatCameraControlCallback = null;
+        }
+        // Trigger TaskInfoChanged to update the camera compat UI.
+        getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+    }
+
+    void updateCameraCompatStateFromUser(@CameraCompatControlState int state) {
+        if (!isCameraCompatControlEnabled()) {
+            // Feature is disabled by config_isCameraCompatControlForStretchedIssuesEnabled.
+            return;
+        }
+        if (state == TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN) {
+            Slog.w(TAG, "Unexpected hidden state in updateCameraCompatState");
+            return;
+        }
+        boolean changed = setCameraCompatControlState(state);
+        mCameraCompatControlClickedByUser = true;
+        if (!changed) {
+            return;
+        }
+        if (state == TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED) {
+            mCompatCameraControlCallback = null;
+            return;
+        }
+        if (mCompatCameraControlCallback == null) {
+            Slog.w(TAG, "Callback for a camera compat control is null");
+            return;
+        }
+        try {
+            if (state == TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED) {
+                mCompatCameraControlCallback.applyCameraCompatTreatment();
+            } else {
+                mCompatCameraControlCallback.revertCameraCompatTreatment();
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to apply or revert camera compat treatment", e);
+        }
+    }
+
+    private boolean setCameraCompatControlState(@CameraCompatControlState int state) {
+        if (!isCameraCompatControlEnabled()) {
+            // Feature is disabled by config_isCameraCompatControlForStretchedIssuesEnabled.
+            return false;
+        }
+        if (mCameraCompatControlState != state) {
+            mCameraCompatControlState = state;
+            return true;
+        }
+        return false;
+    }
+
+    @CameraCompatControlState
+    int getCameraCompatControlState() {
+        return mCameraCompatControlState;
+    }
+
+    @VisibleForTesting
+    boolean isCameraCompatControlEnabled() {
+        return mCameraCompatControlEnabled;
+    }
+
     /**
      * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
      *     when the current activity is displayed.
@@ -1794,6 +1900,8 @@
         taskDescription = _taskDescription;
 
         mLetterboxUiController = new LetterboxUiController(mWmService, this);
+        mCameraCompatControlEnabled = mWmService.mContext.getResources()
+                .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
 
         if (_createTime > 0) {
             createTime = _createTime;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1c93b99..e80a9b9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2834,8 +2834,14 @@
         mBaseDisplayDensity = baseDensity;
 
         if (mMaxUiWidth > 0 && mBaseDisplayWidth > mMaxUiWidth) {
-            mBaseDisplayHeight = (mMaxUiWidth * mBaseDisplayHeight) / mBaseDisplayWidth;
+            final float ratio = mMaxUiWidth / (float) mBaseDisplayWidth;
+            mBaseDisplayHeight = (int) (mBaseDisplayHeight * ratio);
             mBaseDisplayWidth = mMaxUiWidth;
+            if (!mIsDensityForced) {
+                // Update the density proportionally so the size of the UI elements won't change
+                // from the user's perspective.
+                mBaseDisplayDensity = (int) (mBaseDisplayDensity * ratio);
+            }
 
             if (DEBUG_DISPLAY) {
                 Slog.v(TAG_WM, "Applying config restraints:" + mBaseDisplayWidth + "x"
@@ -2892,6 +2898,13 @@
 
     /** If the given width and height equal to initial size, the setting will be cleared. */
     void setForcedSize(int width, int height) {
+        // Can't force size higher than the maximal allowed
+        if (mMaxUiWidth > 0 && width > mMaxUiWidth) {
+            final float ratio = mMaxUiWidth / (float) width;
+            height = (int) (height * ratio);
+            width = mMaxUiWidth;
+        }
+
         mIsSizeForced = mInitialDisplayWidth != width || mInitialDisplayHeight != height;
         if (mIsSizeForced) {
             // Set some sort of reasonable bounds on the size of the display that we will try
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3e55811..7c3432e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3395,11 +3395,18 @@
         info.topActivityInfo = mReuseActivitiesReport.top != null
                 ? mReuseActivitiesReport.top.info
                 : null;
+
+        boolean isTopActivityResumed = mReuseActivitiesReport.top != null
+                 && mReuseActivitiesReport.top.getOrganizedTask() == this
+                 && mReuseActivitiesReport.top.isState(RESUMED);
         // Whether the direct top activity is in size compat mode on foreground.
-        info.topActivityInSizeCompat = mReuseActivitiesReport.top != null
-                && mReuseActivitiesReport.top.getOrganizedTask() == this
-                && mReuseActivitiesReport.top.inSizeCompatMode()
-                && mReuseActivitiesReport.top.isState(RESUMED);
+        info.topActivityInSizeCompat = isTopActivityResumed
+                && mReuseActivitiesReport.top.inSizeCompatMode();
+        // Whether the direct top activity requested showing camera compat control.
+        info.cameraCompatControlState = isTopActivityResumed
+                ? mReuseActivitiesReport.top.getCameraCompatControlState()
+                : TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+
         info.launchCookies.clear();
         info.addLaunchCookie(mLaunchCookie);
         forAllActivities(r -> {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 3d5f988..037d582 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.app.TaskInfo.cameraCompatControlStateToString;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
@@ -931,6 +933,35 @@
         }
     }
 
+    @Override
+    public void updateCameraCompatControlState(WindowContainerToken token, int state) {
+        enforceTaskPermission("updateCameraCompatControlState()");
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final WindowContainer wc = WindowContainer.fromBinder(token.asBinder());
+                if (wc == null) {
+                    Slog.w(TAG, "Could not resolve window from token");
+                    return;
+                }
+                final Task task = wc.asTask();
+                if (task == null) {
+                    Slog.w(TAG, "Could not resolve task from token");
+                    return;
+                }
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                        "Update camera compat control state to %s for taskId=%d",
+                        cameraCompatControlStateToString(state), task.mTaskId);
+                final ActivityRecord activity = task.getTopNonFinishingActivity();
+                if (activity != null) {
+                    activity.updateCameraCompatStateFromUser(state);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     public boolean handleInterceptBackPressedOnTaskRoot(Task task) {
         if (task == null || !task.isOrganized()
                 || !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c7c3bb6..c6948ee 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -368,6 +368,7 @@
                 t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
                 t.setCornerRadius(targetLeash, 0);
                 t.setShadowRadius(targetLeash, 0);
+                t.setMatrix(targetLeash, 1, 0, 0, 1);
                 // The bounds sent to the transition is always a real bounds. This means we lose
                 // information about "null" bounds (inheriting from parent). Core will fix-up
                 // non-organized window surface bounds; however, since Core can't touch organized
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 429edf1..2f4dd57 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -26,6 +26,10 @@
     <xs:element name="displayConfiguration">
         <xs:complexType>
             <xs:sequence>
+                <xs:element type="densityMap" name="densityMap" minOccurs="0" maxOccurs="1">
+                    <xs:annotation name="nullable"/>
+                    <xs:annotation name="final"/>
+                </xs:element>
                 <xs:element type="nitsMap" name="screenBrightnessMap">
                     <xs:annotation name="nonnull"/>
                     <xs:annotation name="final"/>
@@ -181,5 +185,27 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:complexType name="densityMap">
+        <xs:sequence>
+            <xs:element name="density" type="density" maxOccurs="unbounded" minOccurs="1">
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
 
+    <xs:complexType name="density">
+        <xs:sequence>
+            <xs:element type="xs:nonNegativeInteger" name="width">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element type="xs:nonNegativeInteger" name="height">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element type="xs:nonNegativeInteger" name="density">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index ad18602..5b2b87c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -1,8 +1,24 @@
 // Signature format: 2.0
 package com.android.server.display.config {
 
+  public class Density {
+    ctor public Density();
+    method @NonNull public final java.math.BigInteger getDensity();
+    method @NonNull public final java.math.BigInteger getHeight();
+    method @NonNull public final java.math.BigInteger getWidth();
+    method public final void setDensity(@NonNull java.math.BigInteger);
+    method public final void setHeight(@NonNull java.math.BigInteger);
+    method public final void setWidth(@NonNull java.math.BigInteger);
+  }
+
+  public class DensityMap {
+    ctor public DensityMap();
+    method public java.util.List<com.android.server.display.config.Density> getDensity();
+  }
+
   public class DisplayConfiguration {
     ctor public DisplayConfiguration();
+    method @Nullable public final com.android.server.display.config.DensityMap getDensityMap();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
     method public final com.android.server.display.config.SensorDetails getProxSensor();
@@ -13,6 +29,7 @@
     method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease();
     method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease();
     method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
+    method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 45d9626..663e17b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -151,6 +151,7 @@
 import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.people.PeopleService;
+import com.android.server.pm.ApexManager;
 import com.android.server.pm.CrossProfileAppsService;
 import com.android.server.pm.DataLoaderManagerService;
 import com.android.server.pm.DynamicCodeLoggingService;
@@ -220,6 +221,7 @@
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Timer;
 import java.util.TreeSet;
 import java.util.concurrent.CountDownLatch;
@@ -918,6 +920,13 @@
             startBootstrapServices(t);
             startCoreServices(t);
             startOtherServices(t);
+            // Apex services must be the last category of services to start. No other service must
+            // be starting after this point. This is to prevent unnessary stability issues when
+            // these apexes are updated outside of OTA; and to avoid breaking dependencies from
+            // system into apexes.
+            // TODO(satayev): lock mSystemServiceManager.startService to stop accepting new services
+            // after this step
+            startApexServices(t);
         } catch (Throwable ex) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting system services", ex);
@@ -3051,6 +3060,27 @@
         t.traceEnd(); // startOtherServices
     }
 
+    /**
+     * Starts system services defined in apexes.
+     */
+    private void startApexServices(@NonNull TimingsTraceAndSlog t) {
+        t.traceBegin("startApexServices");
+        Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
+        // TODO(satayev): filter out already started services
+        // TODO(satayev): introduce android:order for services coming the same apexes
+        for (String name : new TreeSet<>(services.keySet())) {
+            String jarPath = services.get(name);
+            t.traceBegin("starting " + name);
+            if (TextUtils.isEmpty(jarPath)) {
+                mSystemServiceManager.startService(name);
+            } else {
+                mSystemServiceManager.startServiceFromJar(name, jarPath);
+            }
+            t.traceEnd();
+        }
+        t.traceEnd(); // startApexServices
+    }
+
     private boolean deviceHasConfigString(@NonNull Context context, @StringRes int resId) {
         String serviceName = context.getString(resId);
         return !TextUtils.isEmpty(serviceName);
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
new file mode 100644
index 0000000..c26965d
--- /dev/null
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.selectiontoolbar;
+
+import android.service.selectiontoolbar.SelectionToolbarRenderService;
+import android.util.Log;
+import android.view.selectiontoolbar.ShowInfo;
+
+/**
+ * The default implementation of {@link SelectionToolbarRenderService}.
+ */
+public final class DefaultSelectionToolbarRenderService extends SelectionToolbarRenderService {
+
+    private static final String TAG = "DefaultSelectionToolbarRenderService";
+
+    @Override
+    public void onShow(ShowInfo showInfo,
+            SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper) {
+        // TODO: Add implementation
+        Log.w(TAG, "onShow()");
+    }
+
+    @Override
+    public void onHide(long widgetToken) {
+        // TODO: Add implementation
+        Log.w(TAG, "onHide()");
+    }
+
+    @Override
+    public void onDismiss(long widgetToken) {
+        // TODO: Add implementation
+        Log.w(TAG, "onDismiss()");
+    }
+}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
new file mode 100644
index 0000000..ced24e0
--- /dev/null
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.selectiontoolbar;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.service.selectiontoolbar.ISelectionToolbarRenderService;
+import android.service.selectiontoolbar.SelectionToolbarRenderService;
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ShowInfo;
+
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.ServiceConnector;
+
+final class RemoteSelectionToolbarRenderService extends
+        ServiceConnector.Impl<ISelectionToolbarRenderService> {
+    private static final String TAG = "RemoteSelectionToolbarRenderService";
+
+    private static final long TIMEOUT_IDLE_UNBIND_MS =
+            AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+
+    private final ComponentName mComponentName;
+
+
+    RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId) {
+        super(context, new Intent(SelectionToolbarRenderService.SERVICE_INTERFACE).setComponent(
+                serviceName), 0, userId, ISelectionToolbarRenderService.Stub::asInterface);
+        mComponentName = serviceName;
+        // Bind right away.
+        connect();
+    }
+
+    @Override // from AbstractRemoteService
+    protected long getAutoDisconnectTimeoutMs() {
+        return TIMEOUT_IDLE_UNBIND_MS;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    public void onShow(ShowInfo showInfo, ISelectionToolbarCallback callback) {
+        run((s) -> s.onShow(showInfo, callback));
+    }
+
+    public void onHide(long widgetToken) {
+        run((s) -> s.onHide(widgetToken));
+    }
+
+    public void onDismiss(long widgetToken) {
+        run((s) -> s.onDismiss(widgetToken));
+    }
+}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
index 94bf712..235f547 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
@@ -17,9 +17,18 @@
 package com.android.server.selectiontoolbar;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.util.Slog;
 import android.view.selectiontoolbar.ISelectionToolbarCallback;
 import android.view.selectiontoolbar.ShowInfo;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.infra.AbstractPerUserSystemService;
 
 final class SelectionToolbarManagerServiceImpl extends
@@ -28,20 +37,92 @@
 
     private static final String TAG = "SelectionToolbarManagerServiceImpl";
 
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSelectionToolbarRenderService mRemoteService;
+
     protected SelectionToolbarManagerServiceImpl(@NonNull SelectionToolbarManagerService master,
             @NonNull Object lock, int userId) {
         super(master, lock, userId);
+        updateRemoteServiceLocked();
     }
 
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        return getServiceInfoOrThrow(serviceComponent, mUserId);
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        updateRemoteServiceLocked();
+        return enabledChanged;
+    }
+
+    /**
+     * Updates the reference to the remote service.
+     */
+    @GuardedBy("mLock")
+    private void updateRemoteServiceLocked() {
+        if (mRemoteService != null) {
+            Slog.d(TAG, "updateRemoteService(): destroying old remote service");
+            mRemoteService.unbind();
+            mRemoteService = null;
+        }
+    }
+
+    @GuardedBy("mLock")
     void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback) {
-        // TODO: add implementation to bind service
+        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.onShow(showInfo, callback);
+        }
     }
 
+    @GuardedBy("mLock")
     void hideToolbar(long widgetToken) {
-        // TODO: add implementation to bind service
+        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.onHide(widgetToken);
+        }
     }
 
+    @GuardedBy("mLock")
     void dismissToolbar(long widgetToken) {
-        // TODO: add implementation to bind service
+        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.onDismiss(widgetToken);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSelectionToolbarRenderService ensureRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            final String serviceName = getComponentNameLocked();
+            final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+            mRemoteService = new RemoteSelectionToolbarRenderService(getContext(), serviceComponent,
+                    mUserId);
+        }
+        return mRemoteService;
+    }
+
+    private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException {
+        int flags = PackageManager.GET_META_DATA;
+
+        ServiceInfo si = null;
+        try {
+            si = AppGlobals.getPackageManager().getServiceInfo(comp, flags, userId);
+        } catch (RemoteException e) {
+        }
+        if (si == null) {
+            throw new PackageManager.NameNotFoundException("Could not get serviceInfo for "
+                    + comp.flattenToShortString());
+        }
+        return si;
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java
new file mode 100644
index 0000000..3f69f1b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.server.display.DensityMap.Entry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DensityMapTest {
+
+    @Test
+    public void testConstructor_withBadConfig_throwsException() {
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(1080, 1920, 320)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(1920, 1080, 120)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(2160, 3840, 120)})
+        );
+
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(1080, 1920, 320),
+                        new Entry(3840, 2160, 120)})
+        );
+
+        // Two entries with the same diagonal
+        assertThrows(IllegalStateException.class, () ->
+                DensityMap.createByOwning(new Entry[]{
+                        new Entry(500, 500, 123),
+                        new Entry(100, 700, 456)})
+        );
+    }
+
+    @Test
+    public void testGetDensityForResolution_withResolutionMatch_returnsDensityFromConfig() {
+        DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
+                new Entry(720, 1280, 213),
+                new Entry(1080, 1920, 320),
+                new Entry(2160, 3840, 640)});
+
+        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
+
+        assertEquals(320, densityMap.getDensityForResolution(1080, 1920));
+        assertEquals(320, densityMap.getDensityForResolution(1920, 1080));
+
+        assertEquals(640, densityMap.getDensityForResolution(2160, 3840));
+        assertEquals(640, densityMap.getDensityForResolution(3840, 2160));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withDiagonalMatch_returnsDensityFromConfig() {
+        DensityMap densityMap = DensityMap.createByOwning(
+                        new Entry[]{ new Entry(500, 500, 123)});
+
+        // 500x500 has the same diagonal as 100x700
+        assertEquals(123, densityMap.getDensityForResolution(100, 700));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withOneEntry_withNoMatch_returnsExtrapolatedDensity() {
+        DensityMap densityMap = DensityMap.createByOwning(
+                new Entry[]{ new Entry(1080, 1920, 320)});
+
+        assertEquals(320, densityMap.getDensityForResolution(1081, 1920));
+        assertEquals(320, densityMap.getDensityForResolution(1080, 1921));
+
+        assertEquals(640, densityMap.getDensityForResolution(2160, 3840));
+        assertEquals(640, densityMap.getDensityForResolution(3840, 2160));
+
+        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withTwoEntries_withNoMatch_returnExtrapolatedDensity() {
+        DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
+                new Entry(1080, 1920, 320),
+                new Entry(2160, 3840, 320)});
+
+        // Resolution is smaller than all entries
+        assertEquals(213, densityMap.getDensityForResolution(720, 1280));
+        assertEquals(213, densityMap.getDensityForResolution(1280, 720));
+
+        // Resolution is bigger than all entries
+        assertEquals(320 * 2, densityMap.getDensityForResolution(2160 * 2, 3840 * 2));
+        assertEquals(320 * 2, densityMap.getDensityForResolution(3840 * 2, 2160 * 2));
+    }
+
+    @Test
+    public void testGetDensityForResolution_withNoMatch_returnsInterpolatedDensity() {
+        {
+            DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
+                    new Entry(1080, 1920, 320),
+                    new Entry(2160, 3840, 320)});
+
+            assertEquals(320, densityMap.getDensityForResolution(2000, 2000));
+        }
+
+        {
+            DensityMap densityMap = DensityMap.createByOwning(new Entry[]{
+                    new Entry(720, 1280, 213),
+                    new Entry(2160, 3840, 640)});
+
+            assertEquals(320, densityMap.getDensityForResolution(1080, 1920));
+            assertEquals(320, densityMap.getDensityForResolution(1920, 1080));
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java
new file mode 100644
index 0000000..7f7901f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.transport;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.backup.BackupTransport;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TransportStatusCallbackTest {
+    private static final int OPERATION_TIMEOUT_MILLIS = 10;
+    private static final int OPERATION_COMPLETE_STATUS = 123;
+
+    private TransportStatusCallback mTransportStatusCallback;
+
+    @Before
+    public void setUp() {
+        mTransportStatusCallback = new TransportStatusCallback();
+    }
+
+    @Test
+    public void testGetOperationStatus_withPreCompletedOperation_returnsStatus() throws Exception {
+        mTransportStatusCallback.onOperationCompleteWithStatus(OPERATION_COMPLETE_STATUS);
+
+        int result = mTransportStatusCallback.getOperationStatus();
+
+        assertThat(result).isEqualTo(OPERATION_COMPLETE_STATUS);
+    }
+
+    @Test
+    public void testGetOperationStatus_completeOperation_returnsStatus() throws Exception {
+        Thread thread = new Thread(() -> {
+            int result = mTransportStatusCallback.getOperationStatus();
+            assertThat(result).isEqualTo(OPERATION_COMPLETE_STATUS);
+        });
+        thread.start();
+
+        mTransportStatusCallback.onOperationCompleteWithStatus(OPERATION_COMPLETE_STATUS);
+
+        thread.join();
+    }
+
+    @Test
+    public void testGetOperationStatus_operationTimesOut_returnsError() throws Exception {
+        TransportStatusCallback callback = new TransportStatusCallback(OPERATION_TIMEOUT_MILLIS);
+
+        int result = callback.getOperationStatus();
+
+        assertThat(result).isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 71d5b77..28f24f2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -470,9 +470,7 @@
                 .addUsesPermission(
                         new ParsedUsesPermissionImpl(Manifest.permission.FACTORY_TEST, 0));
 
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(
-                mMockPackageManager, mMockInjector);
-        final ScanResult scanResult = scanPackageHelper.scanPackageOnlyLI(
+        final ScanResult scanResult = ScanPackageUtils.scanPackageOnlyLI(
                 createBasicScanRequestBuilder(basicPackage).build(),
                 mMockInjector,
                 true /*isUnderFactoryTest*/,
@@ -520,9 +518,7 @@
 
     private ScanResult executeScan(
             ScanRequest scanRequest) throws PackageManagerException {
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(
-                mMockPackageManager, mMockInjector);
-        ScanResult result = scanPackageHelper.scanPackageOnlyLI(
+        ScanResult result = ScanPackageUtils.scanPackageOnlyLI(
                 scanRequest,
                 mMockInjector,
                 false /*isUnderFactoryTest*/,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index e8a27990..d49cf67 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -5112,6 +5112,11 @@
                 assertTrue(expected.containsKey(uid));
                 assertThat(expected.get(uid)).isEqualTo(
                             builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER));
+
+                // pre-migration, the userSet field will always default to false
+                boolean userSet = builder.getBoolean(
+                        PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER);
+                assertFalse(userSet);
             }
         }
     }
@@ -5123,8 +5128,8 @@
         // build a collection of app permissions that should be passed in but ignored
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, true));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, true)); // in local prefs
 
         // package preferences: PKG_O not banned based on local importance, and PKG_P is
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -5132,11 +5137,11 @@
 
         // expected output. format: uid -> importance, as only uid (and not package name)
         // is in PackageNotificationPreferences
-        ArrayMap<Integer, Integer> expected = new ArrayMap<>();
-        expected.put(1, IMPORTANCE_DEFAULT);
-        expected.put(3, IMPORTANCE_NONE);
-        expected.put(UID_O, IMPORTANCE_NONE);    // banned by permissions
-        expected.put(UID_P, IMPORTANCE_NONE);    // defaults to none
+        ArrayMap<Integer, Pair<Integer, Boolean>> expected = new ArrayMap<>();
+        expected.put(1, new Pair(IMPORTANCE_DEFAULT, false));
+        expected.put(3, new Pair(IMPORTANCE_NONE, true));
+        expected.put(UID_O, new Pair(IMPORTANCE_NONE, true));     // banned by permissions
+        expected.put(UID_P, new Pair(IMPORTANCE_NONE, false));    // defaults to none, false
 
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackagePreferencesStats(events, appPermissions);
@@ -5144,11 +5149,14 @@
         for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
             if (builder.getAtomId() == PACKAGE_NOTIFICATION_PREFERENCES) {
                 int uid = builder.getInt(PackageNotificationPreferences.UID_FIELD_NUMBER);
+                boolean userSet = builder.getBoolean(
+                        PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER);
 
                 // if it's one of the expected ids, then make sure the importance matches
                 assertTrue(expected.containsKey(uid));
-                assertThat(expected.get(uid)).isEqualTo(
+                assertThat(expected.get(uid).first).isEqualTo(
                         builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER));
+                assertThat(expected.get(uid).second).isEqualTo(userSet);
             }
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4a8e121..2e62286 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -16,6 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -103,6 +107,7 @@
 import static org.mockito.Mockito.never;
 
 import android.app.ActivityOptions;
+import android.app.ICompatCameraControlCallback;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.DestroyActivityItem;
@@ -3084,6 +3089,188 @@
                 eq(null));
     }
 
+    @Test
+    public void testUpdateCameraCompatState_flagIsEnabled_controlStateIsUpdated() {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being enabled.
+        doReturn(true).when(activity).isCameraCompatControlEnabled();
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+    }
+
+    @Test
+    public void testUpdateCameraCompatState_flagIsDisabled_controlStateIsHidden() {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being disabled.
+        doReturn(false).when(activity).isCameraCompatControlEnabled();
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+    }
+
+    @Test
+    public void testUpdateCameraCompatStateFromUser_clickedOnDismiss() throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being enabled.
+        doReturn(true).when(activity).isCameraCompatControlEnabled();
+
+        ICompatCameraControlCallback callback = getCompatCameraControlCallback();
+        spyOn(callback);
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, callback);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Clicking on the button.
+        activity.updateCameraCompatStateFromUser(CAMERA_COMPAT_CONTROL_DISMISSED);
+
+        verify(callback, never()).revertCameraCompatTreatment();
+        verify(callback, never()).applyCameraCompatTreatment();
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_DISMISSED);
+
+        // All following updates are ignored.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_DISMISSED);
+
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_DISMISSED);
+
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_DISMISSED);
+    }
+
+    @Test
+    public void testUpdateCameraCompatStateFromUser_clickedOnApplyTreatment()
+            throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being enabled.
+        doReturn(true).when(activity).isCameraCompatControlEnabled();
+
+        ICompatCameraControlCallback callback = getCompatCameraControlCallback();
+        spyOn(callback);
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, callback);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Clicking on the button.
+        activity.updateCameraCompatStateFromUser(CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        verify(callback, never()).revertCameraCompatTreatment();
+        verify(callback).applyCameraCompatTreatment();
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        // Request from the client to show the control are ignored respecting the user choice.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        // Request from the client to hide the control is respected.
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        // Request from the client to show the control again is respected.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ false, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+    }
+
+    @Test
+    public void testUpdateCameraCompatStateFromUser_clickedOnRevertTreatment()
+            throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        // Mock a flag being enabled.
+        doReturn(true).when(activity).isCameraCompatControlEnabled();
+
+        ICompatCameraControlCallback callback = getCompatCameraControlCallback();
+        spyOn(callback);
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, callback);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+
+        // Clicking on the button.
+        activity.updateCameraCompatStateFromUser(CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        verify(callback).revertCameraCompatTreatment();
+        verify(callback, never()).applyCameraCompatTreatment();
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Request from the client to show the control are ignored respecting the user choice.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+
+        // Request from the client to hide the control is respected.
+        activity.updateCameraCompatState(/* showControl */ false,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(), CAMERA_COMPAT_CONTROL_HIDDEN);
+
+        // Request from the client to show the control again is respected.
+        activity.updateCameraCompatState(/* showControl */ true,
+                /* transformationApplied */ true, /* callback */ null);
+
+        assertEquals(activity.getCameraCompatControlState(),
+                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+    }
+
+    private ICompatCameraControlCallback getCompatCameraControlCallback() {
+        return new ICompatCameraControlCallback.Stub() {
+            @Override
+            public void applyCameraCompatTreatment() {}
+
+            @Override
+            public void revertCameraCompatTreatment() {}
+        };
+    }
+
     private void assertHasStartingWindow(ActivityRecord atoken) {
         assertNotNull(atoken.mStartingSurface);
         assertNotNull(atoken.mStartingData);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index deba835..dc0e028 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -681,7 +681,7 @@
 
         final int maxWidth = 300;
         final int resultingHeight = (maxWidth * baseHeight) / baseWidth;
-        final int resultingDensity = baseDensity;
+        final int resultingDensity = (baseDensity * maxWidth) / baseWidth;
 
         displayContent.setMaxUiWidth(maxWidth);
         verifySizes(displayContent, maxWidth, resultingHeight, resultingDensity);
@@ -756,6 +756,33 @@
     }
 
     @Test
+    public void testSetForcedDensity() {
+        final DisplayContent displayContent = createDisplayNoUpdateDisplayInfo();
+        final int baseWidth = 1280;
+        final int baseHeight = 720;
+        final int baseDensity = 320;
+
+        displayContent.mInitialDisplayWidth = baseWidth;
+        displayContent.mInitialDisplayHeight = baseHeight;
+        displayContent.mInitialDisplayDensity = baseDensity;
+        displayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
+
+        final int forcedDensity = 600;
+
+        // Verify that forcing the density is honored and the size doesn't change.
+        displayContent.setForcedDensity(forcedDensity, 0 /* userId */);
+        verifySizes(displayContent, baseWidth, baseHeight, forcedDensity);
+
+        // Verify that forcing the density is idempotent.
+        displayContent.setForcedDensity(forcedDensity, 0 /* userId */);
+        verifySizes(displayContent, baseWidth, baseHeight, forcedDensity);
+
+        // Verify that forcing resolution won't affect the already forced density.
+        displayContent.setForcedSize(1800, 1200);
+        verifySizes(displayContent, 1800, 1200, forcedDensity);
+    }
+
+    @Test
     public void testDisplayCutout_rot0() {
         final DisplayContent dc = createNewDisplay();
         dc.mInitialDisplayWidth = 200;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4bfb2d8..53261cb 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4466,6 +4466,29 @@
             "subscription_group_uuid_string";
 
     /**
+     * Controls the cellular usage setting.
+     *
+     * The usage setting indicates whether a device will remain attached to a network based on
+     * the primary use case for the service. A device will detach and search for a more-preferred
+     * network if the primary use case (voice or data) is not satisfied. Depending on the type
+     * of device, it may operate in a voice or data-centric mode by default.
+     *
+     * <p>Sets the usage setting in accordance with 3gpp 24.301 sec 4.3 and 3gpp 24.501 sec 4.3.
+     * Also refer to "UE's usage setting" as defined in 3gpp 24.301 section 3.1 and 3gpp 23.221
+     * Annex A.
+     *
+     * Either omit this key or pass a value of
+     * {@link SubscriptionManager#USAGE_SETTING_UNKNOWN unknown} to preserve the current setting.
+     *
+     * {@link SubscriptionManager#USAGE_SETTING_DEFAULT default},
+     * {@link SubscriptionManager#USAGE_SETTING_VOICE_CENTRIC voice-centric},
+     * or {@link SubscriptionManager#USAGE_SETTING_DATA_CENTRIC data-centric}.
+     * {@see SubscriptionInfo#getUsageSetting}
+     */
+    public static final String KEY_CELLULAR_USAGE_SETTING_INT =
+            "cellular_usage_setting_int";
+
+    /**
      * Data switch validation minimal gap time, in milliseconds.
      *
      * Which means, if the same subscription on the same network (based on MCC+MNC+TAC+subId)
@@ -6087,6 +6110,8 @@
         sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
                 "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
                         + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
+        sDefaults.putInt(KEY_CELLULAR_USAGE_SETTING_INT,
+                SubscriptionManager.USAGE_SETTING_UNKNOWN);
     }
 
     /**
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index d11ad91..c36eb2f 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -37,6 +37,7 @@
 import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.os.Parcelable;
+import android.telephony.SubscriptionManager.UsageSetting;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -227,6 +228,11 @@
     private final int mPortIndex;
 
     /**
+     * Subscription's preferred usage setting.
+     */
+    private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
+
+    /**
      * Public copy constructor.
      * @hide
      */
@@ -284,6 +290,7 @@
                 cardId, isOpportunistic, groupUUID, isGroupDisabled, carrierId, profileClass,
                 subType, groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, 0);
     }
+
     /**
      * @hide
      */
@@ -295,6 +302,24 @@
             int carrierId, int profileClass, int subType, @Nullable String groupOwner,
             @Nullable UiccAccessRule[] carrierConfigAccessRules,
             boolean areUiccApplicationsEnabled, int portIndex) {
+        this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
+                roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString,
+                cardId, isOpportunistic, groupUUID, isGroupDisabled, carrierId, profileClass,
+                subType, groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
+                portIndex, SubscriptionManager.USAGE_SETTING_DEFAULT);
+    }
+
+    /**
+     * @hide
+     */
+    public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
+            CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
+            Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
+            @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
+            boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
+            int carrierId, int profileClass, int subType, @Nullable String groupOwner,
+            @Nullable UiccAccessRule[] carrierConfigAccessRules,
+            boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -322,6 +347,7 @@
         this.mCarrierConfigAccessRules = carrierConfigAccessRules;
         this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
         this.mPortIndex = portIndex;
+        this.mUsageSetting = usageSetting;
     }
     /**
      * @return the subscription ID.
@@ -796,7 +822,18 @@
         return mAreUiccApplicationsEnabled;
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<SubscriptionInfo> CREATOR = new Parcelable.Creator<SubscriptionInfo>() {
+    /**
+     * Get the usage setting for this subscription.
+     *
+     * @return the usage setting used for this subscription.
+     */
+    public @UsageSetting int getUsageSetting() {
+        return mUsageSetting;
+    }
+
+    public static final @android.annotation.NonNull
+            Parcelable.Creator<SubscriptionInfo> CREATOR =
+                    new Parcelable.Creator<SubscriptionInfo>() {
         @Override
         public SubscriptionInfo createFromParcel(Parcel source) {
             int id = source.readInt();
@@ -828,12 +865,14 @@
             UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
                 UiccAccessRule.CREATOR);
             boolean areUiccApplicationsEnabled = source.readBoolean();
+            int usageSetting = source.readInt();
 
             SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
                     carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null,
                     mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId,
                     isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType,
-                    groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, portId);
+                    groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
+                    portId, usageSetting);
             info.setAssociatedPlmns(ehplmns, hplmns);
             return info;
         }
@@ -875,6 +914,7 @@
         dest.writeString(mGroupOwner);
         dest.writeTypedArray(mCarrierConfigAccessRules, flags);
         dest.writeBoolean(mAreUiccApplicationsEnabled);
+        dest.writeInt(mUsageSetting);
     }
 
     @Override
@@ -919,7 +959,8 @@
                 + " subscriptionType=" + mSubscriptionType
                 + " groupOwner=" + mGroupOwner
                 + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
-                + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}";
+                + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
+                + " usageSetting=" + mUsageSetting + "}";
     }
 
     @Override
@@ -927,7 +968,8 @@
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
                 mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
                 mCardId, mDisplayName, mCarrierName, mNativeAccessRules, mIsGroupDisabled,
-                mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex);
+                mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex,
+                mUsageSetting);
     }
 
     @Override
@@ -967,6 +1009,7 @@
                 && Arrays.equals(mNativeAccessRules, toCompare.mNativeAccessRules)
                 && mProfileClass == toCompare.mProfileClass
                 && Arrays.equals(mEhplmns, toCompare.mEhplmns)
-                && Arrays.equals(mHplmns, toCompare.mHplmns);
+                && Arrays.equals(mHplmns, toCompare.mHplmns)
+                && mUsageSetting == toCompare.mUsageSetting;
     }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 821b74a..4e6a7a3 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1037,12 +1037,75 @@
     public static final String UICC_APPLICATIONS_ENABLED = SimInfo.COLUMN_UICC_APPLICATIONS_ENABLED;
 
     /**
-     * Indicate which network type is allowed. By default it's enabled.
+     * Indicate which network type is allowed.
      * @hide
      */
     public static final String ALLOWED_NETWORK_TYPES =
             SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"USAGE_SETTING_"},
+        value = {
+            USAGE_SETTING_UNKNOWN,
+            USAGE_SETTING_DEFAULT,
+            USAGE_SETTING_VOICE_CENTRIC,
+            USAGE_SETTING_DATA_CENTRIC})
+    public @interface UsageSetting {}
+
+    /**
+     * The usage setting is unknown.
+     *
+     * This will be the usage setting returned on devices that do not support querying the
+     * or setting the usage setting.
+     *
+     * It may also be provided by a carrier that wishes to provide a value to avoid making any
+     * settings changes.
+     */
+    public static final int USAGE_SETTING_UNKNOWN = -1;
+
+    /**
+     * Subscription uses the default setting.
+     *
+     * The value is based upon device capability and the other properties of the subscription.
+     *
+     * Most subscriptions will default to voice-centric when in a phone.
+     *
+     * An opportunistic subscription will default to data-centric.
+     *
+     * {@see SubscriptionInfo#isOpportunistic}
+     */
+    public static final int USAGE_SETTING_DEFAULT = 0;
+
+    /**
+     * This subscription is forced to voice-centric mode
+     *
+     * <p>Refer to voice-centric mode in 3gpp 24.301 sec 4.3 and 3gpp 24.501 sec 4.3.
+     * Also refer to "UE's usage setting" as defined in 3gpp 24.301 section 3.1 and 3gpp 23.221
+     * Annex A.
+     */
+    public static final int USAGE_SETTING_VOICE_CENTRIC = 1;
+
+    /**
+     * This subscription is forced to data-centric mode
+     *
+     * <p>Refer to data-centric mode in 3gpp 24.301 sec 4.3 and 3gpp 24.501 sec 4.3.
+     * Also refer to "UE's usage setting" as defined in 3gpp 24.301 section 3.1 and 3gpp 23.221
+     * Annex A.
+     */
+    public static final int USAGE_SETTING_DATA_CENTRIC = 2;
+
+    /**
+     * Indicate the preferred usage setting for the subscription.
+     *
+     * 0 - Default - If the value has not been explicitly set, it will be "default"
+     * 1 - Voice-centric
+     * 2 - Data-centric
+     *
+     * @hide
+     */
+    public static final String USAGE_SETTING = SimInfo.COLUMN_USAGE_SETTING;
+
     /**
      * Broadcast Action: The user has changed one of the default subs related to
      * data, phone calls, or sms</p>
@@ -3950,4 +4013,33 @@
             throw ex.rethrowAsRuntimeException();
         }
     }
+
+    /**
+     * Set the preferred usage setting.
+     *
+     * The cellular usage setting is a switch which controls the mode of operation for the cellular
+     * radio to either require or not require voice service. It is not managed via Android’s
+     * Settings.
+     *
+     * @param subscriptionId the subId of the subscription.
+     * @param usageSetting the requested usage setting.
+     *
+     * @throws IllegalStateException if a specific mode or setting the mode is not supported on a
+     * particular device.
+     *
+     * <p>Requires {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * or that the calling app has CarrierPrivileges for the given subscription.
+     *
+     * Note: This method will not allow the setting of USAGE_SETTING_UNKNOWN.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    void setUsageSetting(int subscriptionId, @UsageSetting int usageSetting) {
+        if (VDBG) logd("[setUsageSetting]+ setting:" + usageSetting + " subId:" + subscriptionId);
+        setSubscriptionPropertyHelper(subscriptionId, "setUsageSetting",
+                (iSub)-> iSub.setUsageSetting(
+                        usageSetting, subscriptionId, mContext.getOpPackageName()));
+    }
 }
+
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c78e42f..2d16f9e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16448,4 +16448,76 @@
         }
         return null;
     }
+
+    /**
+     * Callback to listen for when the set of packages with carrier privileges for a SIM changes.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface CarrierPrivilegesListener {
+        /**
+         * Called when the set of packages with carrier privileges has changed.
+         *
+         * <p>Of note, this callback will <b>not</b> be fired if a carrier triggers a SIM profile
+         * switch and the same set of packages remains privileged after the switch.
+         *
+         * <p>At registration, the callback will receive the current set of privileged packages.
+         *
+         * @param privilegedPackageNames The updated set of package names that have carrier
+         *     privileges
+         * @param privilegedUids The updated set of UIDs that have carrier privileges
+         */
+        void onCarrierPrivilegesChanged(
+                @NonNull List<String> privilegedPackageNames, @NonNull int[] privilegedUids);
+    }
+
+    /**
+     * Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to
+     * receive callbacks when the set of packages with carrier privileges changes. The callback will
+     * immediately be called with the latest state.
+     *
+     * @param logicalSlotIndex The SIM slot to listen on
+     * @param executor The executor where {@code listener} will be invoked
+     * @param listener The callback to register
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void addCarrierPrivilegesListener(
+            int logicalSlotIndex,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CarrierPrivilegesListener listener) {
+        if (mContext == null) {
+            throw new IllegalStateException("Telephony service is null");
+        } else if (executor == null || listener == null) {
+            throw new IllegalArgumentException(
+                    "CarrierPrivilegesListener and executor must be non-null");
+        }
+        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (mTelephonyRegistryMgr == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        mTelephonyRegistryMgr.addCarrierPrivilegesListener(logicalSlotIndex, executor, listener);
+    }
+
+    /**
+     * Unregisters an existing {@link CarrierPrivilegesListener}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) {
+        if (mContext == null) {
+            throw new IllegalStateException("Telephony service is null");
+        } else if (listener == null) {
+            throw new IllegalArgumentException("CarrierPrivilegesListener must be non-null");
+        }
+        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (mTelephonyRegistryMgr == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        mTelephonyRegistryMgr.removeCarrierPrivilegesListener(listener);
+    }
 }
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 2f03475..892eb29 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -402,6 +402,21 @@
         }
 
         /**
+         * Notify the system that a given DataProfile was unthrottled.
+         *
+         * @param dataProfile DataProfile associated with an APN returned from the modem
+         */
+        public final void notifyDataProfileUnthrottled(@NonNull DataProfile dataProfile) {
+            synchronized (mApnUnthrottledCallbacks) {
+                for (IDataServiceCallback callback : mApnUnthrottledCallbacks) {
+                    mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED,
+                            mSlotIndex, 0, new ApnUnthrottledIndication(dataProfile,
+                                    callback)).sendToTarget();
+                }
+            }
+        }
+
+        /**
          * Called when the instance of data service is destroyed (e.g. got unbind or binder died)
          * or when the data service provider is removed. The extended class should implement this
          * method to perform cleanup works.
@@ -496,13 +511,20 @@
     }
 
     private static final class ApnUnthrottledIndication {
+        public final DataProfile dataProfile;
         public final String apn;
         public final IDataServiceCallback callback;
         ApnUnthrottledIndication(String apn,
                 IDataServiceCallback callback) {
+            this.dataProfile = null;
             this.apn = apn;
             this.callback = callback;
         }
+        ApnUnthrottledIndication(DataProfile dataProfile, IDataServiceCallback callback) {
+            this.dataProfile = dataProfile;
+            this.apn = null;
+            this.callback = callback;
+        }
     }
 
     private class DataServiceHandler extends Handler {
@@ -636,8 +658,13 @@
                     ApnUnthrottledIndication apnUnthrottledIndication =
                             (ApnUnthrottledIndication) message.obj;
                     try {
-                        apnUnthrottledIndication.callback
-                                .onApnUnthrottled(apnUnthrottledIndication.apn);
+                        if (apnUnthrottledIndication.dataProfile != null) {
+                            apnUnthrottledIndication.callback
+                                    .onDataProfileUnthrottled(apnUnthrottledIndication.dataProfile);
+                        } else {
+                            apnUnthrottledIndication.callback
+                                    .onApnUnthrottled(apnUnthrottledIndication.apn);
+                        }
                     } catch (RemoteException e) {
                         loge("Failed to call onApnUnthrottled. " + e);
                     }
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index d082715..051d6c5 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -260,8 +260,8 @@
 
     /**
      * Unthrottles the APN on the current transport.  There is no matching "APN throttle" method.
-     * Instead, the APN is throttled for the time specified in
-     * {@link DataCallResponse#getRetryDurationMillis}.
+     * Instead, the APN is throttled when {@link IDataService#setupDataCall} fails within
+     * the time specified by {@link DataCallResponse#getRetryDurationMillis}.
      * <p/>
      * see: {@link DataCallResponse#getRetryDurationMillis}
      *
@@ -279,4 +279,27 @@
             Rlog.e(TAG, "onApnUnthrottled: callback is null!");
         }
     }
+
+    /**
+     * Unthrottles the DataProfile on the current transport.
+     * There is no matching "DataProfile throttle" method.
+     * Instead, the DataProfile is throttled when {@link IDataService#setupDataCall} fails within
+     * the time specified by {@link DataCallResponse#getRetryDurationMillis}.
+     * <p/>
+     * see: {@link DataCallResponse#getRetryDurationMillis}
+     *
+     * @param dataProfile DataProfile containing the APN to be throttled
+     */
+    public void onDataProfileUnthrottled(final @NonNull DataProfile dataProfile) {
+        if (mCallback != null) {
+            try {
+                if (DBG) Rlog.d(TAG, "onDataProfileUnthrottled");
+                mCallback.onDataProfileUnthrottled(dataProfile);
+            } catch (RemoteException e) {
+                Rlog.e(TAG, "onDataProfileUnthrottled: remote exception", e);
+            }
+        } else {
+            Rlog.e(TAG, "onDataProfileUnthrottled: callback is null!");
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/data/IDataServiceCallback.aidl b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
index 9cc2fea..8205b5e 100644
--- a/telephony/java/android/telephony/data/IDataServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
@@ -17,6 +17,7 @@
 package android.telephony.data;
 
 import android.telephony.data.DataCallResponse;
+import android.telephony.data.DataProfile;
 
 /**
  * The call back interface
@@ -33,4 +34,5 @@
     void onHandoverStarted(int result);
     void onHandoverCancelled(int result);
     void onApnUnthrottled(in String apn);
+    void onDataProfileUnthrottled(in DataProfile dp);
 }
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index 9293a40..7a1a2af 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -93,8 +93,8 @@
      * Notify the framework that the ImsService has refreshed the PUBLISH
      * internally, which has resulted in a new PUBLISH result.
      * <p>
-     * This method must return both SUCCESS (200 OK) and FAILURE (300+) codes in order to
-     * keep the AOSP stack up to date.
+     * This method must be called to notify the framework of SUCCESS (200 OK) and FAILURE (300+)
+     * codes in order to keep the AOSP stack up to date.
      * @param reasonCode The SIP response code sent from the network.
      * @param reasonPhrase The optional reason response from the network. If the
      * network provided no reason with the sip code, the string should be empty.
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index a900c84..1e38b92 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -313,4 +313,15 @@
 
     void setPhoneNumber(int subId, int source, String number,
             String callingPackage, String callingFeatureId);
+
+    /**
+     * Set the Usage Setting for this subscription.
+     *
+     * @param usageSetting the usage setting for this subscription
+     * @param subId the unique SubscriptionInfo index in database
+     * @param callingPackage The package making the IPC.
+     *
+     * @throws SecurityException if doesn't have MODIFY_PHONE_STATE or Carrier Privileges
+     */
+    int setUsageSetting(int usageSetting, int subId, String callingPackage);
 }