Merge "Add source style for each TypedValue."
diff --git a/Android.bp b/Android.bp
index c202408..b4d5e58 100644
--- a/Android.bp
+++ b/Android.bp
@@ -305,6 +305,7 @@
         "core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl",
         "core/java/android/service/gatekeeper/IGateKeeperService.aidl",
         "core/java/android/service/contentcapture/IContentCaptureService.aidl",
+        "core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl",
         "core/java/android/service/notification/INotificationListener.aidl",
         "core/java/android/service/notification/IStatusBarNotificationHolder.aidl",
         "core/java/android/service/notification/IConditionListener.aidl",
@@ -1317,7 +1318,6 @@
         "ext",
         "framework",
         "voip-common",
-        "android.test.mock.impl",
     ],
     local_sourcepaths: frameworks_base_subdirs,
     installable: false,
diff --git a/api/current.txt b/api/current.txt
index 2357d08..3d6645b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16458,9 +16458,9 @@
 
   public class BiometricManager {
     method public int canAuthenticate();
-    field public static final int BIOMETRIC_ERROR_NO_BIOMETRICS = 11; // 0xb
+    field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
+    field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
     field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
-    field public static final int BIOMETRIC_ERROR_UNAVAILABLE = 1; // 0x1
     field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
   }
 
@@ -16503,6 +16503,7 @@
     method public android.hardware.biometrics.BiometricPrompt build();
     method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(java.lang.CharSequence);
     method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(java.lang.CharSequence, java.util.concurrent.Executor, android.content.DialogInterface.OnClickListener);
+    method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean);
     method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(java.lang.CharSequence);
     method public android.hardware.biometrics.BiometricPrompt.Builder setTitle(java.lang.CharSequence);
   }
@@ -53014,19 +53015,8 @@
     ctor public InspectionCompanion.UninitializedPropertyMapException();
   }
 
-  public final class IntEnumMapping {
-    method public java.lang.String nameOf(int);
-  }
-
-  public static final class IntEnumMapping.Builder {
-    ctor public IntEnumMapping.Builder();
-    method public android.view.inspector.IntEnumMapping.Builder addValue(java.lang.String, int);
-    method public android.view.inspector.IntEnumMapping build();
-    method public void clear();
-  }
-
   public final class IntFlagMapping {
-    method public java.lang.String[] namesOf(int);
+    method public java.util.Set<java.lang.String> get(int);
   }
 
   public static final class IntFlagMapping.Builder {
@@ -53034,7 +53024,6 @@
     method public android.view.inspector.IntFlagMapping.Builder addFlag(java.lang.String, int);
     method public android.view.inspector.IntFlagMapping.Builder addFlag(java.lang.String, int, int);
     method public android.view.inspector.IntFlagMapping build();
-    method public void clear();
   }
 
   public abstract interface PropertyMapper {
@@ -53046,7 +53035,7 @@
     method public abstract int mapFloat(java.lang.String, int);
     method public abstract int mapGravity(java.lang.String, int);
     method public abstract int mapInt(java.lang.String, int);
-    method public abstract int mapIntEnum(java.lang.String, int, android.view.inspector.IntEnumMapping);
+    method public abstract int mapIntEnum(java.lang.String, int, android.util.SparseArray<java.lang.String>);
     method public abstract int mapIntFlag(java.lang.String, int, android.view.inspector.IntFlagMapping);
     method public abstract int mapLong(java.lang.String, int);
     method public abstract int mapObject(java.lang.String, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 276be9d..83d1da7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -94,7 +94,6 @@
     field public static final java.lang.String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
     field public static final java.lang.String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
     field public static final java.lang.String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
-    field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
     field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
     field public static final java.lang.String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
@@ -151,6 +150,7 @@
     field public static final java.lang.String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
     field public static final java.lang.String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
     field public static final java.lang.String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
+    field public static final java.lang.String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
     field public static final java.lang.String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
     field public static final java.lang.String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final java.lang.String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -4445,6 +4445,10 @@
     method public abstract java.lang.Object onTransactStarted(android.os.IBinder, int);
   }
 
+  public static class Build.VERSION {
+    field public static final java.lang.String PREVIEW_SDK_FINGERPRINT;
+  }
+
   public final class ConfigUpdate {
     field public static final java.lang.String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
     field public static final java.lang.String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 4beb699..5a7ec8b 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -1,3 +1,11 @@
+package android {
+
+  public static final class Manifest.permission {
+    field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
+  }
+
+}
+
 package android.app {
 
   public class Notification implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 71f02b9..6ddb341 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -509,6 +509,11 @@
     method public static android.graphics.ImageDecoder.Source createSource(android.content.res.Resources, java.io.InputStream, int);
   }
 
+  public class Paint {
+    method public void setColor(long);
+    method public void setShadowLayer(float, float, float, long);
+  }
+
 }
 
 package android.graphics.drawable {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f4a1715..453a0c0 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -25,6 +25,7 @@
 import "frameworks/base/core/proto/android/app/settings_enums.proto";
 import "frameworks/base/core/proto/android/app/job/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/enums.proto";
+import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
 import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
 import "frameworks/base/core/proto/android/os/enums.proto";
 import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
@@ -183,6 +184,7 @@
         RescuePartyResetReported rescue_party_reset_reported = 122;
         SignedConfigReported signed_config_reported = 123;
         GnssNiEventReported gnss_ni_event_reported = 124;
+        BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125;
     }
 
     // Pulled events will start at field 10000.
@@ -1316,6 +1318,90 @@
     optional int32 bt_profile = 3;
 }
 
+// Logs when there is an event affecting Bluetooth device's link layer connection.
+// - This event is triggered when there is a related HCI command or event
+// - Users of this metrics can deduce Bluetooth device's connection state from these events
+// - HCI commands are logged before the command is sent, after receiving command status, and after
+//   receiving command complete
+// - HCI events are logged when they arrive
+//
+// Low level log from system/bt
+//
+// Bluetooth classic commands:
+// - CMD_CREATE_CONNECTION
+// - CMD_DISCONNECT
+// - CMD_CREATE_CONNECTION_CANCEL
+// - CMD_ACCEPT_CONNECTION_REQUEST
+// - CMD_REJECT_CONNECTION_REQUEST
+// - CMD_SETUP_ESCO_CONNECTION
+// - CMD_ACCEPT_ESCO_CONNECTION
+// - CMD_REJECT_ESCO_CONNECTION
+// - CMD_ENH_SETUP_ESCO_CONNECTION
+// - CMD_ENH_ACCEPT_ESCO_CONNECTION
+//
+// Bluetooth low energy commands:
+// - CMD_BLE_CREATE_LL_CONN [Only logged on error or when initiator filter policy is 0x00]
+// - CMD_BLE_CREATE_CONN_CANCEL [Only logged when there is an error]
+// - CMD_BLE_EXTENDED_CREATE_CONNECTION [Only logged on error or when initiator filter policy is 0x00]
+// - CMD_BLE_CLEAR_WHITE_LIST
+// - CMD_BLE_ADD_WHITE_LIST
+// - CMD_BLE_REMOVE_WHITE_LIST
+//
+// Bluetooth classic events:
+// - EVT_CONNECTION_COMP
+// - EVT_CONNECTION_REQUEST
+// - EVT_DISCONNECTION_COMP
+// - EVT_ESCO_CONNECTION_COMP
+// - EVT_ESCO_CONNECTION_CHANGED
+//
+// Bluetooth low energy meta events:
+// - BLE_EVT_CONN_COMPLETE_EVT
+// - BLE_EVT_ENHANCED_CONN_COMPLETE_EVT
+//
+// Next tag: 10
+message BluetoothLinkLayerConnectionEvent {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Connection handle of this connection if available
+    // Range: 0x0000 - 0x0EFF (12 bits)
+    // Default: 0xFFFF if the handle is unknown
+    optional int32 connection_handle = 2;
+    // Direction of the link
+    // Default: DIRECTION_UNKNOWN
+    optional android.bluetooth.DirectionEnum direction = 3;
+    // Type of this link
+    // Default: LINK_TYPE_UNKNOWN
+    optional android.bluetooth.LinkTypeEnum type = 4;
+
+    // Reason metadata for this link layer connection event, rules for interpretation:
+    // 1. If hci_cmd is set and valid, hci_event can be either EVT_COMMAND_STATUS or
+    //    EVT_COMMAND_COMPLETE, ignore hci_ble_event in this case
+    // 2. If hci_event is set to EVT_BLE_META, look at hci_ble_event; otherwise, if hci_event is
+    //    set and valid, ignore hci_ble_event
+
+    // HCI command associated with this event
+    // Default: CMD_UNKNOWN
+    optional android.bluetooth.hci.CommandEnum hci_cmd = 5;
+    // HCI event associated with this event
+    // Default: EVT_UNKNOWN
+    optional android.bluetooth.hci.EventEnum hci_event = 6;
+    // HCI BLE meta event associated with this event
+    // Default: BLE_EVT_UNKNOWN
+    optional android.bluetooth.hci.BleMetaEventEnum hci_ble_event = 7;
+    // HCI command status code if this is triggerred by hci_cmd
+    // Default: STATUS_UNKNOWN
+    optional android.bluetooth.hci.StatusEnum cmd_status = 8;
+    // HCI reason code associated with this event
+    // Default: STATUS_UNKNOWN
+    optional android.bluetooth.hci.StatusEnum reason_code = 9;
+}
+
+
 /**
  * Logs when something is plugged into or removed from the USB-C connector.
  *
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 4d3c5d1..5392a3c 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -1492,7 +1492,6 @@
 Landroid/test/AndroidTestCase;->getTestContext()Landroid/content/Context;
 Landroid/test/AndroidTestCase;->setTestContext(Landroid/content/Context;)V
 Landroid/test/InstrumentationTestCase;->runMethod(Ljava/lang/reflect/Method;I)V
-Landroid/test/RepetitiveTest;->numIterations()I
 Landroid/util/Singleton;-><init>()V
 Landroid/util/XmlPullAttributes;-><init>(Lorg/xmlpull/v1/XmlPullParser;)V
 Landroid/util/XmlPullAttributes;->mParser:Lorg/xmlpull/v1/XmlPullParser;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 98c5a0fb..d374f1c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.os.Process.myUid;
 
 import static java.lang.Character.MIN_VALUE;
 
@@ -1025,7 +1026,7 @@
      */
     @Nullable private ContentCaptureManager getContentCaptureManager() {
         // ContextCapture disabled for system apps
-        if (getApplicationInfo().isSystemApp()) return null;
+        if (!UserHandle.isApp(myUid())) return null;
         if (mContentCaptureManager == null) {
             mContentCaptureManager = getSystemService(ContentCaptureManager.class);
         }
@@ -1048,9 +1049,8 @@
 
     private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
         final ContentCaptureManager cm = getContentCaptureManager();
-        if (cm == null) {
-            return;
-        }
+        if (cm == null) return;
+
         switch (type) {
             case CONTENT_CAPTURE_START:
                 //TODO(b/111276913): decide whether the InteractionSessionId should be
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d423260..e0b8d78 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3705,11 +3705,16 @@
      * Returns whether switching to provided user was successful.
      *
      * @param user the user to switch to.
+     *
+     * @throws IllegalArgumentException if the user is null.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
-    public boolean switchUser(UserHandle user) {
+    public boolean switchUser(@NonNull UserHandle user) {
+        if (user == null) {
+            throw new IllegalArgumentException("UserHandle cannot be null.");
+        }
         return switchUser(user.getIdentifier());
     }
 
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 57132a7..ab8f234 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -16,6 +16,10 @@
 
 package android.app;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+
 import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityManager.StackInfo;
@@ -32,7 +36,6 @@
 import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.MotionEvent;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceSession;
@@ -82,7 +85,6 @@
     private boolean mOpened; // Protected by mGuard.
 
     private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
-    private Surface mTmpSurface = new Surface();
 
     /** The ActivityView is only allowed to contain one task. */
     private final boolean mSingleTaskInstance;
@@ -319,20 +321,20 @@
     private class SurfaceCallback implements SurfaceHolder.Callback {
         @Override
         public void surfaceCreated(SurfaceHolder surfaceHolder) {
-            mTmpSurface = new Surface();
             if (mVirtualDisplay == null) {
                 initVirtualDisplay(new SurfaceSession());
                 if (mVirtualDisplay != null && mActivityViewCallback != null) {
                     mActivityViewCallback.onActivityViewReady(ActivityView.this);
                 }
             } else {
-                // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by
-                // whether it has a surface. Setting a fake surface here so DisplayManager will
-                // consider this display on.
-                mVirtualDisplay.setSurface(mTmpSurface);
                 mTmpTransaction.reparent(mRootSurfaceControl,
                         mSurfaceView.getSurfaceControl().getHandle()).apply();
             }
+
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.setDisplayState(true);
+            }
+
             updateLocation();
         }
 
@@ -346,10 +348,8 @@
 
         @Override
         public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
-            mTmpSurface.release();
-            mTmpSurface = null;
             if (mVirtualDisplay != null) {
-                mVirtualDisplay.setSurface(null);
+                mVirtualDisplay.setDisplayState(false);
             }
             cleanTapExcludeRegion();
         }
@@ -370,15 +370,11 @@
         final int height = mSurfaceView.getHeight();
         final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
 
-        // TODO (b/119209373): DisplayManager determines if a VirtualDisplay is on by
-        // whether it has a surface. Setting a fake surface here so DisplayManager will consider
-        // this display on.
         mVirtualDisplay = displayManager.createVirtualDisplay(
-                DISPLAY_NAME + "@" + System.identityHashCode(this),
-                width, height, getBaseDisplayDensity(), mTmpSurface,
-                DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
-                        | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
-                        | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
+                DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
+                getBaseDisplayDensity(), null,
+                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                        | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
         if (mVirtualDisplay == null) {
             Log.e(TAG, "Failed to initialize ActivityView");
             return;
@@ -443,11 +439,6 @@
             displayReleased = false;
         }
 
-        if (mTmpSurface != null) {
-            mTmpSurface.release();
-            mTmpSurface = null;
-        }
-
         if (displayReleased && mActivityViewCallback != null) {
             mActivityViewCallback.onActivityViewDestroyed(this);
         }
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 83563d0..45b5dca 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -137,6 +137,12 @@
     public abstract void setLocationPackagesProvider(PackagesProvider provider);
 
     /**
+     * Set the location extra packages provider.
+     * @param provider The packages provider.
+     */
+    public abstract  void setLocationExtraPackagesProvider(PackagesProvider provider);
+
+    /**
      * Sets the voice interaction packages provider.
      * @param provider The packages provider.
      */
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index eb3414d..8aac1bf 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -42,13 +42,13 @@
     /**
      * The hardware is unavailable. Try again later.
      */
-    public static final int BIOMETRIC_ERROR_UNAVAILABLE =
+    public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE =
             BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
 
     /**
      * The user does not have any biometrics enrolled.
      */
-    public static final int BIOMETRIC_ERROR_NO_BIOMETRICS =
+    public static final int BIOMETRIC_ERROR_NONE_ENROLLED =
             BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS;
 
     /**
@@ -58,8 +58,8 @@
             BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
 
     @IntDef({BIOMETRIC_SUCCESS,
-            BIOMETRIC_ERROR_UNAVAILABLE,
-            BIOMETRIC_ERROR_NO_BIOMETRICS,
+            BIOMETRIC_ERROR_HW_UNAVAILABLE,
+            BIOMETRIC_ERROR_NONE_ENROLLED,
             BIOMETRIC_ERROR_NO_HARDWARE})
     @interface BiometricError {}
 
@@ -95,8 +95,8 @@
      * Determine if biometrics can be used. In other words, determine if {@link BiometricPrompt}
      * can be expected to be shown (hardware available, templates enrolled, user-enabled).
      *
-     * @return Returns {@link #BIOMETRIC_ERROR_NO_BIOMETRICS} if the user does not have any
-     *     enrolled, or {@link #BIOMETRIC_ERROR_UNAVAILABLE} if none are currently
+     * @return Returns {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any
+     *     enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are currently
      *     supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if a biometric can currently be
      *     used (enrolled and available).
      */
@@ -113,7 +113,7 @@
                 return BIOMETRIC_ERROR_NO_HARDWARE;
             } else {
                 Slog.w(TAG, "hasEnrolledBiometrics(): Service not connected");
-                return BIOMETRIC_ERROR_UNAVAILABLE;
+                return BIOMETRIC_ERROR_HW_UNAVAILABLE;
             }
         }
     }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index f652f85..c69b68e4 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -235,7 +235,6 @@
          * requiring confirmation.
          *
          * @param requireConfirmation
-         * @hide
          */
         public Builder setRequireConfirmation(boolean requireConfirmation) {
             mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index cda8498..7e45441 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -436,6 +436,7 @@
     public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
         try {
             mDm.setVirtualDisplaySurface(token, surface);
+            setVirtualDisplayState(token, surface != null);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -458,6 +459,14 @@
         }
     }
 
+    void setVirtualDisplayState(IVirtualDisplayCallback token, boolean isOn) {
+        try {
+            mDm.setVirtualDisplayState(token, isOn);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Gets the stable device display size, in pixels.
      */
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 2d81cdf..aae8afb 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -82,6 +82,9 @@
     // No permissions required but must be same Uid as the creator.
     void releaseVirtualDisplay(in IVirtualDisplayCallback token);
 
+    // No permissions required but must be same Uid as the creator.
+    void setVirtualDisplayState(in IVirtualDisplayCallback token, boolean isOn);
+
     // Get a stable metric for the device's display size. No permissions required.
     Point getStableDisplaySize();
 
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index d354666..bf62c95 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -104,6 +104,18 @@
         }
     }
 
+    /**
+     * Sets the on/off state for a virtual display.
+     *
+     * @param isOn Whether the display should be on or off.
+     * @hide
+     */
+    public void setDisplayState(boolean isOn) {
+        if (mToken != null) {
+            mGlobal.setVirtualDisplayState(mToken, isOn);
+        }
+    }
+
     @Override
     public String toString() {
         return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken
diff --git a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
index d382eb9..bdd5ab6 100644
--- a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
+++ b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
@@ -15,10 +15,12 @@
  */
 package android.hardware.hdmi;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -56,6 +58,21 @@
         mHandler = handler == null ? new Handler(Looper.getMainLooper()) : handler;
     }
 
+    /**
+     * Callback interface used to get the set System Audio Mode result.
+     *
+     * @hide
+     */
+    // TODO(b/110094868): unhide and add @SystemApi for Q
+    public interface SetSystemAudioModeCallback {
+        /**
+         * Called when the input was changed.
+         *
+         * @param result the result of the set System Audio Mode
+         */
+        void onComplete(int result);
+    }
+
     /** @hide */
     // TODO(b/110094868): unhide and add @SystemApi for Q
     @Override
@@ -117,4 +134,34 @@
             mPendingReportAudioStatus = true;
         }
     }
+
+    /**
+     * Set System Audio Mode on/off with audio system device.
+     *
+     * @param state true to set System Audio Mode on. False to set off.
+     * @param callback callback offer the setting result.
+     *
+     * @hide
+     */
+    // TODO(b/110094868): unhide and add @SystemApi for Q
+    public void setSystemAudioMode(boolean state, @NonNull SetSystemAudioModeCallback callback) {
+        // TODO(amyjojo): implement this when needed.
+    }
+
+    /**
+     * When device is switching to an audio only source, this method is called to broadcast
+     * a setSystemAudioMode on message to the HDMI CEC system without querying Active Source or
+     * TV supporting System Audio Control or not. This is to get volume control passthrough
+     * from STB even if TV does not support it.
+     *
+     * @hide
+     */
+    // TODO(b/110094868): unhide and add @SystemApi for Q
+    public void setSystemAudioModeOnForAudioOnlySource() {
+        try {
+            mService.setSystemAudioModeOnForAudioOnlySource();
+        } catch (RemoteException e) {
+            Log.d(TAG, "Failed to set System Audio Mode on for Audio Only source");
+        }
+    }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index f5d288e..a7734f5 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -33,6 +33,8 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import java.util.List;
+
 /**
  * The {@link HdmiControlManager} class is used to send HDMI control messages
  * to attached CEC devices.
@@ -404,6 +406,72 @@
     }
 
     /**
+     * Get a snapshot of the real-time status of the remote devices.
+     *
+     * @return a list of {@link HdmiDeviceInfo} of the devices connected to the current device.
+     *
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public List<HdmiDeviceInfo> getConnectedDevicesList() {
+        try {
+            return mService.getDeviceList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Power off the target device.
+     *
+     * @param deviceInfo HdmiDeviceInfo of the device to be powered off
+     *
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public void powerOffRemoteDevice(HdmiDeviceInfo deviceInfo) {
+        try {
+            mService.powerOffRemoteDevice(
+                    deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Power on the target device.
+     *
+     * @param deviceInfo HdmiDeviceInfo of the device to be powered on
+     *
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
+        try {
+            mService.powerOnRemoteDevice(
+                    deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Ask the target device to be the new Active Source.
+     *
+     * @param deviceInfo HdmiDeviceInfo of the target device
+     *
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    public void askRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+        try {
+            mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Controls standby mode of the system. It will also try to turn on/off the connected devices if
      * necessary.
      *
@@ -432,6 +500,19 @@
     }
 
     /**
+     * Get the physical address of the device.
+     *
+     * @hide
+     */
+    public int getPhysicalAddress() {
+        try {
+            return mService.getPhysicalAddress();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Listener used to get hotplug event from HDMI port.
      */
     public interface HotplugEventListener {
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 2b8d00b..1cd9920 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -50,6 +50,7 @@
     List<HdmiPortInfo> getPortInfo();
     boolean canChangeSystemAudioMode();
     boolean getSystemAudioMode();
+    int getPhysicalAddress();
     void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback);
     void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
     void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener);
@@ -60,6 +61,9 @@
     void setInputChangeListener(IHdmiInputChangeListener listener);
     List<HdmiDeviceInfo> getInputDevices();
     List<HdmiDeviceInfo> getDeviceList();
+    void powerOffRemoteDevice(int logicalAddress, int powerStatus);
+    void powerOnRemoteDevice(int logicalAddress, int powerStatus);
+    void askRemoteDeviceToBecomeActiveSource(int physicalAddress);
     void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
             boolean hasVendorId);
     void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
@@ -73,4 +77,5 @@
     void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener);
     void setStandbyMode(boolean isStandbyModeOn);
     void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute);
+    void setSystemAudioModeOnForAudioOnlySource();
 }
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 617125b3..c2963fd 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -191,6 +191,7 @@
             }
             setMtu(source.mMtu);
             mTcpBufferSizes = source.mTcpBufferSizes;
+            mNat64Prefix = source.mNat64Prefix;
         }
     }
 
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index d7e5b27..b932d21 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -28,6 +28,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.StringJoiner;
 
 /**
  * A POD object to represent attributes of a single L2 network entry.
@@ -207,4 +208,52 @@
     public int hashCode() {
         return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
     }
+
+    /** Pretty print */
+    @Override
+    public String toString() {
+        final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
+        final ArrayList<String> nullFields = new ArrayList<>();
+
+        if (null != assignedV4Address) {
+            resultJoiner.add("assignedV4Addr :");
+            resultJoiner.add(assignedV4Address.toString());
+        } else {
+            nullFields.add("assignedV4Addr");
+        }
+
+        if (null != groupHint) {
+            resultJoiner.add("groupHint :");
+            resultJoiner.add(groupHint);
+        } else {
+            nullFields.add("groupHint");
+        }
+
+        if (null != dnsAddresses) {
+            resultJoiner.add("dnsAddr : [");
+            for (final InetAddress addr : dnsAddresses) {
+                resultJoiner.add(addr.getHostAddress());
+            }
+            resultJoiner.add("]");
+        } else {
+            nullFields.add("dnsAddr");
+        }
+
+        if (null != mtu) {
+            resultJoiner.add("mtu :");
+            resultJoiner.add(mtu.toString());
+        } else {
+            nullFields.add("mtu");
+        }
+
+        if (!nullFields.isEmpty()) {
+            resultJoiner.add("; Null fields : [");
+            for (final String field : nullFields) {
+                resultJoiner.add(field);
+            }
+            resultJoiner.add("]");
+        }
+
+        return resultJoiner.toString();
+    }
 }
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
index 0cb37e9..d040dcc 100644
--- a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -128,4 +128,19 @@
     public int hashCode() {
         return Objects.hash(l2Key1, l2Key2, confidence);
     }
+
+    @Override
+    /** Pretty print */
+    public String toString() {
+        switch (getNetworkSameness()) {
+            case NETWORK_SAME:
+                return "\"" + l2Key1 + "\" same L3 network as \"" + l2Key2 + "\"";
+            case NETWORK_DIFFERENT:
+                return "\"" + l2Key1 + "\" different L3 network from \"" + l2Key2 + "\"";
+            case NETWORK_NEVER_CONNECTED:
+                return "\"" + l2Key1 + "\" can't be tested against \"" + l2Key2 + "\"";
+            default:
+                return "Buggy sameness value ? \"" + l2Key1 + "\", \"" + l2Key2 + "\"";
+        }
+    }
 }
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
index 5b016ec..95e5042 100644
--- a/core/java/android/net/ipmemorystore/Status.java
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -26,6 +26,8 @@
 public class Status {
     public static final int SUCCESS = 0;
 
+    public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1;
+
     public final int resultCode;
 
     public Status(final int resultCode) {
@@ -47,4 +49,14 @@
     public boolean isSuccess() {
         return SUCCESS == resultCode;
     }
+
+    /** Pretty print */
+    @Override
+    public String toString() {
+        switch (resultCode) {
+            case SUCCESS: return "SUCCESS";
+            case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
+            default: return "Unknown value ?!";
+        }
+    }
 }
diff --git a/core/java/android/net/ipmemorystore/Utils.java b/core/java/android/net/ipmemorystore/Utils.java
new file mode 100644
index 0000000..73d8c83
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/** {@hide} */
+public class Utils {
+    /** Pretty print */
+    public static String blobToString(final Blob blob) {
+        final StringBuilder sb = new StringBuilder("Blob : [");
+        if (blob.data.length <= 24) {
+            appendByteArray(sb, blob.data, 0, blob.data.length);
+        } else {
+            appendByteArray(sb, blob.data, 0, 16);
+            sb.append("...");
+            appendByteArray(sb, blob.data, blob.data.length - 8, blob.data.length);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    // Adds the hex representation of the array between the specified indices (inclusive, exclusive)
+    private static void appendByteArray(@NonNull final StringBuilder sb, @NonNull final byte[] ar,
+            final int from, final int to) {
+        for (int i = from; i < to; ++i) {
+            sb.append(String.format("%02X", ar[i]));
+        }
+    }
+}
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index b15a4d3..cbb3909 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -114,7 +114,6 @@
         }
     }
 
-    // TODO(b/111441001) Connect up with BugreportListener methods.
     private final class DumpstateListener extends IDumpstateListener.Stub
             implements DeathRecipient {
         private final BugreportListener mListener;
@@ -130,35 +129,35 @@
 
         @Override
         public void onProgress(int progress) throws RemoteException {
-            // TODO(b/111441001): implement
+            mListener.onProgress(progress);
         }
 
         @Override
         public void onError(int errorCode) throws RemoteException {
-            // TODO(b/111441001): implement
+            mListener.onError(errorCode);
         }
 
         @Override
         public void onFinished(long durationMs, String title, String description)
                 throws RemoteException {
-            // TODO(b/111441001): implement
+            mListener.onFinished(durationMs, title, description);
         }
 
         // Old methods; should go away
         @Override
         public void onProgressUpdated(int progress) throws RemoteException {
-            // TODO(b/111441001): implement
+            // TODO(b/111441001): remove from interface
         }
 
         @Override
         public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
-            // TODO(b/111441001): implement
+            // TODO(b/111441001): remove from interface
         }
 
         @Override
         public void onSectionComplete(String title, int status, int size, int durationMs)
                 throws RemoteException {
-            // TODO(b/111441001): implement
+            // TODO(b/111441001): remove from interface
         }
     }
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9fea873..2d61a4e 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -289,6 +289,26 @@
                 "ro.build.version.preview_sdk", 0);
 
         /**
+         * The SDK fingerprint for a given prerelease SDK. This value will always be
+         * {@code REL} on production platform builds/devices.
+         *
+         * <p>When this value is not {@code REL}, it contains a string fingerprint of the API
+         * surface exposed by the preview SDK. Preview platforms with different API surfaces
+         * will have different {@code PREVIEW_SDK_FINGERPRINT}.
+         *
+         * <p>This attribute is intended for use by installers for finer grained targeting of
+         * packages. Applications targeting preview APIs should not use this field and should
+         * instead use {@code PREVIEW_SDK_INT} or use reflection or other runtime checks to
+         * detect the presence of an API or guard themselves against unexpected runtime
+         * behavior.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final String PREVIEW_SDK_FINGERPRINT = SystemProperties.get(
+                "ro.build.version.preview_sdk_fingerprint", "REL");
+
+        /**
          * The current development codename, or the string "REL" if this is
          * a release build.
          */
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index fdd7488..8ced722 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -356,16 +356,6 @@
     void removeVpnUidRanges(int netId, in UidRange[] ranges);
 
     /**
-     * Start the clatd (464xlat) service on the given interface.
-     */
-    void startClatd(String interfaceName);
-
-    /**
-     * Stop the clatd (464xlat) service on the given interface.
-     */
-    void stopClatd(String interfaceName);
-
-    /**
      * Start listening for mobile activity state changes.
      */
     void registerNetworkActivityListener(INetworkActivityListener listener);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8482548..39c4266 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9381,6 +9381,15 @@
                 "hdmi_system_audio_control_enabled";
 
         /**
+         * Whether HDMI Routing Control feature is enabled. If enabled, the switch device will
+         * route to the correct input source on receiving Routing Control related messages. If
+         * disabled, you can only switch the input via controls on this device.
+         * @hide
+         */
+        public static final String HDMI_CEC_SWITCH_ENABLED =
+                "hdmi_cec_switch_enabled";
+
+        /**
          * Whether TV will automatically turn on upon reception of the CEC command
          * &lt;Text View On&gt; or &lt;Image View On&gt;. (0 = false, 1 = true)
          *
@@ -11082,6 +11091,31 @@
         /** {@hide} */
         public static final String
                 BLUETOOTH_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_";
+        /**
+         * Enable/disable radio bug detection
+         *
+         * {@hide}
+         */
+        public static final String
+                ENABLE_RADIO_BUG_DETECTION = "enable_radio_bug_detection";
+
+        /**
+         * Count threshold of RIL wakelock timeout for radio bug detection
+         *
+         * {@hide}
+         */
+        public static final String
+                RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD =
+                "radio_bug_wakelock_timeout_count_threshold";
+
+        /**
+         * Count threshold of RIL system error for radio bug detection
+         *
+         * {@hide}
+         */
+        public static final String
+                RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD =
+                "radio_bug_system_error_count_threshold";
 
         /**
          * Activity manager specific settings.
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 0116961..aaba85b 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -42,6 +42,7 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAugmentedAutofillManagerClient;
+import android.view.autofill.IAutofillWindowPresenter;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -95,10 +96,10 @@
         }
 
         @Override
-        public void onDestroyFillWindowRequest(int sessionId) {
+        public void onDestroyAllFillWindowsRequest() {
             mHandler.sendMessage(
-                    obtainMessage(AugmentedAutofillService::handleOnDestroyFillWindowRequest,
-                            AugmentedAutofillService.this, sessionId));
+                    obtainMessage(AugmentedAutofillService::handleOnDestroyAllFillWindowsRequest,
+                            AugmentedAutofillService.this));
         }
     };
 
@@ -185,18 +186,21 @@
                 new FillCallback(proxy));
     }
 
-    private void handleOnDestroyFillWindowRequest(@NonNull int sessionId) {
-        AutofillProxy proxy = null;
+    private void handleOnDestroyAllFillWindowsRequest() {
         if (mAutofillProxies != null) {
-            proxy = mAutofillProxies.get(sessionId);
+            final int size = mAutofillProxies.size();
+            for (int i = 0; i < size; i++) {
+                final int sessionId = mAutofillProxies.keyAt(i);
+                final AutofillProxy proxy = mAutofillProxies.valueAt(i);
+                if (proxy == null) {
+                    // TODO(b/111330312): this might be fine, in which case we should logv it
+                    Log.w(TAG, "No proxy for session " + sessionId);
+                    return;
+                }
+                proxy.destroy();
+            }
+            mAutofillProxies.clear();
         }
-        if (proxy == null) {
-            // TODO(b/111330312): this might be fine, in which case we should logv it
-            Log.w(TAG, "No proxy for session " + sessionId);
-            return;
-        }
-        proxy.destroy();
-        mAutofillProxies.remove(sessionId);
     }
 
     private void handleOnUnbind() {
@@ -350,6 +354,16 @@
             }
         }
 
+        public void requestShowFillUi(int width, int height, Rect anchorBounds,
+                IAutofillWindowPresenter presenter) throws RemoteException {
+            mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds,
+                    presenter);
+        }
+
+        public void requestHideFillUi() throws RemoteException {
+            mClient.requestHideFillUi(mSessionId, mFocusedId);
+        }
+
         private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) {
             synchronized (mLock) {
                 // TODO(b/111330312): should we close the popupwindow if the focused id changed?
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 33b88e42..51b0f01 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -16,22 +16,25 @@
 package android.service.autofill.augmented;
 
 import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG;
+import static android.service.autofill.augmented.AugmentedAutofillService.VERBOSE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.app.Dialog;
 import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
 import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
 import android.service.autofill.augmented.PresentationParams.Area;
 import android.util.Log;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
 import android.view.WindowManager;
+import android.view.autofill.IAutofillWindowPresenter;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
@@ -71,7 +74,7 @@
     /** Indicates the data being shown is a physical address */
     public static final long FLAG_METADATA_ADDRESS = 0x1;
 
-    // TODO(b/111330312): add moar flags
+    // TODO(b/111330312): add more flags
 
     /** @hide */
     @LongDef(prefix = { "FLAG" }, value = {
@@ -83,8 +86,17 @@
     private final Object mLock = new Object();
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
+    private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
+    private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter();
+
     @GuardedBy("mLock")
-    private Dialog mDialog;
+    private WindowManager mWm;
+    @GuardedBy("mLock")
+    private View mFillView;
+    @GuardedBy("mLock")
+    private boolean mShowing;
+    @GuardedBy("mLock")
+    private Rect mBounds;
 
     @GuardedBy("mLock")
     private boolean mDestroyed;
@@ -140,51 +152,28 @@
             // window instead of destroying. In fact, it might be better to allocate a full window
             // initially, which is transparent (and let touches get through) everywhere but in the
             // rect boundaries.
-            destroy();
 
             // TODO(b/111330312): make sure all touch events are handled, window is always closed,
             // etc.
 
-            mDialog = new Dialog(rootView.getContext()) {
-                @Override
-                public boolean onTouchEvent(MotionEvent event) {
-                    if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
-                        FillWindow.this.destroy();
+            mWm = rootView.getContext().getSystemService(WindowManager.class);
+            mFillView = rootView;
+            // Listen to the touch outside to destroy the window when typing is detected.
+            mFillView.setOnTouchListener(
+                    (view, motionEvent) -> {
+                        if (motionEvent.getAction() == MotionEvent.ACTION_OUTSIDE) {
+                            if (VERBOSE) Log.v(TAG, "Outside touch detected, hiding the window");
+                            hide();
+                        }
+                        return false;
                     }
-                    return false;
-                }
-            };
-            mCloseGuard.open("destroy");
-            final Window window = mDialog.getWindow();
-            window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
-            // Makes sure touch outside the dialog is received by the window behind the dialog.
-            window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
-            // Makes sure the touch outside the dialog is received by the dialog to dismiss it.
-            window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
-            // Makes sure keyboard shows up.
-            window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
-
-            final int height = rect.bottom - rect.top;
-            final int width = rect.right - rect.left;
-            final WindowManager.LayoutParams windowParams = window.getAttributes();
-            windowParams.gravity = Gravity.TOP | Gravity.LEFT;
-            windowParams.y = rect.top + height;
-            windowParams.height = height;
-            windowParams.x = rect.left;
-            windowParams.width = width;
-
-            window.setAttributes(windowParams);
-            window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-            window.setBackgroundDrawableResource(android.R.color.transparent);
-
-            mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
-            final ViewGroup.LayoutParams diagParams = new ViewGroup.LayoutParams(width, height);
-            mDialog.setContentView(rootView, diagParams);
-
+            );
+            mShowing = false;
+            mBounds = new Rect(area.getBounds());
             if (DEBUG) {
                 Log.d(TAG, "Created FillWindow: params= " + smartSuggestion + " view=" + rootView);
             }
-
+            mDestroyed = false;
             mProxy.setFillWindow(this);
             return true;
         }
@@ -194,36 +183,87 @@
     void show() {
         // TODO(b/111330312): check if updated first / throw exception
         if (DEBUG) Log.d(TAG, "show()");
-
         synchronized (mLock) {
             checkNotDestroyedLocked();
-            if (mDialog == null) {
+            if (mWm == null || mFillView == null) {
                 throw new IllegalStateException("update() not called yet, or already destroyed()");
             }
-
-            mDialog.show();
             if (mProxy != null) {
+                try {
+                    mProxy.requestShowFillUi(mBounds.right - mBounds.left,
+                            mBounds.bottom - mBounds.top,
+                            /*anchorBounds=*/ null, mFillWindowPresenter);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error requesting to show fill window", e);
+                }
                 mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
             }
         }
     }
 
     /**
+     * Hides the window.
+     *
+     * <p>The window is not destroyed and can be shown again
+     */
+    private void hide() {
+        if (DEBUG) Log.d(TAG, "hide()");
+        synchronized (mLock) {
+            checkNotDestroyedLocked();
+            if (mWm == null || mFillView == null) {
+                throw new IllegalStateException("update() not called yet, or already destroyed()");
+            }
+            if (mProxy != null && mShowing) {
+                try {
+                    mProxy.requestHideFillUi();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error requesting to hide fill window", e);
+                }
+            }
+        }
+    }
+
+    private void handleShow(WindowManager.LayoutParams p) {
+        if (DEBUG) Log.d(TAG, "handleShow()");
+        synchronized (mLock) {
+            if (mWm != null && mFillView != null) {
+                p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+                if (!mShowing) {
+                    mWm.addView(mFillView, p);
+                    mShowing = true;
+                } else {
+                    mWm.updateViewLayout(mFillView, p);
+                }
+            }
+        }
+    }
+
+    private void handleHide() {
+        if (DEBUG) Log.d(TAG, "handleHide()");
+        synchronized (mLock) {
+            if (mWm != null && mFillView != null && mShowing) {
+                mWm.removeView(mFillView);
+                mShowing = false;
+            }
+        }
+    }
+
+    /**
      * Destroys the window.
      *
      * <p>Once destroyed, this window cannot be used anymore
      */
     public void destroy() {
-        if (DEBUG) Log.d(TAG, "destroy(): mDestroyed=" + mDestroyed + " mDialog=" + mDialog);
-
-        synchronized (this) {
-            if (mDestroyed || mDialog == null) return;
-
-            mDialog.dismiss();
-            mDialog = null;
-            if (mProxy != null) {
-                mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
-            }
+        if (DEBUG) {
+            Log.d(TAG,
+                    "destroy(): mDestroyed=" + mDestroyed + " mShowing=" + mShowing + " mFillView="
+                            + mFillView);
+        }
+        synchronized (mLock) {
+            if (mDestroyed) return;
+            hide();
+            mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
+            mDestroyed = true;
             mCloseGuard.close();
         }
     }
@@ -250,11 +290,15 @@
     public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
         synchronized (this) {
             pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
-            if (mDialog != null) {
-                pw.print(prefix); pw.print("dialog: ");
-                pw.println(mDialog.isShowing() ? "shown" : "hidden");
-                pw.print(prefix); pw.print("window: ");
-                pw.println(mDialog.getWindow().getAttributes());
+            if (mFillView != null) {
+                pw.print(prefix); pw.print("fill window: ");
+                pw.println(mShowing ? "shown" : "hidden");
+                pw.print(prefix); pw.print("fill view: ");
+                pw.println(mFillView);
+                pw.print(prefix); pw.print("mBounds: ");
+                pw.println(mBounds);
+                pw.print(prefix); pw.print("mWm: ");
+                pw.println(mWm);
             }
         }
     }
@@ -264,4 +308,19 @@
     public void close() throws Exception {
         destroy();
     }
+
+    private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
+        @Override
+        public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
+                boolean fitsSystemWindows, int layoutDirection) {
+            if (DEBUG) Log.d(TAG, "FillWindowPresenter.show()");
+            mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p));
+        }
+
+        @Override
+        public void hide(Rect transitionEpicenter) {
+            if (DEBUG) Log.d(TAG, "FillWindowPresenter.hide()");
+            mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this));
+        }
+    }
 }
diff --git a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
index b3ac2da..fb6912a 100644
--- a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
+++ b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl
@@ -36,5 +36,5 @@
                        in ComponentName activityComponent, in AutofillId focusedId,
                        in AutofillValue focusedValue, long requestTime, in IFillCallback callback);
 
-    void onDestroyFillWindowRequest(int sessionId);
+    void onDestroyAllFillWindowsRequest();
 }
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index e5e028d..302e1a6 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -48,6 +48,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -77,6 +78,7 @@
             "android.service.contentcapture.ContentCaptureService";
 
     private Handler mHandler;
+    private IContentCaptureServiceCallback mCallback;
 
     /**
      * Binder that receives calls from the system server.
@@ -84,9 +86,15 @@
     private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {
 
         @Override
-        public void onConnectedStateChanged(boolean state) {
-            mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnConnectedStateChanged,
-                    ContentCaptureService.this, state));
+        public void onConnected(IBinder callback) {
+            mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnConnected,
+                    ContentCaptureService.this, callback));
+        }
+
+        @Override
+        public void onDisconnected() {
+            mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDisconnected,
+                    ContentCaptureService.this));
         }
 
         @Override
@@ -166,7 +174,16 @@
      */
     public final void setContentCaptureWhitelist(@Nullable List<String> packages,
             @Nullable List<ComponentName> activities) {
-        //TODO(b/122595322): implement
+        final IContentCaptureServiceCallback callback = mCallback;
+        if (callback == null) {
+            Log.w(TAG, "setContentCaptureWhitelist(): no server callback");
+            return;
+        }
+        try {
+            callback.setContentCaptureWhitelist(packages, activities);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -177,7 +194,16 @@
      */
     public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
             boolean enabled) {
-        //TODO(b/122595322): implement
+        final IContentCaptureServiceCallback callback = mCallback;
+        if (callback == null) {
+            Log.w(TAG, "setActivityContentCaptureEnabled(): no server callback");
+            return;
+        }
+        try {
+            callback.setActivityContentCaptureEnabled(activity, enabled);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -188,7 +214,16 @@
      */
     public final void setPackageContentCaptureEnabled(@NonNull String packageName,
             boolean enabled) {
-        //TODO(b/122595322): implement
+        final IContentCaptureServiceCallback callback = mCallback;
+        if (callback == null) {
+            Log.w(TAG, "setPackageContentCaptureEnabled(): no server callback");
+            return;
+        }
+        try {
+            callback.setPackageContentCaptureEnabled(packageName, enabled);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -197,7 +232,12 @@
      */
     @NonNull
     public final Set<ComponentName> getContentCaptureDisabledActivities() {
-        //TODO(b/122595322): implement
+        final IContentCaptureServiceCallback callback = mCallback;
+        if (callback == null) {
+            Log.w(TAG, "getContentCaptureDisabledActivities(): no server callback");
+            return Collections.emptySet();
+        }
+        //TODO(b/122595322): implement (using SyncResultReceiver)
         return null;
     }
 
@@ -207,7 +247,12 @@
      */
     @NonNull
     public final Set<String> getContentCaptureDisabledPackages() {
-        //TODO(b/122595322): implement
+        final IContentCaptureServiceCallback callback = mCallback;
+        if (callback == null) {
+            Log.w(TAG, "getContentCaptureDisabledPackages(): no server callback");
+            return Collections.emptySet();
+        }
+        //TODO(b/122595322): implement (using SyncResultReceiver)
         return null;
     }
 
@@ -307,12 +352,14 @@
         }
     }
 
-    private void handleOnConnectedStateChanged(boolean state) {
-        if (state) {
-            onConnected();
-        } else {
-            onDisconnected();
-        }
+    private void handleOnConnected(@NonNull IBinder callback) {
+        mCallback = IContentCaptureServiceCallback.Stub.asInterface(callback);
+        onConnected();
+    }
+
+    private void handleOnDisconnected() {
+        onDisconnected();
+        mCallback = null;
     }
 
     //TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session,
@@ -323,15 +370,21 @@
         mSessionUids.put(sessionId, uid);
         onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
 
-        final int flags = context.getFlags();
-        if ((flags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
-            setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_BY_FLAG_SECURE,
-                    mClientInterface.asBinder());
-            return;
+        final int clientFlags = context.getFlags();
+        int stateFlags = 0;
+        if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) {
+            stateFlags |= ContentCaptureSession.STATE_FLAG_SECURE;
         }
+        if ((clientFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0) {
+            stateFlags |= ContentCaptureSession.STATE_BY_APP;
+        }
+        if (stateFlags == 0) {
+            stateFlags = ContentCaptureSession.STATE_ACTIVE;
+        } else {
+            stateFlags |= ContentCaptureSession.STATE_DISABLED;
 
-        setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE,
-                mClientInterface.asBinder());
+        }
+        setClientState(clientReceiver, stateFlags, mClientInterface.asBinder());
     }
 
     private void handleSendEvents(int uid,
diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl
index 1b4cccf..a8dd213 100644
--- a/core/java/android/service/contentcapture/IContentCaptureService.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl
@@ -16,6 +16,7 @@
 
 package android.service.contentcapture;
 
+import android.os.IBinder;
 import android.service.contentcapture.SnapshotData;
 import android.view.contentcapture.ContentCaptureContext;
 
@@ -29,7 +30,8 @@
  * @hide
  */
 oneway interface IContentCaptureService {
-    void onConnectedStateChanged(boolean state);
+    void onConnected(IBinder callback);
+    void onDisconnected();
     void onSessionStarted(in ContentCaptureContext context, String sessionId, int uid,
                           in IResultReceiver clientReceiver);
     void onSessionFinished(String sessionId);
diff --git a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
new file mode 100644
index 0000000..e84bd6f
--- /dev/null
+++ b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package android.service.contentcapture;
+
+import android.content.ComponentName;
+import com.android.internal.os.IResultReceiver;
+
+import java.util.List;
+
+/**
+ * Interface from the Content Capture service to the system.
+ *
+ * @hide
+ */
+oneway interface IContentCaptureServiceCallback {
+    void setContentCaptureWhitelist(in List<String> packages, in List<ComponentName> activities);
+    void setActivityContentCaptureEnabled(in ComponentName activity, boolean enabled);
+    void setPackageContentCaptureEnabled(in String packageName, boolean enabled);
+    void getContentCaptureDisabledActivities(in IResultReceiver receiver);
+    void getContentCaptureDisabledPackages(in IResultReceiver receiver);
+}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 85b6b88..44353b1 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -33,8 +33,7 @@
 import android.icu.util.ULocale;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.SystemProperties;
-import android.provider.Settings;
+import android.sysprop.DisplayProperties;
 import android.text.style.AbsoluteSizeSpan;
 import android.text.style.AccessibilityClickableSpan;
 import android.text.style.AccessibilityURLSpan;
@@ -2059,7 +2058,7 @@
         return ((locale != null && !locale.equals(Locale.ROOT)
                         && ULocale.forLocale(locale).isRightToLeft())
                 // If forcing into RTL layout mode, return RTL as default
-                || SystemProperties.getBoolean(Settings.Global.DEVELOPMENT_FORCE_RTL, false))
+                || DisplayProperties.debug_force_rtl().orElse(false))
             ? View.LAYOUT_DIRECTION_RTL
             : View.LAYOUT_DIRECTION_LTR;
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index abefd55..32974ac 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -76,8 +76,8 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.Trace;
+import android.sysprop.DisplayProperties;
 import android.text.InputType;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -812,14 +812,6 @@
     private static final String CONTENT_CAPTURE_LOG_TAG = "View.ContentCapture";
 
     /**
-     * When set to true, apps will draw debugging information about their layouts.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static final String DEBUG_LAYOUT_PROPERTY = "debug.layout";
-
-    /**
      * When set to true, this view will save its attribute data.
      *
      * @hide
@@ -27833,7 +27825,7 @@
         /**
          * Show where the margins, bounds and layout bounds are for each view.
          */
-        boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false);
+        boolean mDebugLayout = DisplayProperties.debug_layout().orElse(false);
 
         /**
          * Point used to compute visible regions.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8e4dc67..27d4ea4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -75,6 +75,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.sysprop.DisplayProperties;
 import android.util.AndroidRuntimeException;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -7071,7 +7072,7 @@
                 }
 
                 // Layout debugging
-                boolean layout = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false);
+                boolean layout = DisplayProperties.debug_layout().orElse(false);
                 if (layout != mAttachInfo.mDebugLayout) {
                     mAttachInfo.mDebugLayout = layout;
                     if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index c630177..dfd9a2e 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -187,6 +187,7 @@
     /**
      * An animation listener to be notified when the animation starts, ends or repeats.
      */
+    @UnsupportedAppUsage
     private AnimationListener mListener;
 
     /**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 93941d0..888a4c5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2997,5 +2997,23 @@
                 afm.post(() -> afm.autofill(sessionId, ids, values));
             }
         }
+
+        @Override
+        public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+                Rect anchorBounds, IAutofillWindowPresenter presenter) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
+                        presenter));
+            }
+        }
+
+        @Override
+        public void requestHideFillUi(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestHideFillUi(id, false));
+            }
+        }
     }
 }
diff --git a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
index 67cd0bf..140507c 100644
--- a/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
 
 /**
  * Object running in the application process and responsible to provide the functionalities
@@ -29,6 +30,24 @@
  * @hide
  */
 interface IAugmentedAutofillManagerClient {
-   Rect getViewCoordinates(in AutofillId id);
-   void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values);
+    /**
+      * Gets the coordinates of the input field view.
+      */
+    Rect getViewCoordinates(in AutofillId id);
+
+    /**
+     * Autofills the activity with the contents of the values.
+     */
+    void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values);
+
+    /**
+      * Requests showing the fill UI.
+      */
+    void requestShowFillUi(int sessionId, in AutofillId id, int width, int height,
+            in Rect anchorBounds, in IAutofillWindowPresenter presenter);
+
+    /**
+      * Requests hiding the fill UI.
+      */
+    void requestHideFillUi(int sessionId, in AutofillId id);
 }
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index ff45efd..81b2e01 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -29,12 +29,12 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.SyncResultReceiver;
 
 import java.io.PrintWriter;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /*
  * NOTE: all methods in this class should return right away, or do the real work in a handler
@@ -62,8 +62,10 @@
     static final boolean VERBOSE = false;
     static final boolean DEBUG = true; // STOPSHIP if not set to false
 
-    @NonNull
-    private final AtomicBoolean mDisabled = new AtomicBoolean();
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mDisabled;
 
     @NonNull
     private final Context mContext;
@@ -71,11 +73,16 @@
     @Nullable
     private final IContentCaptureManager mService;
 
+    // Flags used for starting session.
+    @GuardedBy("mLock")
+    private int mFlags;
+
     // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
     // held at the Application level
     @NonNull
     private final Handler mHandler;
 
+    @GuardedBy("mLock")
     private MainContentCaptureSession mMainSession;
 
     /** @hide */
@@ -114,20 +121,25 @@
     @NonNull
     @UiThread
     public MainContentCaptureSession getMainContentCaptureSession() {
-        if (mMainSession == null) {
-            mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
-                    mDisabled);
-            if (VERBOSE) {
-                Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
+        synchronized (mLock) {
+            if (mMainSession == null) {
+                mMainSession = new MainContentCaptureSession(mContext, mHandler, mService,
+                        mDisabled);
+                if (VERBOSE) {
+                    Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
+                }
             }
+            return mMainSession;
         }
-        return mMainSession;
     }
 
     /** @hide */
     public void onActivityStarted(@NonNull IBinder applicationToken,
             @NonNull ComponentName activityComponent, int flags) {
-        getMainContentCaptureSession().start(applicationToken, activityComponent, flags);
+        synchronized (mLock) {
+            mFlags |= flags;
+            getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags);
+        }
     }
 
     /** @hide */
@@ -173,7 +185,9 @@
      * Checks whether content capture is enabled for this activity.
      */
     public boolean isContentCaptureEnabled() {
-        return mService != null && !mDisabled.get();
+        synchronized (mLock) {
+            return mService != null && !mDisabled;
+        }
     }
 
     /**
@@ -183,7 +197,9 @@
      * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
      */
     public void setContentCaptureEnabled(boolean enabled) {
-        //TODO(b/111276913): implement (need to finish / disable all sessions)
+        synchronized (mLock) {
+            mFlags |= enabled ? 0 : ContentCaptureContext.FLAG_DISABLED_BY_APP;
+        }
     }
 
     /**
@@ -198,20 +214,22 @@
 
     /** @hide */
     public void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.println("ContentCaptureManager");
-
-        pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled.get());
-        pw.print(prefix); pw.print("Context: "); pw.println(mContext);
-        pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
-        if (mService != null) {
-            pw.print(prefix); pw.print("Service: "); pw.println(mService);
-        }
-        if (mMainSession != null) {
-            final String prefix2 = prefix + "  ";
-            pw.print(prefix); pw.println("Main session:");
-            mMainSession.dump(prefix2, pw);
-        } else {
-            pw.print(prefix); pw.println("No sessions");
+        synchronized (mLock) {
+            pw.print(prefix); pw.println("ContentCaptureManager");
+            pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
+            pw.print(prefix); pw.print("Context: "); pw.println(mContext);
+            pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
+            if (mService != null) {
+                pw.print(prefix); pw.print("Service: "); pw.println(mService);
+            }
+            pw.print(prefix); pw.print("Flags: "); pw.println(mFlags);
+            if (mMainSession != null) {
+                final String prefix2 = prefix + "  ";
+                pw.print(prefix); pw.println("Main session:");
+                mMainSession.dump(prefix2, pw);
+            } else {
+                pw.print(prefix); pw.println("No sessions");
+            }
         }
     }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index d9a8416..2123308 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -21,6 +21,7 @@
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.DebugUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewStructure;
@@ -56,42 +57,58 @@
      *
      * @hide
      */
-    public static final int STATE_UNKNOWN = 0;
+    // NOTE: not prefixed by STATE_ so it's not printed on getStateAsString()
+    public static final int UNKNWON_STATE = 0x0;
 
     /**
      * Service's startSession() was called, but server didn't confirm it was created yet.
      *
      * @hide
      */
-    public static final int STATE_WAITING_FOR_SERVER = 1;
+    public static final int STATE_WAITING_FOR_SERVER = 0x1;
 
     /**
      * Session is active.
      *
      * @hide
      */
-    public static final int STATE_ACTIVE = 2;
+    public static final int STATE_ACTIVE = 0x2;
 
     /**
      * Session is disabled because there is no service for this user.
      *
      * @hide
      */
-    public static final int STATE_DISABLED_NO_SERVICE = 3;
+    public static final int STATE_DISABLED = 0x4;
 
     /**
      * Session is disabled because its id already existed on server.
      *
      * @hide
      */
-    public static final int STATE_DISABLED_DUPLICATED_ID = 4;
+    public static final int STATE_DUPLICATED_ID = 0x8;
+
+    /**
+     * Session is disabled because service is not set for user.
+     *
+     * @hide
+     */
+    public static final int STATE_NO_SERVICE = 0x10;
 
     /**
      * Session is disabled by FLAG_SECURE
      *
      * @hide
      */
-    public static final int STATE_DISABLED_BY_FLAG_SECURE = 5;
+    public static final int STATE_FLAG_SECURE = 0x20;
+
+    /**
+     * Session is disabled manually by the specific app.
+     *
+     * @hide
+     */
+    public static final int STATE_BY_APP = 0x40;
+
 
     private static final int INITIAL_CHILDREN_CAPACITY = 5;
 
@@ -110,7 +127,7 @@
     @Nullable
     protected final String mId;
 
-    private int mState = STATE_UNKNOWN;
+    private int mState = UNKNWON_STATE;
 
     // Lazily created on demand.
     private ContentCaptureSessionId mContentCaptureSessionId;
@@ -382,21 +399,7 @@
      */
     @NonNull
     protected static String getStateAsString(int state) {
-        switch (state) {
-            case STATE_UNKNOWN:
-                return "UNKNOWN";
-            case STATE_WAITING_FOR_SERVER:
-                return "WAITING_FOR_SERVER";
-            case STATE_ACTIVE:
-                return "ACTIVE";
-            case STATE_DISABLED_NO_SERVICE:
-                return "DISABLED_NO_SERVICE";
-            case STATE_DISABLED_DUPLICATED_ID:
-                return "DISABLED_DUPLICATED_ID";
-            case STATE_DISABLED_BY_FLAG_SECURE:
-                return "DISABLED_FLAG_SECURE";
-            default:
-                return "INVALID:" + state;
-        }
+        return state + " (" + (state == UNKNWON_STATE ? "UNKNOWN"
+                : DebugUtils.flagsToString(ContentCaptureSession.class, "STATE_", state)) + ")";
     }
 }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index a29aaf0..1d9018c 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -88,6 +88,7 @@
      */
     public static final String EXTRA_BINDER = "binder";
 
+    // TODO(b/111276913): make sure disabled state is in sync with manager's disabled
     @NonNull
     private final AtomicBoolean mDisabled;
 
@@ -113,7 +114,7 @@
     @Nullable
     private DeathRecipient mDirectServiceVulture;
 
-    private int mState = STATE_UNKNOWN;
+    private int mState = UNKNWON_STATE;
 
     @Nullable
     private IBinder mApplicationToken;
@@ -133,11 +134,11 @@
     /** @hide */
     protected MainContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
             @Nullable IContentCaptureManager systemServerInterface,
-            @NonNull AtomicBoolean disabled) {
+            @NonNull boolean disabled) {
         mContext = context;
         mHandler = handler;
         mSystemServerInterface = systemServerInterface;
-        mDisabled = disabled;
+        mDisabled = new AtomicBoolean(disabled);
     }
 
     @Override
@@ -184,7 +185,7 @@
 
     private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
             int flags) {
-        if (mState != STATE_UNKNOWN) {
+        if (mState != UNKNWON_STATE) {
             // TODO(b/111276913): revisit this scenario
             Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
                     + getStateAsString(mState));
@@ -247,17 +248,14 @@
             }
         }
 
-        // TODO(b/111276913): change the resultCode to use flags so there's just one flag for
-        // disabled stuff
-        if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID
-                || resultCode == STATE_DISABLED_BY_FLAG_SECURE) {
+        if ((mState & STATE_DISABLED) != 0) {
             mDisabled.set(true);
             handleResetSession(/* resetState= */ false);
         } else {
             mDisabled.set(false);
         }
         if (VERBOSE) {
-            Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId
+            Log.v(TAG, "handleSessionStarted() result: id=" + mId
                     + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
                     + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
         }
@@ -407,7 +405,7 @@
     // clearings out.
     private void handleResetSession(boolean resetState) {
         if (resetState) {
-            mState = STATE_UNKNOWN;
+            mState = UNKNWON_STATE;
         }
 
         // TODO(b/122454205): must reset children (which currently is owned by superclass)
@@ -496,8 +494,7 @@
         }
         pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
         pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
-        pw.print(prefix); pw.print("state: "); pw.print(mState); pw.print(" (");
-        pw.print(getStateAsString(mState)); pw.println(")");
+        pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
         if (mApplicationToken != null) {
             pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
         }
diff --git a/core/java/android/view/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java
index 5b957156..e2a763e 100644
--- a/core/java/android/view/inspector/InspectableProperty.java
+++ b/core/java/android/view/inspector/InspectableProperty.java
@@ -105,7 +105,6 @@
     /**
      * One entry in an enumeration packed into a primitive {int}.
      *
-     * @see IntEnumMapping
      * @hide
      */
     @Target({TYPE})
diff --git a/core/java/android/view/inspector/IntEnumMapping.java b/core/java/android/view/inspector/IntEnumMapping.java
deleted file mode 100644
index 69f6dce..0000000
--- a/core/java/android/view/inspector/IntEnumMapping.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.inspector;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.util.ArrayList;
-
-/**
- * Maps the values of an {int} property to string names for properties that encode enumerations.
- *
- * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
- * for enumerations packed into primitive {int} properties.
- *
- * This class is immutable, and must be constructed by a {@link Builder}.
- *
- * @see PropertyMapper#mapIntEnum(String, int, IntEnumMapping)
- */
-public final class IntEnumMapping {
-    private final Value[] mValues;
-
-    /**
-     * Map from a property value to a string name.
-     *
-     * @param value The value of a property
-     * @return The name of the enumeration value, null if the value is not mapped
-     */
-    @Nullable
-    public String nameOf(int value) {
-        for (Value valueTuple : mValues) {
-            if (valueTuple.mValue == value) {
-                return valueTuple.mName;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Create a new instance from a builder.
-     *
-     * This constructor is private, use {@link Builder#build()} instead.
-     *
-     * @param builder A builder to create from
-     */
-    private IntEnumMapping(Builder builder) {
-        mValues = builder.mValues.toArray(new Value[builder.mValues.size()]);
-    }
-
-    /**
-     * A builder for {@link IntEnumMapping}
-     */
-    public static final class Builder {
-        private final ArrayList<Value> mValues;
-
-        public Builder() {
-            mValues = new ArrayList<>();
-        }
-
-        /**
-         * Add a new entry to this mapping.
-         *
-         * @param name Name of the enumeration value
-         * @param value Int value of the enumeration value
-         * @return This builder
-         */
-        @NonNull
-        public Builder addValue(@NonNull String name, int value) {
-            mValues.add(new Value(name, value));
-            return this;
-        }
-
-        /**
-         * Clear the builder, allowing for recycling.
-         */
-        public void clear() {
-            mValues.clear();
-        }
-
-        /**
-         * Build a new {@link IntEnumMapping} from this builder
-         *
-         * @return A new mapping
-         */
-        @NonNull
-        public IntEnumMapping build() {
-            return new IntEnumMapping(this);
-        }
-    }
-
-    /**
-     * Inner class that holds the name and value of an enumeration value.
-     */
-    private static final class Value {
-        @NonNull private final String mName;
-        private final int mValue;
-
-        private Value(@NonNull String name, int value) {
-            mName = name;
-            mValue = value;
-        }
-    }
-}
diff --git a/core/java/android/view/inspector/IntFlagMapping.java b/core/java/android/view/inspector/IntFlagMapping.java
index dcb87e1..8f7dfd5 100644
--- a/core/java/android/view/inspector/IntFlagMapping.java
+++ b/core/java/android/view/inspector/IntFlagMapping.java
@@ -19,14 +19,20 @@
 import android.annotation.NonNull;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
- * Maps the values of an {int} property to arrays of string for properties that encode flags.
+ * Maps the values of an {@code int} property to arrays of string for properties that encode flags.
  *
  * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
- * for flag values packed into primitive {int} properties.
+ * for flag values packed into primitive {@code int} properties.
  *
- * Each flag has a
+ * Each flag has a mask and a target value, for non-exclusive flags, the target can also be used as
+ * the mask. A given integer value is compared against each flag to find what flags are active for
+ * it by bitwise anding it with the mask and comparing the result against the target, that is,
+ * {@code (value & mask) == target}.
  *
  * This class is immutable, and must be constructed by a {@link Builder}.
  *
@@ -42,8 +48,8 @@
      * @return The names of the enabled flags
      */
     @NonNull
-    public String[] namesOf(int value) {
-        ArrayList<String> enabledFlagNames = new ArrayList<>(mFlags.length);
+    public Set<String> get(int value) {
+        final Set<String> enabledFlagNames = new HashSet<>(mFlags.length);
 
         for (Flag flag : mFlags) {
             if (flag.isEnabledFor(value)) {
@@ -51,7 +57,7 @@
             }
         }
 
-        return enabledFlagNames.toArray(new String[enabledFlagNames.size()]);
+        return Collections.unmodifiableSet(enabledFlagNames);
     }
 
     /**
@@ -81,7 +87,7 @@
          * The target value will be used as a mask, to handle the common case where flag values
          * are not mutually exclusive. The flag will be considered enabled for a property value if
          * the result of bitwise anding the target and the value equals the target, that is:
-         * {(value & target) == target}.
+         * {@code (value & target) == target}.
          *
          * @param name The name of the flag
          * @param target The value to compare against
@@ -97,7 +103,7 @@
          * Add a new flag with a mask.
          *
          * The flag will be considered enabled for a property value if the result of bitwise anding
-         * the value and the mask equals the target, that is: {(value & mask) == target}.
+         * the value and the mask equals the target, that is: {@code (value & mask) == target}.
          *
          * @param name The name of the flag
          * @param target The value to compare against
@@ -111,13 +117,6 @@
         }
 
         /**
-         * Clear the builder, allowing for recycling.
-         */
-        public void clear() {
-            mFlags.clear();
-        }
-
-        /**
          * Build a new {@link IntFlagMapping} from this builder.
          *
          * @return A new mapping
@@ -143,7 +142,7 @@
         }
 
         /**
-         * Compare the supplied property value against the mask and taget.
+         * Compare the supplied property value against the mask and target.
          *
          * @param value The value to check
          * @return True if this flag is enabled
diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java
index 5fb291b..e20582b 100644
--- a/core/java/android/view/inspector/PropertyMapper.java
+++ b/core/java/android/view/inspector/PropertyMapper.java
@@ -18,6 +18,7 @@
 
 import android.annotation.AttrRes;
 import android.annotation.NonNull;
+import android.util.SparseArray;
 
 /**
  * An interface for mapping the string names of inspectable properties to integer identifiers.
@@ -154,7 +155,7 @@
     int mapIntEnum(
             @NonNull String name,
             @AttrRes int attributeId,
-            @NonNull IntEnumMapping mapping);
+            @NonNull SparseArray<String> mapping);
 
     /**
      * Map a string name to an integer ID for a flag set packed into an int property.
diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java
index fd83e8d..a8b7ecc 100644
--- a/core/java/android/view/inspector/PropertyReader.java
+++ b/core/java/android/view/inspector/PropertyReader.java
@@ -25,7 +25,13 @@
 /**
  * An interface for reading the properties of an inspectable object.
  *
- * Used as the parameter for {@link InspectionCompanion#readProperties(Object, PropertyReader)}.
+ * {@code PropertyReader} is defined as an interface that will be called by
+ * {@link InspectionCompanion#readProperties(Object, PropertyReader)}. This approach allows a
+ * client inspector to read the values of primitive properties without the overhead of
+ * instantiating a class to hold the property values for each inspection pass. If an inspectable
+ * remains unchanged between reading passes, it should be possible for a {@code PropertyReader} to
+ * avoid new allocations for subsequent reading passes.
+ *
  * It has separate methods for all primitive types to avoid autoboxing overhead if a concrete
  * implementation is able to work with primitives. Implementations should be prepared to accept
  * {null} as the value of {@link PropertyReader#readObject(int, Object)}.
@@ -38,7 +44,7 @@
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
-     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {boolean}
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code boolean}
      */
     void readBoolean(int id, boolean value);
 
@@ -47,7 +53,7 @@
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
-     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {byte}
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code byte}
      */
     void readByte(int id, byte value);
 
@@ -56,7 +62,7 @@
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
-     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {char}
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code char}
      */
     void readChar(int id, char value);
 
@@ -65,7 +71,7 @@
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
-     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {double}
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code double}
      */
     void readDouble(int id, double value);
 
@@ -74,7 +80,7 @@
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
-     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {float}
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code float}
      */
     void readFloat(int id, float value);
 
@@ -83,7 +89,7 @@
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
-     * @throws PropertyTypeMismatchException If the property ID is not mapped as an {int}
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as an {@code int}
      */
     void readInt(int id, int value);
 
@@ -92,7 +98,7 @@
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
-     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {long}
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code long}
      */
     void readLong(int id, long value);
 
@@ -101,7 +107,7 @@
      *
      * @param id Identifier of the property from a {@link PropertyMapper}
      * @param value Value of the property
-     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {short}
+     * @throws PropertyTypeMismatchException If the property ID is not mapped as a {@code short}
      */
     void readShort(int id, short value);
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index e97c9bc..bc49771 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -557,6 +557,38 @@
         return result;
     }
 
+    // FIXME: Should this be FastNative?
+    static void setColorLong(JNIEnv* env, jobject clazz, jlong paintHandle, jobject jColorSpace,
+            jfloat r, jfloat g, jfloat b, jfloat a) {
+        sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
+        if (GraphicsJNI::hasException(env)) {
+            return;
+        }
+
+        SkColor4f color = SkColor4f{r, g, b, a};
+        reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get());
+    }
+
+    static void setShadowLayerLong(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat radius,
+                                   jfloat dx, jfloat dy, jobject jColorSpace,
+                                   jfloat r, jfloat g, jfloat b, jfloat a) {
+        sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace);
+        if (GraphicsJNI::hasException(env)) {
+            return;
+        }
+
+        SkColor4f color = SkColor4f{r, g, b, a};
+
+        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        if (radius <= 0) {
+            paint->setLooper(nullptr);
+        }
+        else {
+            SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
+            paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
+        }
+    }
+
     // ------------------ @FastNative ---------------------------
 
     static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
@@ -1075,6 +1107,9 @@
     {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
     {"nGetOffsetForAdvance", "(J[CIIIIZF)I",
             (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
+    {"nSetColor","(JLandroid/graphics/ColorSpace;FFFF)V", (void*) PaintGlue::setColorLong},
+    {"nSetShadowLayer", "(JFFFLandroid/graphics/ColorSpace;FFFF)V",
+            (void*)PaintGlue::setShadowLayerLong},
 
     // --------------- @FastNative ----------------------
 
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 49a24a3..b2d3651 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -281,15 +281,18 @@
     std::unique_ptr<NativeCode> code;
     bool needs_native_bridge = false;
 
+    char* nativeloader_error_msg = nullptr;
     void* handle = OpenNativeLibrary(env,
                                      sdkVersion,
                                      pathStr.c_str(),
                                      classLoader,
                                      libraryPath,
                                      &needs_native_bridge,
-                                     &g_error_msg);
+                                     &nativeloader_error_msg);
 
     if (handle == nullptr) {
+        g_error_msg = nativeloader_error_msg;
+        NativeLoaderFreeErrorMessage(nativeloader_error_msg);
         ALOGW("NativeActivity LoadNativeLibrary(\"%s\") failed: %s",
               pathStr.c_str(),
               g_error_msg.c_str());
diff --git a/core/proto/Android.bp b/core/proto/Android.bp
new file mode 100644
index 0000000..80cc2d4
--- /dev/null
+++ b/core/proto/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// C++ library for Bluetooth platform wide protobuf definitions
+cc_library_static {
+    name: "libbt-platform-protos-lite",
+    host_supported: true,
+    proto: {
+        export_proto_headers: true,
+        type: "lite",
+    },
+    srcs: [
+        "android/bluetooth/enums.proto",
+        "android/bluetooth/hci/enums.proto",
+    ],
+}
diff --git a/core/proto/android/bluetooth/enums.proto b/core/proto/android/bluetooth/enums.proto
index d0c9226..76c240e 100644
--- a/core/proto/android/bluetooth/enums.proto
+++ b/core/proto/android/bluetooth/enums.proto
@@ -41,3 +41,18 @@
     ENABLE_DISABLE_REASON_USER_SWITCH = 8;
     ENABLE_DISABLE_REASON_RESTORE_USER_SETTING = 9;
 }
+
+enum DirectionEnum {
+    DIRECTION_UNKNOWN = 0;
+    DIRECTION_OUTGOING = 1;
+    DIRECTION_INCOMING = 2;
+}
+
+// First item is the default value, other values follow Bluetooth spec definition
+enum LinkTypeEnum {
+    // Link type is at most 1 byte (0xFF), thus 0xFFF must not be a valid value
+    LINK_TYPE_UNKNOWN = 0xFFF;
+    LINK_TYPE_SCO = 0x00;
+    LINK_TYPE_ACL = 0x01;
+    LINK_TYPE_ESCO = 0x02;
+}
diff --git a/core/proto/android/bluetooth/hci/enums.proto b/core/proto/android/bluetooth/hci/enums.proto
new file mode 100644
index 0000000..e1d96bb
--- /dev/null
+++ b/core/proto/android/bluetooth/hci/enums.proto
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.bluetooth.hci;
+
+option java_outer_classname = "BluetoothHciProtoEnums";
+option java_multiple_files = true;
+
+// HCI command opcodes (OCF+OGF) from Bluetooth 5.0 specification Vol 2, Part E, Section 7
+// Original definition: system/bt/stack/include/hcidefs.h
+enum CommandEnum {
+    // Opcode is at most 2 bytes (0xFFFF), thus 0xFFFFF must not be a valid value
+    CMD_UNKNOWN = 0xFFFFF;
+    // Link control commands 0x0400
+    CMD_INQUIRY = 0x0401;
+    CMD_INQUIRY_CANCEL = 0x0402;
+    CMD_PERIODIC_INQUIRY_MODE = 0x0403;
+    CMD_EXIT_PERIODIC_INQUIRY_MODE = 0x0404;
+    CMD_CREATE_CONNECTION = 0x0405;
+    CMD_DISCONNECT = 0x0406;
+    CMD_ADD_SCO_CONNECTION = 0x0407; // Deprecated since Bluetooth 1.2
+    CMD_CREATE_CONNECTION_CANCEL = 0x0408;
+    CMD_ACCEPT_CONNECTION_REQUEST = 0x0409;
+    CMD_REJECT_CONNECTION_REQUEST = 0x040A;
+    CMD_LINK_KEY_REQUEST_REPLY = 0x040B;
+    CMD_LINK_KEY_REQUEST_NEG_REPLY = 0x040C;
+    CMD_PIN_CODE_REQUEST_REPLY = 0x040D;
+    CMD_PIN_CODE_REQUEST_NEG_REPLY = 0x040E;
+    CMD_CHANGE_CONN_PACKET_TYPE = 0x040F;
+    CMD_AUTHENTICATION_REQUESTED = 0x0411;
+    CMD_SET_CONN_ENCRYPTION = 0x0413;
+    CMD_CHANGE_CONN_LINK_KEY = 0x0415;
+    CMD_MASTER_LINK_KEY = 0x0417;
+    CMD_RMT_NAME_REQUEST = 0x0419;
+    CMD_RMT_NAME_REQUEST_CANCEL = 0x041A;
+    CMD_READ_RMT_FEATURES = 0x041B;
+    CMD_READ_RMT_EXT_FEATURES = 0x041C;
+    CMD_READ_RMT_VERSION_INFO = 0x041D;
+    CMD_READ_RMT_CLOCK_OFFSET = 0x041F;
+    CMD_READ_LMP_HANDLE = 0x0420;
+    CMD_SETUP_ESCO_CONNECTION = 0x0428;
+    CMD_ACCEPT_ESCO_CONNECTION = 0x0429;
+    CMD_REJECT_ESCO_CONNECTION = 0x042A;
+    CMD_IO_CAPABILITY_REQUEST_REPLY = 0x042B;
+    CMD_USER_CONF_REQUEST_REPLY = 0x042C;
+    CMD_USER_CONF_VALUE_NEG_REPLY = 0x042D;
+    CMD_USER_PASSKEY_REQ_REPLY = 0x042E;
+    CMD_USER_PASSKEY_REQ_NEG_REPLY = 0x042F;
+    CMD_REM_OOB_DATA_REQ_REPLY = 0x0430;
+    CMD_REM_OOB_DATA_REQ_NEG_REPLY = 0x0433;
+    CMD_IO_CAP_REQ_NEG_REPLY = 0x0434;
+    // BEGIN: AMP commands (not used in system/bt)
+    CMD_CREATE_PHYSICAL_LINK = 0x0435;
+    CMD_ACCEPT_PHYSICAL_LINK = 0x0436;
+    CMD_DISCONNECT_PHYSICAL_LINK = 0x0437;
+    CMD_CREATE_LOGICAL_LINK = 0x0438;
+    CMD_ACCEPT_LOGICAL_LINK = 0x0439;
+    CMD_DISCONNECT_LOGICAL_LINK = 0x043A;
+    CMD_LOGICAL_LINK_CANCEL = 0x043B;
+    CMD_FLOW_SPEC_MODIFY = 0x043C;
+    // END: AMP commands
+    CMD_ENH_SETUP_ESCO_CONNECTION = 0x043D;
+    CMD_ENH_ACCEPT_ESCO_CONNECTION = 0x043E;
+    CMD_TRUNCATED_PAGE = 0x043F;
+    CMD_TRUNCATED_PAGE_CANCEL = 0x0440;
+    CMD_SET_CLB = 0x0441;
+    CMD_RECEIVE_CLB = 0x0442;
+    CMD_START_SYNC_TRAIN = 0x0443;
+    CMD_RECEIVE_SYNC_TRAIN = 0x0444;
+    CMD_REM_OOB_EXTENDED_DATA_REQ_REPLY = 0x0445; // Not currently used in system/bt
+    // Link policy commands 0x0800
+    CMD_HOLD_MODE = 0x0801;
+    CMD_SNIFF_MODE = 0x0803;
+    CMD_EXIT_SNIFF_MODE = 0x0804;
+    CMD_PARK_MODE = 0x0805;
+    CMD_EXIT_PARK_MODE = 0x0806;
+    CMD_QOS_SETUP = 0x0807;
+    CMD_ROLE_DISCOVERY = 0x0809;
+    CMD_SWITCH_ROLE = 0x080B;
+    CMD_READ_POLICY_SETTINGS = 0x080C;
+    CMD_WRITE_POLICY_SETTINGS = 0x080D;
+    CMD_READ_DEF_POLICY_SETTINGS = 0x080E;
+    CMD_WRITE_DEF_POLICY_SETTINGS = 0x080F;
+    CMD_FLOW_SPECIFICATION = 0x0810;
+    CMD_SNIFF_SUB_RATE = 0x0811;
+    // Host controller baseband commands 0x0C00
+    CMD_SET_EVENT_MASK = 0x0C01;
+    CMD_RESET = 0x0C03;
+    CMD_SET_EVENT_FILTER = 0x0C05;
+    CMD_FLUSH = 0x0C08;
+    CMD_READ_PIN_TYPE = 0x0C09;
+    CMD_WRITE_PIN_TYPE = 0x0C0A;
+    CMD_CREATE_NEW_UNIT_KEY = 0x0C0B;
+    CMD_GET_MWS_TRANS_LAYER_CFG = 0x0C0C; // Deprecated (not used in spec)
+    CMD_READ_STORED_LINK_KEY = 0x0C0D;
+    CMD_WRITE_STORED_LINK_KEY = 0x0C11;
+    CMD_DELETE_STORED_LINK_KEY = 0x0C12;
+    CMD_CHANGE_LOCAL_NAME = 0x0C13;
+    CMD_READ_LOCAL_NAME = 0x0C14;
+    CMD_READ_CONN_ACCEPT_TOUT = 0x0C15;
+    CMD_WRITE_CONN_ACCEPT_TOUT = 0x0C16;
+    CMD_READ_PAGE_TOUT = 0x0C17;
+    CMD_WRITE_PAGE_TOUT = 0x0C18;
+    CMD_READ_SCAN_ENABLE = 0x0C19;
+    CMD_WRITE_SCAN_ENABLE = 0x0C1A;
+    CMD_READ_PAGESCAN_CFG = 0x0C1B;
+    CMD_WRITE_PAGESCAN_CFG = 0x0C1C;
+    CMD_READ_INQUIRYSCAN_CFG = 0x0C1D;
+    CMD_WRITE_INQUIRYSCAN_CFG = 0x0C1E;
+    CMD_READ_AUTHENTICATION_ENABLE = 0x0C1F;
+    CMD_WRITE_AUTHENTICATION_ENABLE = 0x0C20;
+    CMD_READ_ENCRYPTION_MODE = 0x0C21; // Deprecated
+    CMD_WRITE_ENCRYPTION_MODE = 0x0C22; // Deprecated
+    CMD_READ_CLASS_OF_DEVICE = 0x0C23;
+    CMD_WRITE_CLASS_OF_DEVICE = 0x0C24;
+    CMD_READ_VOICE_SETTINGS = 0x0C25;
+    CMD_WRITE_VOICE_SETTINGS = 0x0C26;
+    CMD_READ_AUTOMATIC_FLUSH_TIMEOUT = 0x0C27;
+    CMD_WRITE_AUTOMATIC_FLUSH_TIMEOUT = 0x0C28;
+    CMD_READ_NUM_BCAST_REXMITS = 0x0C29;
+    CMD_WRITE_NUM_BCAST_REXMITS = 0x0C2A;
+    CMD_READ_HOLD_MODE_ACTIVITY = 0x0C2B;
+    CMD_WRITE_HOLD_MODE_ACTIVITY = 0x0C2C;
+    CMD_READ_TRANSMIT_POWER_LEVEL = 0x0C2D;
+    CMD_READ_SCO_FLOW_CTRL_ENABLE = 0x0C2E;
+    CMD_WRITE_SCO_FLOW_CTRL_ENABLE = 0x0C2F;
+    CMD_SET_HC_TO_HOST_FLOW_CTRL = 0x0C31;
+    CMD_HOST_BUFFER_SIZE = 0x0C33;
+    CMD_HOST_NUM_PACKETS_DONE = 0x0C35;
+    CMD_READ_LINK_SUPER_TOUT = 0x0C36;
+    CMD_WRITE_LINK_SUPER_TOUT = 0x0C37;
+    CMD_READ_NUM_SUPPORTED_IAC = 0x0C38;
+    CMD_READ_CURRENT_IAC_LAP = 0x0C39;
+    CMD_WRITE_CURRENT_IAC_LAP = 0x0C3A;
+    CMD_READ_PAGESCAN_PERIOD_MODE = 0x0C3B; // Deprecated
+    CMD_WRITE_PAGESCAN_PERIOD_MODE = 0x0C3C; // Deprecated
+    CMD_READ_PAGESCAN_MODE = 0x0C3D; // Deprecated
+    CMD_WRITE_PAGESCAN_MODE = 0x0C3E; // Deprecated
+    CMD_SET_AFH_CHANNELS = 0x0C3F;
+    CMD_READ_INQSCAN_TYPE = 0x0C42;
+    CMD_WRITE_INQSCAN_TYPE = 0x0C43;
+    CMD_READ_INQUIRY_MODE = 0x0C44;
+    CMD_WRITE_INQUIRY_MODE = 0x0C45;
+    CMD_READ_PAGESCAN_TYPE = 0x0C46;
+    CMD_WRITE_PAGESCAN_TYPE = 0x0C47;
+    CMD_READ_AFH_ASSESSMENT_MODE = 0x0C48;
+    CMD_WRITE_AFH_ASSESSMENT_MODE = 0x0C49;
+    CMD_READ_EXT_INQ_RESPONSE = 0x0C51;
+    CMD_WRITE_EXT_INQ_RESPONSE = 0x0C52;
+    CMD_REFRESH_ENCRYPTION_KEY = 0x0C53;
+    CMD_READ_SIMPLE_PAIRING_MODE = 0x0C55;
+    CMD_WRITE_SIMPLE_PAIRING_MODE = 0x0C56;
+    CMD_READ_LOCAL_OOB_DATA = 0x0C57;
+    CMD_READ_INQ_TX_POWER_LEVEL = 0x0C58;
+    CMD_WRITE_INQ_TX_POWER_LEVEL = 0x0C59;
+    CMD_READ_ERRONEOUS_DATA_RPT = 0x0C5A;
+    CMD_WRITE_ERRONEOUS_DATA_RPT = 0x0C5B;
+    CMD_ENHANCED_FLUSH = 0x0C5F;
+    CMD_SEND_KEYPRESS_NOTIF = 0x0C60;
+    CMD_READ_LOGICAL_LINK_ACCEPT_TIMEOUT = 0x0C61;
+    CMD_WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT = 0x0C62;
+    CMD_SET_EVENT_MASK_PAGE_2 = 0x0C63;
+    CMD_READ_LOCATION_DATA = 0x0C64;
+    CMD_WRITE_LOCATION_DATA = 0x0C65;
+    CMD_READ_FLOW_CONTROL_MODE = 0x0C66;
+    CMD_WRITE_FLOW_CONTROL_MODE = 0x0C67;
+    CMD_READ_ENHANCED_TX_PWR_LEVEL = 0x0C68; // Not currently used in system/bt
+    CMD_READ_BE_FLUSH_TOUT = 0x0C69;
+    CMD_WRITE_BE_FLUSH_TOUT = 0x0C6A;
+    CMD_SHORT_RANGE_MODE = 0x0C6B;
+    CMD_READ_BLE_HOST_SUPPORT = 0x0C6C;
+    CMD_WRITE_BLE_HOST_SUPPORT = 0x0C6D;
+    CMD_SET_MWS_CHANNEL_PARAMETERS = 0x0C6E;
+    CMD_SET_EXTERNAL_FRAME_CONFIGURATION = 0x0C6F;
+    CMD_SET_MWS_SIGNALING = 0x0C70;
+    CMD_SET_MWS_TRANSPORT_LAYER = 0x0C71;
+    CMD_SET_MWS_SCAN_FREQUENCY_TABLE = 0x0C72;
+    CMD_SET_MWS_PATTERN_CONFIGURATION = 0x0C73;
+    CMD_SET_RESERVED_LT_ADDR = 0x0C74;
+    CMD_DELETE_RESERVED_LT_ADDR = 0x0C75;
+    CMD_WRITE_CLB_DATA = 0x0C76;
+    CMD_READ_SYNC_TRAIN_PARAM = 0x0C77;
+    CMD_WRITE_SYNC_TRAIN_PARAM = 0x0C78;
+    CMD_READ_SECURE_CONNS_SUPPORT = 0x0C79;
+    CMD_WRITE_SECURE_CONNS_SUPPORT = 0x0C7A;
+    CMD_READ_AUTHED_PAYLOAD_TIMEOUT = 0x0C7B; // Not currently used in system/bt
+    CMD_WRITE_AUTHED_PAYLOAD_TIMEOUT = 0x0C7C; // Not currently used in system/bt
+    CMD_READ_LOCAL_OOB_EXTENDED_DATA = 0x0C7D; // Not currently used in system/bt
+    CMD_READ_EXTENDED_PAGE_TIMEOUT = 0x0C7E; // Not currently used in system/bt
+    CMD_WRITE_EXTENDED_PAGE_TIMEOUT = 0x0C7F; // Not currently used in system/bt
+    CMD_READ_EXTENDED_INQUIRY_LENGTH = 0x0C80; // Not currently used in system/bt
+    CMD_WRITE_EXTENDED_INQUIRY_LENGTH = 0x0C81; // Not currently used in system/bt
+    // Informational parameter commands 0x1000
+    CMD_READ_LOCAL_VERSION_INFO = 0x1001;
+    CMD_READ_LOCAL_SUPPORTED_CMDS = 0x1002;
+    CMD_READ_LOCAL_FEATURES = 0x1003;
+    CMD_READ_LOCAL_EXT_FEATURES = 0x1004;
+    CMD_READ_BUFFER_SIZE = 0x1005;
+    CMD_READ_COUNTRY_CODE = 0x1007; // Deprecated
+    CMD_READ_BD_ADDR = 0x1009;
+    CMD_READ_DATA_BLOCK_SIZE = 0x100A;
+    CMD_READ_LOCAL_SUPPORTED_CODECS = 0x100B;
+    // Status parameter commands 0x1400
+    CMD_READ_FAILED_CONTACT_COUNTER = 0x1401;
+    CMD_RESET_FAILED_CONTACT_COUNTER = 0x1402;
+    CMD_GET_LINK_QUALITY = 0x1403;
+    CMD_READ_RSSI = 0x1405;
+    CMD_READ_AFH_CH_MAP = 0x1406;
+    CMD_READ_CLOCK = 0x1407;
+    CMD_READ_ENCR_KEY_SIZE = 0x1408;
+    CMD_READ_LOCAL_AMP_INFO = 0x1409;
+    CMD_READ_LOCAL_AMP_ASSOC = 0x140A;
+    CMD_WRITE_REMOTE_AMP_ASSOC = 0x140B;
+    CMD_GET_MWS_TRANSPORT_CFG = 0x140C; // Not currently used in system/bt
+    CMD_SET_TRIGGERED_CLK_CAPTURE = 0x140D; // Not currently used in system/bt
+    // Testing commands 0x1800
+    CMD_READ_LOOPBACK_MODE = 0x1801;
+    CMD_WRITE_LOOPBACK_MODE = 0x1802;
+    CMD_ENABLE_DEV_UNDER_TEST_MODE = 0x1803;
+    CMD_WRITE_SIMP_PAIR_DEBUG_MODE = 0x1804;
+    CMD_ENABLE_AMP_RCVR_REPORTS = 0x1807;
+    CMD_AMP_TEST_END = 0x1808;
+    CMD_AMP_TEST = 0x1809;
+    CMD_WRITE_SECURE_CONN_TEST_MODE = 0x180A; // Not currently used in system/bt
+    // BLE commands 0x2000
+    CMD_BLE_SET_EVENT_MASK = 0x2001;
+    CMD_BLE_READ_BUFFER_SIZE = 0x2002;
+    CMD_BLE_READ_LOCAL_SPT_FEAT = 0x2003;
+    CMD_BLE_WRITE_LOCAL_SPT_FEAT = 0x2004;
+    CMD_BLE_WRITE_RANDOM_ADDR = 0x2005;
+    CMD_BLE_WRITE_ADV_PARAMS = 0x2006;
+    CMD_BLE_READ_ADV_CHNL_TX_POWER = 0x2007;
+    CMD_BLE_WRITE_ADV_DATA = 0x2008;
+    CMD_BLE_WRITE_SCAN_RSP_DATA = 0x2009;
+    CMD_BLE_WRITE_ADV_ENABLE = 0x200A;
+    CMD_BLE_WRITE_SCAN_PARAMS = 0x200B;
+    CMD_BLE_WRITE_SCAN_ENABLE = 0x200C;
+    CMD_BLE_CREATE_LL_CONN = 0x200D;
+    CMD_BLE_CREATE_CONN_CANCEL = 0x200E;
+    CMD_BLE_READ_WHITE_LIST_SIZE = 0x200F;
+    CMD_BLE_CLEAR_WHITE_LIST = 0x2010;
+    CMD_BLE_ADD_WHITE_LIST = 0x2011;
+    CMD_BLE_REMOVE_WHITE_LIST = 0x2012;
+    CMD_BLE_UPD_LL_CONN_PARAMS = 0x2013;
+    CMD_BLE_SET_HOST_CHNL_CLASS = 0x2014;
+    CMD_BLE_READ_CHNL_MAP = 0x2015;
+    CMD_BLE_READ_REMOTE_FEAT = 0x2016;
+    CMD_BLE_ENCRYPT = 0x2017;
+    CMD_BLE_RAND = 0x2018;
+    CMD_BLE_START_ENC = 0x2019;
+    CMD_BLE_LTK_REQ_REPLY = 0x201A;
+    CMD_BLE_LTK_REQ_NEG_REPLY = 0x201B;
+    CMD_BLE_READ_SUPPORTED_STATES = 0x201C;
+    CMD_BLE_RECEIVER_TEST = 0x201D;
+    CMD_BLE_TRANSMITTER_TEST = 0x201E;
+    CMD_BLE_TEST_END = 0x201F;
+    CMD_BLE_RC_PARAM_REQ_REPLY = 0x2020;
+    CMD_BLE_RC_PARAM_REQ_NEG_REPLY = 0x2021;
+    CMD_BLE_SET_DATA_LENGTH = 0x2022;
+    CMD_BLE_READ_DEFAULT_DATA_LENGTH = 0x2023;
+    CMD_BLE_WRITE_DEFAULT_DATA_LENGTH = 0x2024;
+    CMD_BLE_GENERATE_DHKEY = 0x2026; // Not currently used in system/bt
+    CMD_BLE_ADD_DEV_RESOLVING_LIST = 0x2027;
+    CMD_BLE_RM_DEV_RESOLVING_LIST = 0x2028;
+    CMD_BLE_CLEAR_RESOLVING_LIST = 0x2029;
+    CMD_BLE_READ_RESOLVING_LIST_SIZE = 0x202A;
+    CMD_BLE_READ_RESOLVABLE_ADDR_PEER = 0x202B;
+    CMD_BLE_READ_RESOLVABLE_ADDR_LOCAL = 0x202C;
+    CMD_BLE_SET_ADDR_RESOLUTION_ENABLE = 0x202D;
+    CMD_BLE_SET_RAND_PRIV_ADDR_TIMOUT = 0x202E;
+    CMD_BLE_READ_MAXIMUM_DATA_LENGTH = 0x202F;
+    CMD_BLE_READ_PHY = 0x2030;
+    CMD_BLE_SET_DEFAULT_PHY = 0x2031;
+    CMD_BLE_SET_PHY = 0x2032;
+    CMD_BLE_ENH_RECEIVER_TEST = 0x2033;
+    CMD_BLE_ENH_TRANSMITTER_TEST = 0x2034;
+    CMD_BLE_SET_EXT_ADVERTISING_RANDOM_ADDRESS = 0x2035;
+    CMD_BLE_SET_EXT_ADVERTISING_PARAM = 0x2036;
+    CMD_BLE_SET_EXT_ADVERTISING_DATA = 0x2037;
+    CMD_BLE_SET_EXT_ADVERTISING_SCAN_RESP = 0x2038;
+    CMD_BLE_SET_EXT_ADVERTISING_ENABLE = 0x2039;
+    CMD_BLE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH = 0x203A;
+    CMD_BLE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS = 0x203B;
+    CMD_BLE_REMOVE_ADVERTISING_SET = 0x203C;
+    CMD_BLE_CLEAR_ADVERTISING_SETS = 0x203D;
+    CMD_BLE_SET_PERIODIC_ADVERTISING_PARAM = 0x203E;
+    CMD_BLE_SET_PERIODIC_ADVERTISING_DATA = 0x203F;
+    CMD_BLE_SET_PERIODIC_ADVERTISING_ENABLE = 0x2040;
+    CMD_BLE_SET_EXTENDED_SCAN_PARAMETERS = 0x2041;
+    CMD_BLE_SET_EXTENDED_SCAN_ENABLE = 0x2042;
+    CMD_BLE_EXTENDED_CREATE_CONNECTION = 0x2043;
+    CMD_BLE_PERIODIC_ADVERTISING_CREATE_SYNC = 0x2044;
+    CMD_BLE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL = 0x2045;
+    CMD_BLE_PERIODIC_ADVERTISING_TERMINATE_SYNC = 0x2046;
+    CMD_BLE_ADD_DEVICE_TO_PERIODIC_ADVERTISING_LIST = 0x2047;
+    CMD_BLE_RM_DEVICE_FROM_PERIODIC_ADVERTISING_LIST = 0x2048;
+    CMD_BLE_CLEAR_PERIODIC_ADVERTISING_LIST = 0x2049;
+    CMD_BLE_READ_PERIODIC_ADVERTISING_LIST_SIZE = 0x204A;
+    CMD_BLE_READ_TRANSMIT_POWER = 0x204B;
+    CMD_BLE_READ_RF_COMPENS_POWER = 0x204C;
+    CMD_BLE_WRITE_RF_COMPENS_POWER = 0x204D;
+    CMD_BLE_SET_PRIVACY_MODE = 0x204E;
+    // Vendor specific commands 0xFC00 and above
+    // Android vendor specific commands defined in
+    // https://source.android.com/devices/bluetooth/hci_requirements#vendor-specific-capabilities
+    CMD_BLE_VENDOR_CAP = 0xFD53;
+    CMD_BLE_MULTI_ADV = 0xFD54;
+    CMD_BLE_BATCH_SCAN = 0xFD56;
+    CMD_BLE_ADV_FILTER = 0xFD57;
+    CMD_BLE_TRACK_ADV = 0xFD58;
+    CMD_BLE_ENERGY_INFO = 0xFD59;
+    CMD_BLE_EXTENDED_SCAN_PARAMS = 0xFD5A;
+    CMD_CONTROLLER_DEBUG_INFO = 0xFD5B;
+    CMD_CONTROLLER_A2DP_OPCODE = 0xFD5D;
+    CMD_BRCM_SET_ACL_PRIORITY = 0xFC57;
+    // Other vendor specific commands below here
+}
+
+// HCI event codes from the Bluetooth 5.0 specification Vol 2, Part 7, Section 7
+// Original definition: system/bt/stack/include/hcidefs.h
+enum EventEnum {
+    // Event is at most 1 byte (0xFF), thus 0xFFF must not be a valid value
+    EVT_UNKNOWN = 0xFFF;
+    EVT_INQUIRY_COMP = 0x01;
+    EVT_INQUIRY_RESULT = 0x02;
+    EVT_CONNECTION_COMP = 0x03;
+    EVT_CONNECTION_REQUEST = 0x04;
+    EVT_DISCONNECTION_COMP = 0x05;
+    EVT_AUTHENTICATION_COMP = 0x06;
+    EVT_RMT_NAME_REQUEST_COMP = 0x07;
+    EVT_ENCRYPTION_CHANGE = 0x08;
+    EVT_CHANGE_CONN_LINK_KEY = 0x09;
+    EVT_MASTER_LINK_KEY_COMP = 0x0A;
+    EVT_READ_RMT_FEATURES_COMP = 0x0B;
+    EVT_READ_RMT_VERSION_COMP = 0x0C;
+    EVT_QOS_SETUP_COMP = 0x0D;
+    EVT_COMMAND_COMPLETE = 0x0E;
+    EVT_COMMAND_STATUS = 0x0F;
+    EVT_HARDWARE_ERROR = 0x10;
+    EVT_FLUSH_OCCURED = 0x11;
+    EVT_ROLE_CHANGE = 0x12;
+    EVT_NUM_COMPL_DATA_PKTS = 0x13;
+    EVT_MODE_CHANGE = 0x14;
+    EVT_RETURN_LINK_KEYS = 0x15;
+    EVT_PIN_CODE_REQUEST = 0x16;
+    EVT_LINK_KEY_REQUEST = 0x17;
+    EVT_LINK_KEY_NOTIFICATION = 0x18;
+    EVT_LOOPBACK_COMMAND = 0x19;
+    EVT_DATA_BUF_OVERFLOW = 0x1A;
+    EVT_MAX_SLOTS_CHANGED = 0x1B;
+    EVT_READ_CLOCK_OFF_COMP = 0x1C;
+    EVT_CONN_PKT_TYPE_CHANGE = 0x1D;
+    EVT_QOS_VIOLATION = 0x1E;
+    EVT_PAGE_SCAN_MODE_CHANGE = 0x1F; // Deprecated
+    EVT_PAGE_SCAN_REP_MODE_CHNG = 0x20;
+    EVT_FLOW_SPECIFICATION_COMP = 0x21;
+    EVT_INQUIRY_RSSI_RESULT = 0x22;
+    EVT_READ_RMT_EXT_FEATURES_COMP = 0x23;
+    EVT_ESCO_CONNECTION_COMP = 0x2C;
+    EVT_ESCO_CONNECTION_CHANGED = 0x2D;
+    EVT_SNIFF_SUB_RATE = 0x2E;
+    EVT_EXTENDED_INQUIRY_RESULT = 0x2F;
+    EVT_ENCRYPTION_KEY_REFRESH_COMP = 0x30;
+    EVT_IO_CAPABILITY_REQUEST = 0x31;
+    EVT_IO_CAPABILITY_RESPONSE = 0x32;
+    EVT_USER_CONFIRMATION_REQUEST = 0x33;
+    EVT_USER_PASSKEY_REQUEST = 0x34;
+    EVT_REMOTE_OOB_DATA_REQUEST = 0x35;
+    EVT_SIMPLE_PAIRING_COMPLETE = 0x36;
+    EVT_LINK_SUPER_TOUT_CHANGED = 0x38;
+    EVT_ENHANCED_FLUSH_COMPLETE = 0x39;
+    EVT_USER_PASSKEY_NOTIFY = 0x3B;
+    EVT_KEYPRESS_NOTIFY = 0x3C;
+    EVT_RMT_HOST_SUP_FEAT_NOTIFY = 0x3D;
+    EVT_BLE_META = 0x3E;
+    EVT_PHYSICAL_LINK_COMP = 0x40;
+    EVT_CHANNEL_SELECTED = 0x41;
+    EVT_DISC_PHYSICAL_LINK_COMP = 0x42;
+    EVT_PHY_LINK_LOSS_EARLY_WARNING = 0x43;
+    EVT_PHY_LINK_RECOVERY = 0x44;
+    EVT_LOGICAL_LINK_COMP = 0x45;
+    EVT_DISC_LOGICAL_LINK_COMP = 0x46;
+    EVT_FLOW_SPEC_MODIFY_COMP = 0x47;
+    EVT_NUM_COMPL_DATA_BLOCKS = 0x48;
+    EVT_AMP_TEST_START = 0x49; // Not currently used in system/bt
+    EVT_AMP_TEST_END = 0x4A; // Not currently used in system/bt
+    EVT_AMP_RECEIVER_RPT = 0x4B; // Not currently used in system/bt
+    EVT_SHORT_RANGE_MODE_COMPLETE = 0x4C;
+    EVT_AMP_STATUS_CHANGE = 0x4D;
+    EVT_SET_TRIGGERED_CLOCK_CAPTURE = 0x4E;
+    EVT_SYNC_TRAIN_CMPL = 0x4F; // Not currently used in system/bt
+    EVT_SYNC_TRAIN_RCVD = 0x50; // Not currently used in system/bt
+    EVT_CONNLESS_SLAVE_BROADCAST_RCVD = 0x51; // Not currently used in system/bt
+    EVT_CONNLESS_SLAVE_BROADCAST_TIMEOUT = 0x52; // Not currently used in system/bt
+    EVT_TRUNCATED_PAGE_CMPL = 0x53; // Not currently used in system/bt
+    EVT_SLAVE_PAGE_RES_TIMEOUT = 0x54; // Not currently used in system/bt
+    EVT_CONNLESS_SLAVE_BROADCAST_CHNL_MAP_CHANGE = 0x55; // Not currently used in system/bt
+    EVT_INQUIRY_RES_NOTIFICATION = 0x56; // Not currently used in system/bt
+    EVT_AUTHED_PAYLOAD_TIMEOUT = 0x57; // Not currently used in system/bt
+    EVT_SAM_STATUS_CHANGE = 0x58; // Not currently used in system/bt
+}
+
+// Bluetooth low energy related meta event codes
+// from the Bluetooth 5.0 specification Vol 2, Part E, Section 7.7.65
+// Original definition: system/bt/stack/include/hcidefs.h
+enum BleMetaEventEnum {
+    // BLE meta event code is at most 1 byte (0xFF), thus 0xFFF must not be a valid value
+    BLE_EVT_UNKNOWN = 0xFFF;
+    BLE_EVT_CONN_COMPLETE_EVT = 0x01;
+    BLE_EVT_ADV_PKT_RPT_EVT = 0x02;
+    BLE_EVT_LL_CONN_PARAM_UPD_EVT = 0x03;
+    BLE_EVT_READ_REMOTE_FEAT_CMPL_EVT = 0x04;
+    BLE_EVT_LTK_REQ_EVT = 0x05;
+    BLE_EVT_RC_PARAM_REQ_EVT = 0x06;
+    BLE_EVT_DATA_LENGTH_CHANGE_EVT = 0x07;
+    BLE_EVT_READ_LOCAL_P256_PUB_KEY = 0x08; // Not currently used in system/bt
+    BLE_EVT_GEN_DHKEY_CMPL = 0x09; // Not currently used in system/bt
+    BLE_EVT_ENHANCED_CONN_COMPLETE_EVT = 0x0a;
+    BLE_EVT_DIRECT_ADV_EVT = 0x0b;
+    BLE_EVT_PHY_UPDATE_COMPLETE_EVT = 0x0c;
+    BLE_EVT_EXTENDED_ADVERTISING_REPORT_EVT = 0x0D;
+    BLE_EVT_PERIODIC_ADV_SYNC_EST_EVT = 0x0E;
+    BLE_EVT_PERIODIC_ADV_REPORT_EVT = 0x0F;
+    BLE_EVT_PERIODIC_ADV_SYNC_LOST_EVT = 0x10;
+    BLE_EVT_SCAN_TIMEOUT_EVT = 0x11;
+    BLE_EVT_ADVERTISING_SET_TERMINATED_EVT = 0x12;
+    BLE_EVT_SCAN_REQ_RX_EVT = 0x13;
+    BLE_EVT_CHNL_SELECTION_ALGORITHM = 0x14; // Not currently used in system/bt
+}
+
+// HCI status code from the Bluetooth 5.0 specification Vol 2, Part D.
+// Original definition: system/bt/stack/include/hcidefs.h
+enum StatusEnum {
+    // Status is at most 1 byte (0xFF), thus 0xFFF must not be a valid value
+    STATUS_UNKNOWN = 0xFFF;
+    STATUS_SUCCESS = 0x00;
+    STATUS_ILLEGAL_COMMAND = 0x01;
+    STATUS_NO_CONNECTION = 0x02;
+    STATUS_HW_FAILURE = 0x03;
+    STATUS_PAGE_TIMEOUT = 0x04;
+    STATUS_AUTH_FAILURE = 0x05;
+    STATUS_KEY_MISSING = 0x06;
+    STATUS_MEMORY_FULL = 0x07;
+    STATUS_CONNECTION_TOUT = 0x08;
+    STATUS_MAX_NUM_OF_CONNECTIONS = 0x09;
+    STATUS_MAX_NUM_OF_SCOS = 0x0A;
+    STATUS_CONNECTION_EXISTS = 0x0B;
+    STATUS_COMMAND_DISALLOWED = 0x0C;
+    STATUS_HOST_REJECT_RESOURCES = 0x0D;
+    STATUS_HOST_REJECT_SECURITY = 0x0E;
+    STATUS_HOST_REJECT_DEVICE = 0x0F;
+    STATUS_HOST_TIMEOUT = 0x10;
+    STATUS_UNSUPPORTED_VALUE = 0x11;
+    STATUS_ILLEGAL_PARAMETER_FMT = 0x12;
+    STATUS_PEER_USER = 0x13;
+    STATUS_PEER_LOW_RESOURCES = 0x14;
+    STATUS_PEER_POWER_OFF = 0x15;
+    STATUS_CONN_CAUSE_LOCAL_HOST = 0x16;
+    STATUS_REPEATED_ATTEMPTS = 0x17;
+    STATUS_PAIRING_NOT_ALLOWED = 0x18;
+    STATUS_UNKNOWN_LMP_PDU = 0x19;
+    STATUS_UNSUPPORTED_REM_FEATURE = 0x1A;
+    STATUS_SCO_OFFSET_REJECTED = 0x1B;
+    STATUS_SCO_INTERVAL_REJECTED = 0x1C;
+    STATUS_SCO_AIR_MODE = 0x1D;
+    STATUS_INVALID_LMP_PARAM = 0x1E;
+    STATUS_UNSPECIFIED = 0x1F;
+    STATUS_UNSUPPORTED_LMP_FEATURE = 0x20;
+    STATUS_ROLE_CHANGE_NOT_ALLOWED = 0x21;
+    STATUS_LMP_RESPONSE_TIMEOUT = 0x22;
+    STATUS_LMP_STATUS_TRANS_COLLISION = 0x23;
+    STATUS_LMP_PDU_NOT_ALLOWED = 0x24;
+    STATUS_ENCRY_MODE_NOT_ACCEPTABLE = 0x25;
+    STATUS_UNIT_KEY_USED = 0x26;
+    STATUS_QOS_NOT_SUPPORTED = 0x27;
+    STATUS_INSTANT_PASSED = 0x28;
+    STATUS_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED = 0x29;
+    STATUS_DIFF_TRANSACTION_COLLISION = 0x2A;
+    STATUS_UNDEFINED_0x2B = 0x2B; // Not used
+    STATUS_QOS_UNACCEPTABLE_PARAM = 0x2C;
+    STATUS_QOS_REJECTED = 0x2D;
+    STATUS_CHAN_CLASSIF_NOT_SUPPORTED = 0x2E;
+    STATUS_INSUFFCIENT_SECURITY = 0x2F;
+    STATUS_PARAM_OUT_OF_RANGE = 0x30;
+    STATUS_UNDEFINED_0x31 = 0x31; // Not used
+    STATUS_ROLE_SWITCH_PENDING = 0x32;
+    STATUS_UNDEFINED_0x33 = 0x33;
+    STATUS_RESERVED_SLOT_VIOLATION = 0x34;
+    STATUS_ROLE_SWITCH_FAILED = 0x35;
+    STATUS_INQ_RSP_DATA_TOO_LARGE = 0x36;
+    STATUS_SIMPLE_PAIRING_NOT_SUPPORTED = 0x37;
+    STATUS_HOST_BUSY_PAIRING = 0x38;
+    STATUS_REJ_NO_SUITABLE_CHANNEL = 0x39;
+    STATUS_CONTROLLER_BUSY = 0x3A;
+    STATUS_UNACCEPT_CONN_INTERVAL = 0x3B;
+    STATUS_ADVERTISING_TIMEOUT = 0x3C;
+    STATUS_CONN_TOUT_DUE_TO_MIC_FAILURE = 0x3D;
+    STATUS_CONN_FAILED_ESTABLISHMENT = 0x3E;
+    STATUS_MAC_CONNECTION_FAILED = 0x3F;
+    STATUS_LT_ADDR_ALREADY_IN_USE = 0x40;
+    STATUS_LT_ADDR_NOT_ALLOCATED = 0x41;
+    STATUS_CLB_NOT_ENABLED = 0x42;
+    STATUS_CLB_DATA_TOO_BIG = 0x43;
+    STATUS_OPERATION_CANCELED_BY_HOST = 0x44; // Not currently used in system/bt
+}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 0ec8c1a..7f3ea7a 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -403,18 +403,23 @@
         optional bool is_charging = 1;
         optional bool is_in_parole = 2;
 
+        // List of UIDs currently in the foreground.
+        repeated int32 foreground_uids = 3;
+
         message TrackedJob {
             option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
             optional JobStatusDumpProto.Bucket effective_standby_bucket = 3;
-            optional bool has_quota = 4;
+            // If the job started while the app was in the TOP state.
+            optional bool is_top_started_job = 4;
+            optional bool has_quota = 5;
             // The amount of time that this job has remaining in its quota. This
             // can be negative if the job is out of quota.
-            optional int64 remaining_quota_ms = 5;
+            optional int64 remaining_quota_ms = 6;
         }
-        repeated TrackedJob tracked_jobs = 3;
+        repeated TrackedJob tracked_jobs = 4;
 
         message Package {
             option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -456,7 +461,7 @@
 
             repeated TimingSession saved_sessions = 3;
         }
-        repeated PackageStats package_stats = 4;
+        repeated PackageStats package_stats = 5;
     }
     message StorageController {
         option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f3a491..7813128 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3196,9 +3196,10 @@
 
     <!-- @SystemApi Required to add or remove another application as a device admin.
          <p>Not for use by third-party applications.
-         @hide -->
+         @hide
+         @removed -->
     <permission android:name="android.permission.MANAGE_DEVICE_ADMINS"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an app to reset the device password.
          <p>Not for use by third-party applications.
@@ -3520,19 +3521,25 @@
     <permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to provide remote displays.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.REMOTE_DISPLAY_PROVIDER"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to capture video output.
          <p>Not for use by third-party applications.</p>
           @hide
           @removed -->
     <permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature" />
 
     <!-- Allows an application to capture secure video output.
          <p>Not for use by third-party applications.</p>
           @hide
           @removed -->
     <permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature" />
 
     <!-- Allows an application to know what content is playing and control its playback.
          <p>Not for use by third-party applications due to privacy of media consumption</p>  -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2e3bd7c..75f8d6f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1689,6 +1689,8 @@
          config_enableFusedLocationOverlay is false. -->
     <string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
 
+    <string-array name="config_locationExtraPackageNames" translatable="false"></string-array>
+
     <!-- The package name of the default network recommendation app.
          A network recommendation provider must:
              * Be granted the SCORE_NETWORKS permission.
@@ -3696,7 +3698,7 @@
     <string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string>
 
     <!-- Component name for the default module metadata provider on this device -->
-    <string name="config_defaultModuleMetadataProvider">com.android.modulemetadata</string>
+    <string name="config_defaultModuleMetadataProvider" translatable="false">com.android.modulemetadata</string>
 
     <!-- This is the default launcher component to use on secondary displays that support system
          decorations.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daf8b44..e5c9b84 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1840,6 +1840,7 @@
   <java-symbol type="array" name="radioAttributes" />
   <java-symbol type="array" name="config_oemUsbModeOverride" />
   <java-symbol type="array" name="config_locationProviderPackageNames" />
+  <java-symbol type="array" name="config_locationExtraPackageNames" />
   <java-symbol type="array" name="config_testLocationProviders" />
   <java-symbol type="array" name="config_defaultNotificationVibePattern" />
   <java-symbol type="array" name="config_notificationFallbackVibePattern" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 1ed5ce4..df4600e 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -27,7 +27,6 @@
 import static java.lang.reflect.Modifier.isStatic;
 
 import android.platform.test.annotations.Presubmit;
-import android.provider.Settings.Global;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -270,6 +269,7 @@
                     Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
                     Settings.Global.GNSS_SATELLITE_BLACKLIST,
                     Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
+                    Settings.Global.HDMI_CEC_SWITCH_ENABLED,
                     Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                     Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
                     Settings.Global.HDMI_CONTROL_ENABLED,
@@ -554,8 +554,10 @@
                     Settings.Global.APPOP_HISTORY_PARAMETERS,
                     Settings.Global.APPOP_HISTORY_MODE,
                     Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER,
-                    Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS);
-
+                    Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
+                    Settings.Global.ENABLE_RADIO_BUG_DETECTION,
+                    Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
+                    Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD);
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
                  Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 0dd9d09..28a8afe 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -318,6 +318,27 @@
             mMaxVolume = maxVolume;
             mIsMute = isMute;
         }
+
+        @Override
+        public void setSystemAudioModeOnForAudioOnlySource() {
+        }
+
+        @Override
+        public int getPhysicalAddress() {
+            return 0x0000;
+        }
+
+        @Override
+        public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
+        }
+
+        @Override
+        public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
+        }
+
+        @Override
+        public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
+        }
     }
 
 }
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 6ce8148..035ee10 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -39,9 +39,6 @@
     name: "privapp-permissions-platform.xml",
     sub_dir: "permissions",
     src: "privapp-permissions-platform.xml",
-    required: [
-        "privapp_whitelist_com.android.settings.intelligence",
-    ],
 }
 
 prebuilt_etc {
@@ -86,6 +83,7 @@
 
 prebuilt_etc {
     name: "privapp_whitelist_com.android.settings.intelligence",
+    product_specific: true,
     sub_dir: "permissions",
     src: "com.android.settings.intelligence.xml",
     filename_from_src: true,
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 4a2db0a..fb43e41 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -223,12 +223,12 @@
          code to link against. -->
 
     <library name="android.test.base"
-            file="/system/framework/android.test.base.impl.jar" />
+            file="/system/framework/android.test.base.jar" />
     <library name="android.test.mock"
-            file="/system/framework/android.test.mock.impl.jar"
+            file="/system/framework/android.test.mock.jar"
             dependency="android.test.base" />
     <library name="android.test.runner"
-            file="/system/framework/android.test.runner.impl.jar"
+            file="/system/framework/android.test.runner.jar"
             dependency="android.test.base:android.test.mock" />
 
     <!-- In BOOT_JARS historically, and now added to legacy applications. -->
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3342fd2..cbb780d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,12 +17,14 @@
 package android.graphics;
 
 import android.annotation.ColorInt;
+import android.annotation.ColorLong;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Px;
 import android.annotation.Size;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.graphics.fonts.FontVariationAxis;
 import android.os.Build;
@@ -972,6 +974,31 @@
     }
 
     /**
+     * Set the paint's color with a {@link ColorLong}. Note that the color is
+     * a long with an encoded {@link ColorSpace} as well as alpha and r,g,b.
+     * These values are not premultiplied, meaning that alpha can be any value,
+     * regardless of the values of r,g,b. See the {@link Color} class for more
+     * details.
+     *
+     * @param color The new color (including alpha and {@link ColorSpace})
+     *      to set in the paint.
+     * @throws IllegalArgumentException if the color space encoded in the long
+     *      is invalid or unknown.
+     *
+     * @hide pending API approval
+     */
+    @TestApi
+    public void setColor(@ColorLong long color) {
+        ColorSpace cs = Color.colorSpace(color);
+        float r = Color.red(color);
+        float g = Color.green(color);
+        float b = Color.blue(color);
+        float a = Color.alpha(color);
+
+        nSetColor(mNativePaint, cs, r, g, b, a);
+    }
+
+    /**
      * Helper to getColor() that just returns the color's alpha value. This is
      * the same as calling getColor() >>> 24. It always returns a value between
      * 0 (completely transparent) and 255 (completely opaque).
@@ -1370,12 +1397,45 @@
      * The alpha of the shadow will be the paint's alpha if the shadow color is
      * opaque, or the alpha from the shadow color if not.
      */
-    public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
-      mShadowLayerRadius = radius;
-      mShadowLayerDx = dx;
-      mShadowLayerDy = dy;
-      mShadowLayerColor = shadowColor;
-      nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
+    public void setShadowLayer(float radius, float dx, float dy, @ColorInt int shadowColor) {
+        mShadowLayerRadius = radius;
+        mShadowLayerDx = dx;
+        mShadowLayerDy = dy;
+        mShadowLayerColor = shadowColor;
+        // FIXME: Share a single native method with the ColorLong version.
+        nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
+    }
+
+    /**
+     * This draws a shadow layer below the main layer, with the specified
+     * offset and color, and blur radius. If radius is 0, then the shadow
+     * layer is removed.
+     * <p>
+     * Can be used to create a blurred shadow underneath text. Support for use
+     * with other drawing operations is constrained to the software rendering
+     * pipeline.
+     * <p>
+     * The alpha of the shadow will be the paint's alpha if the shadow color is
+     * opaque, or the alpha from the shadow color if not.
+     *
+     * @throws IllegalArgumentException if the color space encoded in the long
+     *      is invalid or unknown.
+     *
+     * @hide pending API approval
+     */
+    @TestApi
+    public void setShadowLayer(float radius, float dx, float dy, @ColorLong long shadowColor) {
+        ColorSpace cs = Color.colorSpace(shadowColor);
+        float r = Color.red(shadowColor);
+        float g = Color.green(shadowColor);
+        float b = Color.blue(shadowColor);
+        float a = Color.alpha(shadowColor);
+        nSetShadowLayer(mNativePaint, radius, dx, dy, cs, r, g, b, a);
+
+        mShadowLayerRadius = radius;
+        mShadowLayerDx = dx;
+        mShadowLayerDy = dy;
+        mShadowLayerColor = Color.toArgb(shadowColor);
     }
 
     /**
@@ -2906,6 +2966,11 @@
             int contextStart, int contextEnd, boolean isRtl, int offset);
     private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
             int contextStart, int contextEnd, boolean isRtl, float advance);
+    private static native void nSetColor(long paintPtr, ColorSpace cs,
+            float r, float g, float b, float a);
+    private static native void nSetShadowLayer(long paintPtr,
+            float radius, float dx, float dy, ColorSpace cs,
+            float r, float g, float b, float a);
 
 
     // ---------------- @FastNative ------------------------
@@ -2961,7 +3026,7 @@
             int mMinikinLocaleListId);
     @CriticalNative
     private static native void nSetShadowLayer(long paintPtr,
-            float radius, float dx, float dy, int color);
+            float radius, float dx, float dy, @ColorInt int color);
     @CriticalNative
     private static native boolean nHasShadowLayer(long paintPtr);
     @CriticalNative
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 9170d6d..68541b4 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -86,6 +86,26 @@
     mCallbacks.gles.draw(mFunctor, mData, drawInfo);
 }
 
+void WebViewFunctor::initVk(const VkFunctorInitParams& params) {
+    ATRACE_NAME("WebViewFunctor::initVk");
+    if (!mHasContext) {
+        mHasContext = true;
+    } else {
+        return;
+    }
+    mCallbacks.vk.initialize(mFunctor, mData, params);
+}
+
+void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) {
+    ATRACE_NAME("WebViewFunctor::drawVk");
+    mCallbacks.vk.draw(mFunctor, mData, params);
+}
+
+void WebViewFunctor::postDrawVk() {
+    ATRACE_NAME("WebViewFunctor::postDrawVk");
+    mCallbacks.vk.postDraw(mFunctor, mData);
+}
+
 void WebViewFunctor::destroyContext() {
     if (mHasContext) {
         mHasContext = false;
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index 1719ce7..2846cb1 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -42,6 +42,12 @@
 
         void drawGl(const DrawGlInfo& drawInfo) const { mReference.drawGl(drawInfo); }
 
+        void initVk(const VkFunctorInitParams& params) { mReference.initVk(params); }
+
+        void drawVk(const VkFunctorDrawParams& params) { mReference.drawVk(params); }
+
+        void postDrawVk() { mReference.postDrawVk(); }
+
     private:
         friend class WebViewFunctor;
 
@@ -53,6 +59,9 @@
     int id() const { return mFunctor; }
     void sync(const WebViewSyncData& syncData) const;
     void drawGl(const DrawGlInfo& drawInfo);
+    void initVk(const VkFunctorInitParams& params);
+    void drawVk(const VkFunctorDrawParams& params);
+    void postDrawVk();
     void destroyContext();
 
     sp<Handle> createHandle() {
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 4338b1c..e6e6b0e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -96,7 +96,7 @@
 
     SkASSERT(mRenderThread.getGrContext() != nullptr);
     sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(
-            mRenderThread.getGrContext(), backendRT, kBottomLeft_GrSurfaceOrigin, colorType,
+            mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType,
             mSurfaceColorSpace, &props));
 
     SkiaPipeline::updateLighting(lightGeometry, lightInfo);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 47991069..6692922 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -39,6 +39,7 @@
               const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
               const std::vector<sp<RenderNode> >& renderNodes,
               FrameInfoVisualizer* profiler) override;
+    GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 2e7850d..d7faaf7 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -174,7 +174,8 @@
         SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
         SkASSERT(mRenderThread.getGrContext() != nullptr);
         node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                          SkBudgeted::kYes, info, 0, &props));
+                                                          SkBudgeted::kYes, info, 0,
+                                                          this->getSurfaceOrigin(), &props));
         if (node->getLayerSurface()) {
             // update the transform in window of the layer to reset its origin wrt light source
             // position
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index ff87313..94a699b 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -48,7 +48,7 @@
     bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
                              ErrorHandler* errorHandler) override;
 
-    SkColorType getSurfaceColorType() const { return mSurfaceColorType; }
+    SkColorType getSurfaceColorType() const override { return mSurfaceColorType; }
     sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; }
 
     void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 6eefed9..d54275f 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -125,8 +125,6 @@
                                              uirenderer::GlFunctorLifecycleListener* listener) {
     FunctorDrawable* functorDrawable;
     if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
-        // TODO(cblume) use VkFunctorDrawable instead of VkInteropFunctorDrawable here when the
-        // interop is disabled/moved.
         functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>(
                 functor, listener, asSkCanvas());
     } else {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 53ffc44..9343076 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -35,6 +35,7 @@
               const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
               const std::vector<sp<RenderNode> >& renderNodes,
               FrameInfoVisualizer* profiler) override;
+    GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index 156f74a..2f8d381 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -17,6 +17,8 @@
 #include "VkFunctorDrawable.h"
 #include <private/hwui/DrawVkInfo.h>
 
+#include "renderthread/VulkanManager.h"
+#include "renderthread/RenderThread.h"
 #include <GrBackendDrawableInfo.h>
 #include <SkImage.h>
 #include <utils/Color.h>
@@ -31,34 +33,58 @@
 namespace uirenderer {
 namespace skiapipeline {
 
-VkFunctorDrawHandler::VkFunctorDrawHandler(Functor* functor) : INHERITED(), mFunctor(functor) {}
+VkFunctorDrawHandler::VkFunctorDrawHandler(sp<WebViewFunctor::Handle> functor_handle,
+                                           const SkMatrix& matrix, const SkIRect& clip,
+                                           const SkImageInfo& image_info)
+        : INHERITED()
+        , mFunctorHandle(functor_handle)
+        , mMatrix(matrix)
+        , mClip(clip)
+        , mImageInfo(image_info) {}
 
 VkFunctorDrawHandler::~VkFunctorDrawHandler() {
-    // TODO(cblume) Fill in the DrawVkInfo parameters.
-    (*mFunctor)(DrawVkInfo::kModePostComposite, nullptr);
+    mFunctorHandle->postDrawVk();
 }
 
 void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) {
     ATRACE_CALL();
+    if (!renderthread::RenderThread::isCurrent())
+        LOG_ALWAYS_FATAL("VkFunctorDrawHandler::draw not called on render thread");
 
     GrVkDrawableInfo vulkan_info;
     if (!info.getVkDrawableInfo(&vulkan_info)) {
         return;
     }
+    renderthread::VulkanManager& vk_manager =
+            renderthread::RenderThread::getInstance().vulkanManager();
+    mFunctorHandle->initVk(vk_manager.getVkFunctorInitParams());
 
-    DrawVkInfo draw_vk_info;
-    // TODO(cblume) Fill in the rest of the parameters and test the actual call.
-    draw_vk_info.isLayer = true;
+    SkMatrix44 mat4(mMatrix);
+    VkFunctorDrawParams params{
+      .width = mImageInfo.width(),
+      .height = mImageInfo.height(),
+      .is_layer = false,  // TODO(boliu): Populate is_layer.
+      .color_space_ptr = mImageInfo.colorSpace(),
+      .clip_left = mClip.fLeft,
+      .clip_top = mClip.fTop,
+      .clip_right = mClip.fRight,
+      .clip_bottom = mClip.fBottom,
+    };
+    mat4.asColMajorf(&params.transform[0]);
+    params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
+    params.color_attachment_index = vulkan_info.fColorAttachmentIndex;
+    params.compatible_render_pass = vulkan_info.fCompatibleRenderPass;
+    params.format = vulkan_info.fFormat;
 
-    (*mFunctor)(DrawVkInfo::kModeComposite, &draw_vk_info);
+    mFunctorHandle->drawVk(params);
+
+    vulkan_info.fDrawBounds->offset.x = mClip.fLeft;
+    vulkan_info.fDrawBounds->offset.y = mClip.fTop;
+    vulkan_info.fDrawBounds->extent.width = mClip.fRight - mClip.fLeft;
+    vulkan_info.fDrawBounds->extent.height = mClip.fBottom - mClip.fTop;
 }
 
 VkFunctorDrawable::~VkFunctorDrawable() {
-    if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) {
-        if (lp->listener) {
-            lp->listener->onGlFunctorReleased(lp->functor);
-        }
-    }
 }
 
 void VkFunctorDrawable::onDraw(SkCanvas* /*canvas*/) {
@@ -67,16 +93,17 @@
 }
 
 std::unique_ptr<FunctorDrawable::GpuDrawHandler> VkFunctorDrawable::onSnapGpuDrawHandler(
-        GrBackendApi backendApi, const SkMatrix& matrix) {
+        GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clip,
+        const SkImageInfo& image_info) {
     if (backendApi != GrBackendApi::kVulkan) {
         return nullptr;
     }
     std::unique_ptr<VkFunctorDrawHandler> draw;
     if (mAnyFunctor.index() == 0) {
-        LOG_ALWAYS_FATAL("Not implemented");
-        return nullptr;
+        return std::make_unique<VkFunctorDrawHandler>(std::get<0>(mAnyFunctor).handle, matrix, clip,
+                                                      image_info);
     } else {
-        return std::make_unique<VkFunctorDrawHandler>(std::get<1>(mAnyFunctor).functor);
+        LOG_ALWAYS_FATAL("Not implemented");
     }
 }
 
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.h b/libs/hwui/pipeline/skia/VkFunctorDrawable.h
index d6fefc1..1a53c8f 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.h
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.h
@@ -32,15 +32,18 @@
  */
 class VkFunctorDrawHandler : public FunctorDrawable::GpuDrawHandler {
 public:
-    explicit VkFunctorDrawHandler(Functor* functor);
+    VkFunctorDrawHandler(sp<WebViewFunctor::Handle> functor_handle, const SkMatrix& matrix,
+                         const SkIRect& clip, const SkImageInfo& image_info);
     ~VkFunctorDrawHandler() override;
 
     void draw(const GrBackendDrawableInfo& info) override;
 
 private:
     typedef GpuDrawHandler INHERITED;
-
-    Functor* mFunctor;
+    sp<WebViewFunctor::Handle> mFunctorHandle;
+    const SkMatrix mMatrix;
+    const SkIRect mClip;
+    const SkImageInfo mImageInfo;
 };
 
 /**
@@ -57,7 +60,8 @@
     // SkDrawable functions:
     void onDraw(SkCanvas* canvas) override;
     std::unique_ptr<FunctorDrawable::GpuDrawHandler> onSnapGpuDrawHandler(
-            GrBackendApi backendApi, const SkMatrix& matrix) override;
+            GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clip,
+            const SkImageInfo& image_info) override;
 };
 
 }  // namespace skiapipeline
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index fd824bd..abc4dbf 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -17,80 +17,61 @@
 #ifndef ANDROID_HWUI_DRAW_VK_INFO_H
 #define ANDROID_HWUI_DRAW_VK_INFO_H
 
+#include <SkColorSpace.h>
 #include <vulkan/vulkan.h>
 
 namespace android {
 namespace uirenderer {
 
-/**
- * Structure used by VulkanRenderer::callDrawVKFunction() to pass and receive data from Vulkan
- * functors.
- */
-struct DrawVkInfo {
-    // Input: current width/height of destination surface
-    int width;
-    int height;
+struct VkFunctorInitParams {
+  VkInstance instance;
+  VkPhysicalDevice physical_device;
+  VkDevice device;
+  VkQueue queue;
+  uint32_t graphics_queue_index;
+  uint32_t instance_version;
+  const char* const* enabled_instance_extension_names;
+  uint32_t enabled_instance_extension_names_length;
+  const char* const* enabled_device_extension_names;
+  uint32_t enabled_device_extension_names_length;
+  const VkPhysicalDeviceFeatures2* device_features_2;
+};
 
-    // Input: is the render target an FBO
-    bool isLayer;
+struct VkFunctorDrawParams {
+  // Input: current width/height of destination surface.
+  int width;
+  int height;
 
-    // Input: current transform matrix, in OpenGL format
-    float transform[16];
+  // Input: is the render target a FBO
+  bool is_layer;
 
-    // Input: WebView should do its main compositing draws into this. It cannot do anything that
-    // would require stopping the render pass.
-    VkCommandBuffer secondaryCommandBuffer;
+  // Input: current transform matrix
+  float transform[16];
 
-    // Input: The main color attachment index where secondaryCommandBuffer will eventually be
-    // submitted.
-    uint32_t colorAttachmentIndex;
+  // Input WebView should do its main compositing draws into this. It cannot do
+  // anything that would require stopping the render pass.
+  VkCommandBuffer secondary_command_buffer;
 
-    // Input: A render pass which will be compatible to the one which the secondaryCommandBuffer
-    // will be submitted into.
-    VkRenderPass compatibleRenderPass;
+  // Input: The main color attachment index where secondary_command_buffer will
+  // eventually be submitted.
+  uint32_t color_attachment_index;
 
-    // Input: Format of the destination surface.
-    VkFormat format;
+  // Input: A render pass which will be compatible to the one which the
+  // secondary_command_buffer will be submitted into.
+  VkRenderPass compatible_render_pass;
 
-    // Input: Color space
-    const SkColorSpace* colorSpaceInfo;
+  // Input: Format of the destination surface.
+  VkFormat format;
 
-    // Input: current clip rect
-    int clipLeft;
-    int clipTop;
-    int clipRight;
-    int clipBottom;
+  // Input: Color space.
+  const SkColorSpace* color_space_ptr;
 
-    /**
-     * Values used as the "what" parameter of the functor.
-     */
-    enum Mode {
-        // Called once at WebView start
-        kModeInit,
-        // Called when things need to be re-created
-        kModeReInit,
-        // Notifies the app that the composite functor will be called soon. This allows WebView to
-        // begin work early.
-        kModePreComposite,
-        // Do the actual composite work
-        kModeComposite,
-        // This allows WebView to begin using the previously submitted objects in future work.
-        kModePostComposite,
-        // Invoked every time the UI thread pushes over a frame to the render thread and the owning
-        // view has a dirty display list*. This is a signal to sync any data that needs to be
-        // shared between the UI thread and the render thread. During this time the UI thread is
-        // blocked.
-        kModeSync
-    };
-
-    /**
-     * Values used by Vulkan functors to tell the framework what to do next.
-     */
-    enum Status {
-        // The functor is done
-        kStatusDone = 0x0,
-    };
-};  // struct DrawVkInfo
+  // Input: current clip rect
+  int clip_left;
+  int clip_top;
+  int clip_right;
+  int clip_bottom;
+};
 
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h
index da3d06a..96da947 100644
--- a/libs/hwui/private/hwui/WebViewFunctor.h
+++ b/libs/hwui/private/hwui/WebViewFunctor.h
@@ -19,6 +19,7 @@
 
 #include <cutils/compiler.h>
 #include <private/hwui/DrawGlInfo.h>
+#include <private/hwui/DrawVkInfo.h>
 
 namespace android::uirenderer {
 
@@ -52,18 +53,12 @@
             // Called on RenderThread. initialize is guaranteed to happen before this call
             void (*draw)(int functor, void* data, const DrawGlInfo& params);
         } gles;
-        // TODO: VK support. The current DrawVkInfo is monolithic and needs to be split up for
-        // what params are valid on what callbacks
         struct {
             // Called either the first time the functor is used or the first time it's used after
             // a call to onContextDestroyed.
-            // void (*initialize)(int functor, const InitParams& params);
-            // void (*frameStart)(int functor, /* todo: what params are actually needed for this to
-            // be useful? Is this useful? */)
-            // void (*draw)(int functor, const CompositeParams& params /* todo: rename - composite
-            // almost always means something else, and we aren't compositing */);
-            // void (*frameEnd)(int functor, const PostCompositeParams& params /* todo: same as
-            // CompositeParams - rename */);
+            void (*initialize)(int functor, void* data, const VkFunctorInitParams& params);
+            void (*draw)(int functor, void* data, const VkFunctorDrawParams& params);
+            void (*postDraw)(int functor, void*);
         } vk;
     };
 };
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 42e17b273..d4dd629 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -84,6 +84,7 @@
     virtual void onPrepareTree() = 0;
     virtual SkColorType getSurfaceColorType() const = 0;
     virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
+    virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
 
     virtual ~IRenderPipeline() {}
 };
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 5272227..1ef83fb 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -47,6 +47,10 @@
 class RenderState;
 class TestUtils;
 
+namespace skiapipeline {
+class VkFunctorDrawHandler;
+}
+
 namespace renderthread {
 
 class CanvasContext;
@@ -124,6 +128,7 @@
     friend class DummyVsyncSource;
     friend class android::uirenderer::TestUtils;
     friend class android::uirenderer::WebViewFunctor;
+    friend class android::uirenderer::skiapipeline::VkFunctorDrawHandler;
 
     RenderThread();
     virtual ~RenderThread();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index aa7a141..6c540f6 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -34,6 +34,23 @@
 namespace uirenderer {
 namespace renderthread {
 
+static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
+    // All Vulkan structs that could be part of the features chain will start with the
+    // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
+    // so we can get access to the pNext for the next struct.
+    struct CommonVulkanHeader {
+        VkStructureType sType;
+        void*           pNext;
+    };
+
+    void* pNext = features.pNext;
+    while (pNext) {
+        void* current = pNext;
+        pNext = static_cast<CommonVulkanHeader*>(current)->pNext;
+        free(current);
+    }
+}
+
 #define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F)
 #define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
 #define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
@@ -66,6 +83,11 @@
     mDevice = VK_NULL_HANDLE;
     mPhysicalDevice = VK_NULL_HANDLE;
     mInstance = VK_NULL_HANDLE;
+    mInstanceVersion = 0u;
+    mInstanceExtensions.clear();
+    mDeviceExtensions.clear();
+    free_features_extensions_structs(mPhysicalDeviceFeatures2);
+    mPhysicalDeviceFeatures2 = {};
 }
 
 bool VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFeatures2& features) {
@@ -81,7 +103,6 @@
         VK_MAKE_VERSION(1, 1, 0),           // apiVersion
     };
 
-    std::vector<const char*> instanceExtensions;
     {
         GET_PROC(EnumerateInstanceExtensionProperties);
 
@@ -99,7 +120,7 @@
         bool hasKHRSurfaceExtension = false;
         bool hasKHRAndroidSurfaceExtension = false;
         for (uint32_t i = 0; i < extensionCount; ++i) {
-            instanceExtensions.push_back(extensions[i].extensionName);
+            mInstanceExtensions.push_back(extensions[i].extensionName);
             if (!strcmp(extensions[i].extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) {
                 hasKHRSurfaceExtension = true;
             }
@@ -120,8 +141,8 @@
         &app_info,                                 // pApplicationInfo
         0,                                         // enabledLayerNameCount
         nullptr,                                   // ppEnabledLayerNames
-        (uint32_t) instanceExtensions.size(),      // enabledExtensionNameCount
-        instanceExtensions.data(),                 // ppEnabledExtensionNames
+        (uint32_t) mInstanceExtensions.size(),     // enabledExtensionNameCount
+        mInstanceExtensions.data(),                // ppEnabledExtensionNames
     };
 
     GET_PROC(CreateInstance);
@@ -201,7 +222,6 @@
     // presentation with any native window. So just use the first one.
     mPresentQueueIndex = 0;
 
-    std::vector<const char*> deviceExtensions;
     {
         uint32_t extensionCount = 0;
         err = mEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &extensionCount,
@@ -220,7 +240,7 @@
         }
         bool hasKHRSwapchainExtension = false;
         for (uint32_t i = 0; i < extensionCount; ++i) {
-            deviceExtensions.push_back(extensions[i].extensionName);
+            mDeviceExtensions.push_back(extensions[i].extensionName);
             if (!strcmp(extensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
                 hasKHRSwapchainExtension = true;
             }
@@ -237,8 +257,8 @@
         }
         return vkGetInstanceProcAddr(instance, proc_name);
     };
-    grExtensions.init(getProc, mInstance, mPhysicalDevice, instanceExtensions.size(),
-            instanceExtensions.data(), deviceExtensions.size(), deviceExtensions.data());
+    grExtensions.init(getProc, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
+            mInstanceExtensions.data(), mDeviceExtensions.size(), mDeviceExtensions.data());
 
     if (!grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
         this->destroy();
@@ -308,8 +328,8 @@
         queueInfo,                               // pQueueCreateInfos
         0,                                       // layerCount
         nullptr,                                 // ppEnabledLayerNames
-        (uint32_t) deviceExtensions.size(),      // extensionCount
-        deviceExtensions.data(),                 // ppEnabledExtensionNames
+        (uint32_t) mDeviceExtensions.size(),     // extensionCount
+        mDeviceExtensions.data(),                // ppEnabledExtensionNames
         nullptr,                                 // ppEnabledFeatures
     };
 
@@ -351,36 +371,17 @@
     return true;
 }
 
-static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
-    // All Vulkan structs that could be part of the features chain will start with the
-    // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
-    // so we can get access to the pNext for the next struct.
-    struct CommonVulkanHeader {
-        VkStructureType sType;
-        void*           pNext;
-    };
-
-    void* pNext = features.pNext;
-    while (pNext) {
-        void* current = pNext;
-        pNext = static_cast<CommonVulkanHeader*>(current)->pNext;
-        free(current);
-    }
-}
-
 void VulkanManager::initialize() {
     if (mDevice != VK_NULL_HANDLE) {
         return;
     }
 
     GET_PROC(EnumerateInstanceVersion);
-    uint32_t instanceVersion = 0;
-    LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
-    LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
+    LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&mInstanceVersion));
+    LOG_ALWAYS_FATAL_IF(mInstanceVersion < VK_MAKE_VERSION(1, 1, 0));
 
     GrVkExtensions extensions;
-    VkPhysicalDeviceFeatures2 features;
-    LOG_ALWAYS_FATAL_IF(!this->setupDevice(extensions, features));
+    LOG_ALWAYS_FATAL_IF(!this->setupDevice(extensions, mPhysicalDeviceFeatures2));
 
     mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
 
@@ -397,9 +398,9 @@
     backendContext.fDevice = mDevice;
     backendContext.fQueue = mGraphicsQueue;
     backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex;
-    backendContext.fInstanceVersion = instanceVersion;
+    backendContext.fInstanceVersion = mInstanceVersion;
     backendContext.fVkExtensions = &extensions;
-    backendContext.fDeviceFeatures2 = &features;
+    backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
     backendContext.fGetProc = std::move(getProc);
 
     // create the command pool for the command buffers
@@ -433,13 +434,29 @@
     LOG_ALWAYS_FATAL_IF(!grContext.get());
     mRenderThread.setGrContext(grContext);
 
-    free_features_extensions_structs(features);
-
     if (Properties::enablePartialUpdates && Properties::useBufferAge) {
         mSwapBehavior = SwapBehavior::BufferAge;
     }
 }
 
+VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
+    return VkFunctorInitParams{
+            .instance = mInstance,
+            .physical_device = mPhysicalDevice,
+            .device = mDevice,
+            .queue = mGraphicsQueue,
+            .graphics_queue_index = mGraphicsQueueIndex,
+            .instance_version = mInstanceVersion,
+            .enabled_instance_extension_names = mInstanceExtensions.data(),
+            .enabled_instance_extension_names_length =
+                    static_cast<uint32_t>(mInstanceExtensions.size()),
+            .enabled_device_extension_names = mDeviceExtensions.data(),
+            .enabled_device_extension_names_length =
+                    static_cast<uint32_t>(mDeviceExtensions.size()),
+            .device_features_2 = &mPhysicalDeviceFeatures2,
+    };
+}
+
 // Returns the next BackbufferInfo to use for the next draw. The function will make sure all
 // previous uses have finished before returning.
 VulkanSurface::BackbufferInfo* VulkanManager::getAvailableBackbuffer(VulkanSurface* surface) {
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 9eb942c..105ee09 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -132,6 +132,9 @@
     // Creates a fence that is signaled, when all the pending Vulkan commands are flushed.
     status_t createReleaseFence(sp<Fence>& nativeFence);
 
+    // Returned pointers are owned by VulkanManager.
+    VkFunctorInitParams getVkFunctorInitParams() const;
+
 private:
     friend class RenderThread;
 
@@ -234,6 +237,12 @@
 
     VkCommandBuffer mDummyCB = VK_NULL_HANDLE;
 
+    // Variables saved to populate VkFunctorInitParams.
+    uint32_t mInstanceVersion = 0u;
+    std::vector<const char*> mInstanceExtensions;
+    std::vector<const char*> mDeviceExtensions;
+    VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures2{};
+
     enum class SwapBehavior {
         Discard,
         BufferAge,
diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h
index 75b26c6..0747243 100644
--- a/libs/services/include/android/os/DropBoxManager.h
+++ b/libs/services/include/android/os/DropBoxManager.h
@@ -62,7 +62,7 @@
     // file descriptor.
     Status addFile(const String16& tag, int fd, int flags);
 
-    class Entry : public virtual RefBase, public Parcelable {
+    class Entry : public Parcelable {
     public:
         Entry();
         virtual ~Entry();
@@ -89,9 +89,6 @@
         friend class DropBoxManager;
     };
 
-    // Get the next entry from the drop box after the specified time.
-    Status getNextEntry(const String16& tag, long msec, Entry* entry);
-
 private:
     enum {
         HAS_BYTE_ARRAY = 8
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index 8282518..681d5f7 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -228,15 +228,4 @@
     return service->add(entry);
 }
 
-Status
-DropBoxManager::getNextEntry(const String16& tag, long msec, Entry* entry)
-{
-    sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(
-        defaultServiceManager()->getService(android::String16("dropbox")));
-    if (service == NULL) {
-        return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
-    }
-    return service->getNextEntry(tag, msec, android::String16("android"), entry);
-}
-
 }} // namespace android::os
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 793aa27..5516086 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -816,7 +817,7 @@
      *
      * @return The audio frame size in bytes corresponding to the encoding and the channel mask.
      */
-    public int getFrameSizeInBytes() {
+    public @IntRange(from = 1) int getFrameSizeInBytes() {
         return mFrameSizeInBytes;
     }
 
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index a836f4a..aa79c41 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -776,7 +776,7 @@
                 }
                 boolean hasError = false;
                 for (DataSourceDesc dsd : dsds) {
-                    if (dsd != null) {
+                    if (dsd == null) {
                         hasError = true;
                         continue;
                     }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 452c6e1..0198470 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -84,7 +84,7 @@
 }
 
 cc_library_shared {
-    name: "libmedia2_jni",
+    name: "libmediaplayer2_jni",
 
     srcs: [
         "android_media_DataSourceCallback.cpp",
@@ -116,7 +116,10 @@
         "libz",
     ],
 
-    header_libs: ["libhardware_headers"],
+    header_libs: [
+        "libhardware_headers",
+        "libnativewindow_headers",
+    ],
 
     static_libs: [
         "libbase",
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
new file mode 100644
index 0000000..1d7d6de
--- /dev/null
+++ b/media/jni/Android.mk
@@ -0,0 +1,30 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := -Wall -Werror
+LOCAL_SRC_FILES := \
+    Media2Jni.cpp \
+
+# TODO: Move libmedia2_jni from system to media apex. Currently, libraries defined in
+#       Android.mk is not visible in apex build.
+LOCAL_MODULE:= libmedia2_jni
+LOCAL_SHARED_LIBRARIES := libdl liblog
+
+sanitizer_runtime_libraries := $(call normalize-path-list,$(addsuffix .so,\
+  $(ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
+  $(UBSAN_RUNTIME_LIBRARY) \
+  $(TSAN_RUNTIME_LIBRARY)))
+
+# $(info Sanitizer:  $(sanitizer_runtime_libraries))
+
+ndk_libraries := $(call normalize-path-list,$(addprefix lib,$(addsuffix .so,\
+  $(NDK_PREBUILT_SHARED_LIBRARIES))))
+
+# $(info NDK:  $(ndk_libraries))
+
+LOCAL_CFLAGS += -DLINKED_LIBRARIES='"$(sanitizer_runtime_libraries):$(ndk_libraries)"'
+
+sanitizer_runtime_libraries :=
+ndk_libraries :=
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/Media2Jni.cpp b/media/jni/Media2Jni.cpp
new file mode 100644
index 0000000..6c0a65c
--- /dev/null
+++ b/media/jni/Media2Jni.cpp
@@ -0,0 +1,89 @@
+/*
+**
+** Copyright 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.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaPlayer2-JNI"
+#include "utils/Log.h"
+
+#include "jni.h"
+#include <android/dlext.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <string.h>
+
+extern "C" {
+  // Copied from GraphicsEnv.cpp
+  // TODO(b/37049319) Get this from a header once one exists
+  android_namespace_t* android_create_namespace(const char* name,
+                                                const char* ld_library_path,
+                                                const char* default_library_path,
+                                                uint64_t type,
+                                                const char* permitted_when_isolated_path,
+                                                android_namespace_t* parent);
+  bool android_link_namespaces(android_namespace_t* from,
+                               android_namespace_t* to,
+                               const char* shared_libs_sonames);
+  enum {
+     ANDROID_NAMESPACE_TYPE_ISOLATED = 1,
+  };
+
+}  // extern "C"
+
+static const char kApexLibPath[] =  "/apex/com.android.media/lib"
+#ifdef __LP64__
+    "64"
+#endif
+    "/";
+static const char kMediaPlayer2LibPath[] =  "/apex/com.android.media/lib"
+#ifdef __LP64__
+    "64"
+#endif
+    "/libmediaplayer2_jni.so";
+
+typedef jint (*Media2JniOnLoad)(JavaVM*, void*);
+
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+    android_namespace_t *media2Ns = android_create_namespace("media2",
+            nullptr,  // ld_library_path
+            kApexLibPath,
+            ANDROID_NAMESPACE_TYPE_ISOLATED,
+            nullptr,  // permitted_when_isolated_path
+            nullptr); // parent
+    if (!android_link_namespaces(media2Ns, nullptr, LINKED_LIBRARIES)) {
+        ALOGE("Failed to link namespace. Failed to load extractor plug-ins in apex.");
+        return -1;
+    }
+    const android_dlextinfo dlextinfo = {
+        .flags = ANDROID_DLEXT_USE_NAMESPACE,
+        .library_namespace = media2Ns,
+    };
+    // load libmediaplayer2_jni and call JNI_OnLoad.
+    void *libHandle = android_dlopen_ext(kMediaPlayer2LibPath, RTLD_NOW | RTLD_LOCAL, &dlextinfo);
+    if (libHandle == NULL) {
+        ALOGW("couldn't dlopen(%s) %s", kMediaPlayer2LibPath, strerror(errno));
+        return -1;
+    }
+    Media2JniOnLoad media2JniOnLoad = (Media2JniOnLoad) dlsym(libHandle, "JNI_OnLoad");
+    if (!media2JniOnLoad) {
+        ALOGW("%s does not contain JNI_OnLoad()", kMediaPlayer2LibPath);
+        dlclose(libHandle);
+        return -1;
+    }
+    return media2JniOnLoad(vm, reserved);
+}
diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h
index bb2ee9b..0490e65 100644
--- a/native/webview/plat_support/draw_fn.h
+++ b/native/webview/plat_support/draw_fn.h
@@ -74,7 +74,10 @@
   VkQueue queue;
   uint32_t graphics_queue_index;
   uint32_t instance_version;
-  const char* const* enabled_extension_names;
+  const char* const* enabled_instance_extension_names;
+  uint32_t enabled_instance_extension_names_length;
+  const char* const* enabled_device_extension_names;
+  uint32_t enabled_device_extension_names_length;
   // Only one of device_features and device_features_2 should be non-null.
   // If both are null then no features are enabled.
   VkPhysicalDeviceFeatures* device_features;
@@ -128,15 +131,13 @@
 
 struct AwDrawFn_PostDrawVkParams {
   int version;
-
-  // Input: Fence for the composite command buffer to signal it has finished its
-  // work on the GPU.
-  int fd;
 };
 
 // Called on render thread while UI thread is blocked. Called for both GL and
 // VK.
-typedef void AwDrawFn_OnSync(int functor, void* data, AwDrawFn_OnSyncParams* params);
+typedef void AwDrawFn_OnSync(int functor,
+                             void* data,
+                             AwDrawFn_OnSyncParams* params);
 
 // Called on render thread when either the context is destroyed _or_ when the
 // functor's last reference goes away. Will always be called with an active
@@ -150,17 +151,24 @@
 typedef void AwDrawFn_OnDestroyed(int functor, void* data);
 
 // Only called for GL.
-typedef void AwDrawFn_DrawGL(int functor, void* data, AwDrawFn_DrawGLParams* params);
+typedef void AwDrawFn_DrawGL(int functor,
+                             void* data,
+                             AwDrawFn_DrawGLParams* params);
 
 // Initialize vulkan state. Needs to be called again after any
 // OnContextDestroyed. Only called for Vulkan.
-typedef void AwDrawFn_InitVk(int functor, void* data, AwDrawFn_InitVkParams* params);
+typedef void AwDrawFn_InitVk(int functor,
+                             void* data,
+                             AwDrawFn_InitVkParams* params);
 
 // Only called for Vulkan.
-typedef void AwDrawFn_DrawVk(int functor, void* data, AwDrawFn_DrawVkParams* params);
+typedef void AwDrawFn_DrawVk(int functor,
+                             void* data,
+                             AwDrawFn_DrawVkParams* params);
 
 // Only called for Vulkan.
-typedef void AwDrawFn_PostDrawVk(int functor, void* data,
+typedef void AwDrawFn_PostDrawVk(int functor,
+                                 void* data,
                                  AwDrawFn_PostDrawVkParams* params);
 
 struct AwDrawFnFunctorCallbacks {
@@ -183,7 +191,8 @@
 typedef AwDrawFnRenderMode AwDrawFn_QueryRenderMode(void);
 
 // Create a functor. |functor_callbacks| should be valid until OnDestroyed.
-typedef int AwDrawFn_CreateFunctor(void* data, AwDrawFnFunctorCallbacks* functor_callbacks);
+typedef int AwDrawFn_CreateFunctor(void* data,
+                                   AwDrawFnFunctorCallbacks* functor_callbacks);
 
 // May be called on any thread to signal that the functor should be destroyed.
 // The functor will receive an onDestroyed when the last usage of it is
diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp
index 6c1ceab..b97bbc3 100644
--- a/native/webview/plat_support/draw_functor.cpp
+++ b/native/webview/plat_support/draw_functor.cpp
@@ -74,6 +74,79 @@
   support->callbacks.draw_gl(functor, support->data, &params);
 }
 
+void initializeVk(int functor, void* data,
+                  const uirenderer::VkFunctorInitParams& init_vk_params) {
+  SupportData* support = static_cast<SupportData*>(data);
+  VkPhysicalDeviceFeatures2 device_features_2;
+  if (init_vk_params.device_features_2)
+    device_features_2 = *init_vk_params.device_features_2;
+
+  AwDrawFn_InitVkParams params{
+      .version = kAwDrawFnVersion,
+      .instance = init_vk_params.instance,
+      .physical_device = init_vk_params.physical_device,
+      .device = init_vk_params.device,
+      .queue = init_vk_params.queue,
+      .graphics_queue_index = init_vk_params.graphics_queue_index,
+      .instance_version = init_vk_params.instance_version,
+      .enabled_instance_extension_names =
+          init_vk_params.enabled_instance_extension_names,
+      .enabled_instance_extension_names_length =
+          init_vk_params.enabled_instance_extension_names_length,
+      .enabled_device_extension_names =
+          init_vk_params.enabled_device_extension_names,
+      .enabled_device_extension_names_length =
+          init_vk_params.enabled_device_extension_names_length,
+      .device_features = nullptr,
+      .device_features_2 =
+          init_vk_params.device_features_2 ? &device_features_2 : nullptr,
+  };
+  support->callbacks.init_vk(functor, support->data, &params);
+}
+
+void drawVk(int functor, void* data, const uirenderer::VkFunctorDrawParams& draw_vk_params) {
+  SupportData* support = static_cast<SupportData*>(data);
+  float gabcdef[7];
+  draw_vk_params.color_space_ptr->transferFn(gabcdef);
+  AwDrawFn_DrawVkParams params{
+      .version = kAwDrawFnVersion,
+      .width = draw_vk_params.width,
+      .height = draw_vk_params.height,
+      .is_layer = draw_vk_params.is_layer,
+      .secondary_command_buffer = draw_vk_params.secondary_command_buffer,
+      .color_attachment_index = draw_vk_params.color_attachment_index,
+      .compatible_render_pass = draw_vk_params.compatible_render_pass,
+      .format = draw_vk_params.format,
+      .transfer_function_g = gabcdef[0],
+      .transfer_function_a = gabcdef[1],
+      .transfer_function_b = gabcdef[2],
+      .transfer_function_c = gabcdef[3],
+      .transfer_function_d = gabcdef[4],
+      .transfer_function_e = gabcdef[5],
+      .transfer_function_f = gabcdef[6],
+      .clip_left = draw_vk_params.clip_left,
+      .clip_top = draw_vk_params.clip_top,
+      .clip_right = draw_vk_params.clip_right,
+      .clip_bottom = draw_vk_params.clip_bottom,
+  };
+  COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3),
+                 gamut_transform_size_mismatch);
+  draw_vk_params.color_space_ptr->toXYZD50(
+      reinterpret_cast<skcms_Matrix3x3*>(&params.color_space_toXYZD50));
+  COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_vk_params.transform),
+                 mismatched_transform_matrix_sizes);
+  for (int i = 0; i < NELEM(params.transform); ++i) {
+    params.transform[i] = draw_vk_params.transform[i];
+  }
+  support->callbacks.draw_vk(functor, support->data, &params);
+}
+
+void postDrawVk(int functor, void* data) {
+  SupportData* support = static_cast<SupportData*>(data);
+  AwDrawFn_PostDrawVkParams params{.version = kAwDrawFnVersion};
+  support->callbacks.post_draw_vk(functor, support->data, &params);
+}
+
 int CreateFunctor(void* data, AwDrawFnFunctorCallbacks* functor_callbacks) {
   static bool callbacks_initialized = false;
   static uirenderer::WebViewFunctorCallbacks webview_functor_callbacks = {
@@ -82,9 +155,19 @@
       .onDestroyed = &onDestroyed,
   };
   if (!callbacks_initialized) {
-    // Under uirenderer::RenderMode::Vulkan, whether gles or vk union should
-    // be populated should match whether the vk-gl interop is used.
-    webview_functor_callbacks.gles.draw = &draw_gl;
+    switch (uirenderer::WebViewFunctor_queryPlatformRenderMode()) {
+      case uirenderer::RenderMode::OpenGL_ES:
+        webview_functor_callbacks.gles.draw = &draw_gl;
+        break;
+      case uirenderer::RenderMode::Vulkan:
+        webview_functor_callbacks.vk.initialize = &initializeVk;
+        webview_functor_callbacks.vk.draw = &drawVk;
+        webview_functor_callbacks.vk.postDraw = &postDrawVk;
+        // TODO(boliu): Remove this once SkiaRecordingCanvas::drawWebViewFunctor
+        // no longer uses GL interop.
+        webview_functor_callbacks.gles.draw = &draw_gl;
+        break;
+    }
     callbacks_initialized = true;
   }
   SupportData* support = new SupportData{
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a29cf84..16bfcc9 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -103,6 +103,8 @@
     <string name="ssid_by_passpoint_provider"><xliff:g id="ssid" example="Cafe Wifi">%1$s</xliff:g> by <xliff:g id="passpointProvider" example="Passpoint Provider">%2$s</xliff:g></string>
     <!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
     <string name="available_via_passpoint">Available via %1$s</string>
+    <!-- Status message of OSU Provider network when not connected. [CHAR LIMIT=NONE] -->
+    <string name="tap_to_set_up">Tap to set up</string>
     <!-- Package name for Settings app-->
     <string name="settings_package" translatable="false">com.android.settings</string>
     <!-- Package name for Certinstaller app-->
@@ -125,6 +127,79 @@
     <!-- Status message of Wi-Fi when an available network is a carrier network. [CHAR LIMIT=NONE] -->
     <string name="available_via_carrier">Available via %1$s</string>
 
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_AP_CONNECTION. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_ap_connection">Connection failed</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_SERVER_URL_INVALID. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_server_url_invalid">Invalid OSU server URL</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_SERVER_CONNECTION. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_server_connection">OSU server connection failed</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_SERVER_VALIDATION. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_server_validation">OSU server validation failed</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_service_provider_verification">Invalid OSU server certificate</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_PROVISIONING_ABORTED. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_provisioning_aborted">Provisioning aborted</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_PROVISIONING_NOT_AVAILABLE. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_provisioning_not_available">Provisioning not available</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_INVALID_SERVER_URL. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_invalid_server_url">Invalid OSU server URL</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_UNEXPECTED_COMMAND_TYPE. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_unexpected_command_type">Unexpected command type</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_unexpected_soap_message_type">Unexpected SOAP message type</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_SOAP_MESSAGE_EXCHANGE. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_soap_message_exchange">SOAP message exchange failed</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_START_REDIRECT_LISTENER. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_start_redirect_listener">Redirect listener failed to start</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_timed_out_redirect_listener">Timed out waiting for redirect</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_OSU_ACTIVITY_FOUND. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_no_osu_activity_found">No OSU activity found</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_unexpected_soap_message_status">Unexpected SOAP message status</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_PPS_MO. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_no_pps_mo">Failed to find PPS-MO</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_no_aaa_server_trust_root_node">Failed to find trust root node for AAA server</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_no_remediation_server_trust_root_node">Failed to find trust root node for remediation server</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_no_policy_server_trust_root_node">Failed to find trust root node for policy server</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_retrieve_trust_root_certificates">Failed to retrieve trust root certificates</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_no_aaa_trust_root_certificate">Failed to find trust root certificate for AAA server</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_add_passpoint_configuration">Failed to add PassPoint configuration</string>
+    <!-- Status message of OSU Provider on receiving OSU_FAILURE_OSU_PROVIDER_NOT_FOUND. [CHAR LIMIT=NONE] -->
+    <string name="osu_failure_osu_provider_not_found">Failed to find an OSU provider</string>
+
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_AP_CONNECTING. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_ap_connecting">Connecting</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_AP_CONNECTED. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_ap_connected">Connected</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_SERVER_CONNECTING. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_server_connecting">Connecting to OSU server</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_SERVER_VALIDATED. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_server_validated">OSU server validated</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_SERVER_CONNECTED. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_server_connected">Connected to OSU server</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_INIT_SOAP_EXCHANGE. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_init_soap_exchange">Initial SOAP exchange</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_waiting_for_redirect_response">Waiting for redirect response</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_REDIRECT_RESPONSE_RECEIVED. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_redirect_response_received">Received redirect response</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_SECOND_SOAP_EXCHANGE. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_second_soap_exchange">Second SOAP exchange</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_THIRD_SOAP_EXCHANGE. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_third_soap_exchange">Third SOAP exchange</string>
+    <!-- Status message of OSU Provider on receiving OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS. [CHAR LIMIT=NONE] -->
+    <string name="osu_status_retrieving_trust_root_certs">Retrieving trust root certificates</string>
+
+    <!-- Status message of OSU Provider on completing provisioning. [CHAR LIMIT=NONE] -->
+    <string name="osu_provisioning_complete">Provisioning complete</string>
+
     <!-- Speed label for very slow network speed -->
     <string name="speed_label_very_slow">Very Slow</string>
     <!-- Speed label for slow network speed -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 595aeb3..c751c39 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -38,8 +38,8 @@
     private static final String CURRENT_MODE_KEY = "CURRENT_MODE";
     private static final String NEW_MODE_KEY = "NEW_MODE";
     @VisibleForTesting
-    static final String STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY =
-            "ro.storage_manager.show_opt_in";
+    static final String STORAGE_MANAGER_ENABLED_PROPERTY =
+            "ro.storage_manager.enabled";
 
     private static Signature[] sSystemSignature;
     private static String sPermissionControllerPackageName;
@@ -373,8 +373,7 @@
     public static boolean isStorageManagerEnabled(Context context) {
         boolean isDefaultOn;
         try {
-            // Turn off by default if the opt-in was shown.
-            isDefaultOn = !SystemProperties.getBoolean(STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY, true);
+            isDefaultOn = SystemProperties.getBoolean(STORAGE_MANAGER_ENABLED_PROPERTY, false);
         } catch (Resources.NotFoundException e) {
             isDefaultOn = false;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 12b8efb..4ab9a9a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -55,6 +55,12 @@
             "com.android.settings.category.ia.privacy";
     public static final String CATEGORY_ENTERPRISE_PRIVACY =
             "com.android.settings.category.ia.enterprise_privacy";
+    public static final String CATEGORY_ABOUT_LEGAL =
+            "com.android.settings.category.ia.about_legal";
+    public static final String CATEGORY_MY_DEVICE_INFO =
+            "com.android.settings.category.ia.my_device_info";
+    public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
+            "com.android.settings.category.ia.battery_saver_settings";
 
     public static final Map<String, String> KEY_COMPAT_MAP;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 1ae1d56..27dc628 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -43,6 +43,7 @@
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.ProvisioningCallback;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.RemoteException;
@@ -219,6 +220,11 @@
     private boolean mIsCarrierAp = false;
 
     private OsuProvider mOsuProvider;
+
+    private String mOsuStatus;
+    private String mOsuFailure;
+    private boolean mOsuProvisioningComplete = false;
+
     /**
      * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
      */
@@ -315,9 +321,7 @@
     public AccessPoint(Context context, OsuProvider provider) {
         mContext = context;
         mOsuProvider = provider;
-        mRssi = 1;
-        // TODO: This placeholder SSID is here to avoid null pointer exceptions.
-        ssid = "<OsuProvider AP SSID goes here>";
+        ssid = provider.getFriendlyName();
         updateKey();
     }
 
@@ -358,24 +362,13 @@
     /** Updates {@link #mKey} and should only called upon object creation/initialization. */
     private void updateKey() {
         // TODO(sghuman): Consolidate Key logic on ScanResultMatchInfo
-
-        StringBuilder builder = new StringBuilder();
-
         if (isPasspoint()) {
-            builder.append(KEY_PREFIX_FQDN).append(mConfig.FQDN);
+            mKey = getKey(mConfig);
         } else if (isOsuProvider()) {
-            builder.append(KEY_PREFIX_OSU).append(mOsuProvider.getOsuSsid());
-            builder.append(',').append(mOsuProvider.getServerUri());
+            mKey = getKey(mOsuProvider);
         } else { // Non-Passpoint AP
-            builder.append(KEY_PREFIX_AP);
-            if (TextUtils.isEmpty(getSsidStr())) {
-                builder.append(getBssid());
-            } else {
-                builder.append(getSsidStr());
-            }
-            builder.append(',').append(getSecurity());
+            mKey = getKey(getSsidStr(), getBssid(), getSecurity());
         }
-        mKey = builder.toString();
     }
 
     /**
@@ -616,34 +609,46 @@
     }
 
     public static String getKey(ScanResult result) {
-        StringBuilder builder = new StringBuilder();
-
-        builder.append(KEY_PREFIX_AP);
-        if (TextUtils.isEmpty(result.SSID)) {
-            builder.append(result.BSSID);
-        } else {
-            builder.append(result.SSID);
-        }
-
-        builder.append(',').append(getSecurity(result));
-        return builder.toString();
+        return getKey(result.SSID, result.BSSID, getSecurity(result));
     }
 
+    /**
+     * Returns the AccessPoint key for a WifiConfiguration.
+     * This will return a special Passpoint key if the config is for Passpoint.
+     */
     public static String getKey(WifiConfiguration config) {
-        StringBuilder builder = new StringBuilder();
-
         if (config.isPasspoint()) {
-            builder.append(KEY_PREFIX_FQDN).append(config.FQDN);
+            return new StringBuilder()
+                    .append(KEY_PREFIX_FQDN)
+                    .append(config.FQDN).toString();
         } else {
-            builder.append(KEY_PREFIX_AP);
-            if (TextUtils.isEmpty(config.SSID)) {
-                builder.append(config.BSSID);
-            } else {
-                builder.append(removeDoubleQuotes(config.SSID));
-            }
-            builder.append(',').append(getSecurity(config));
+            return getKey(config.SSID, config.BSSID, getSecurity(config));
         }
+    }
 
+    /**
+     * Returns the AccessPoint key corresponding to the OsuProvider.
+     */
+    public static String getKey(OsuProvider provider) {
+        return new StringBuilder()
+                .append(KEY_PREFIX_OSU)
+                .append(provider.getFriendlyName())
+                .append(',')
+                .append(provider.getServerUri()).toString();
+    }
+
+    /**
+     * Returns the AccessPoint key for a normal non-Passpoint network by ssid/bssid and security.
+     */
+    private static String getKey(String ssid, String bssid, int security) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(KEY_PREFIX_AP);
+        if (TextUtils.isEmpty(ssid)) {
+            builder.append(bssid);
+        } else {
+            builder.append(ssid);
+        }
+        builder.append(',').append(security);
         return builder.toString();
     }
 
@@ -881,7 +886,17 @@
         // Update to new summary
         StringBuilder summary = new StringBuilder();
 
-        if (isActive()) {
+        if (isOsuProvider()) {
+            if (mOsuProvisioningComplete) {
+                summary.append(mContext.getString(R.string.osu_provisioning_complete));
+            } else if (mOsuFailure != null) {
+                summary.append(mOsuFailure);
+            } else if (mOsuStatus != null) {
+                summary.append(mOsuStatus);
+            } else {
+                summary.append(mContext.getString(R.string.tap_to_set_up));
+            }
+        } else if (isActive()) {
             if (isPasspoint()) {
                 // This is the active connection on passpoint
                 summary.append(getSummary(mContext, ssid, getDetailedState(),
@@ -1014,11 +1029,26 @@
     }
 
     /**
+     * Starts the OSU Provisioning flow.
+     */
+    public void startOsuProvisioning() {
+        mContext.getSystemService(WifiManager.class).startSubscriptionProvisioning(
+                mOsuProvider,
+                new AccessPointProvisioningCallback(),
+                ThreadUtils.getUiThreadHandler()
+        );
+    }
+
+    /**
      * Return whether the given {@link WifiInfo} is for this access point.
      * If the current AP does not have a network Id then the config is used to
      * match based on SSID and security.
      */
     private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
+        if (info.isOsuAp()) {
+            return (mOsuStatus != null);
+        }
+
         if (isPasspoint() == false && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
             return networkId == info.getNetworkId();
         } else if (config != null) {
@@ -1491,4 +1521,166 @@
     private static boolean isVerboseLoggingEnabled() {
         return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
     }
+
+    /**
+     * Callbacks relaying changes to the OSU provisioning status started in startOsuProvisioning().
+     *
+     * All methods are invoked on the Main Thread
+     */
+    private class AccessPointProvisioningCallback extends ProvisioningCallback {
+        // TODO: Remove logs and implement summary changing logic for these provisioning callbacks.
+        @Override
+        @MainThread public void onProvisioningFailure(int status) {
+            switch (status) {
+                case OSU_FAILURE_AP_CONNECTION:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_ap_connection);
+                    break;
+                case OSU_FAILURE_SERVER_URL_INVALID:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_server_url_invalid);
+                    break;
+                case OSU_FAILURE_SERVER_CONNECTION:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_server_connection);
+                    break;
+                case OSU_FAILURE_SERVER_VALIDATION:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_server_validation);
+                    break;
+                case OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_service_provider_verification);
+                    break;
+                case OSU_FAILURE_PROVISIONING_ABORTED:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_provisioning_aborted);
+                    break;
+                case OSU_FAILURE_PROVISIONING_NOT_AVAILABLE:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_provisioning_not_available);
+                    break;
+                case OSU_FAILURE_INVALID_SERVER_URL:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_invalid_server_url);
+                    break;
+                case OSU_FAILURE_UNEXPECTED_COMMAND_TYPE:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_unexpected_command_type);
+                    break;
+                case OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_unexpected_soap_message_type);
+                    break;
+                case OSU_FAILURE_SOAP_MESSAGE_EXCHANGE:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_soap_message_exchange);
+                    break;
+                case OSU_FAILURE_START_REDIRECT_LISTENER:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_start_redirect_listener);
+                    break;
+                case OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_timed_out_redirect_listener);
+                    break;
+                case OSU_FAILURE_NO_OSU_ACTIVITY_FOUND:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_no_osu_activity_found);
+                    break;
+                case OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_unexpected_soap_message_status);
+                    break;
+                case OSU_FAILURE_NO_PPS_MO:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_no_pps_mo);
+                    break;
+                case OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_no_aaa_server_trust_root_node);
+                    break;
+                case OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_no_remediation_server_trust_root_node);
+                    break;
+                case OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_no_policy_server_trust_root_node);
+                    break;
+                case OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_retrieve_trust_root_certificates);
+                    break;
+                case OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_no_aaa_trust_root_certificate);
+                    break;
+                case OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION:
+                    mOsuFailure = mContext.getString(
+                            R.string.osu_failure_add_passpoint_configuration);
+                    break;
+                case OSU_FAILURE_OSU_PROVIDER_NOT_FOUND:
+                    mOsuFailure = mContext.getString(R.string.osu_failure_osu_provider_not_found);
+                    break;
+            }
+            mOsuStatus = null;
+            mOsuProvisioningComplete = false;
+            ThreadUtils.postOnMainThread(() -> {
+                if (mAccessPointListener != null) {
+                    mAccessPointListener.onAccessPointChanged(AccessPoint.this);
+                }
+            });
+        }
+
+        @Override
+        @MainThread public void onProvisioningStatus(int status) {
+            switch (status) {
+                case OSU_STATUS_AP_CONNECTING:
+                    mOsuStatus = mContext.getString(R.string.osu_status_ap_connecting);
+                    break;
+                case OSU_STATUS_AP_CONNECTED:
+                    mOsuStatus = mContext.getString(R.string.osu_status_ap_connected);
+                    break;
+                case OSU_STATUS_SERVER_CONNECTING:
+                    mOsuStatus = mContext.getString(R.string.osu_status_server_connecting);
+                    break;
+                case OSU_STATUS_SERVER_VALIDATED:
+                    mOsuStatus = mContext.getString(R.string.osu_status_server_validated);
+                    break;
+                case OSU_STATUS_SERVER_CONNECTED:
+                    mOsuStatus = mContext.getString(R.string.osu_status_server_connected);
+                    break;
+                case OSU_STATUS_INIT_SOAP_EXCHANGE:
+                    mOsuStatus = mContext.getString(R.string.osu_status_init_soap_exchange);
+                    break;
+                case OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE:
+                    mOsuStatus = mContext.getString(
+                            R.string.osu_status_waiting_for_redirect_response);
+                    break;
+                case OSU_STATUS_REDIRECT_RESPONSE_RECEIVED:
+                    mOsuStatus = mContext.getString(R.string.osu_status_redirect_response_received);
+                    break;
+                case OSU_STATUS_SECOND_SOAP_EXCHANGE:
+                    mOsuStatus = mContext.getString(R.string.osu_status_second_soap_exchange);
+                    break;
+                case OSU_STATUS_THIRD_SOAP_EXCHANGE:
+                    mOsuStatus = mContext.getString(R.string.osu_status_third_soap_exchange);
+                    break;
+                case OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS:
+                    mOsuStatus = mContext.getString(
+                            R.string.osu_status_retrieving_trust_root_certs);
+                    break;
+            }
+            mOsuFailure = null;
+            mOsuProvisioningComplete = false;
+            ThreadUtils.postOnMainThread(() -> {
+                if (mAccessPointListener != null) {
+                    mAccessPointListener.onAccessPointChanged(AccessPoint.this);
+                }
+            });
+        }
+
+        @Override
+        @MainThread public void onProvisioningComplete() {
+            mOsuProvisioningComplete = true;
+            mOsuFailure = null;
+            mOsuStatus = null;
+            ThreadUtils.postOnMainThread(() -> {
+                if (mAccessPointListener != null) {
+                    mAccessPointListener.onAccessPointChanged(AccessPoint.this);
+                }
+            });
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 6d28891..b9a5f23 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -68,6 +68,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -585,7 +586,6 @@
                     Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) {
                 WifiConfiguration config = pairing.first;
 
-                // TODO(b/118705403): Prioritize home networks before roaming networks
                 List<ScanResult> scanResults = new ArrayList<>();
 
                 List<ScanResult> homeScans =
@@ -600,8 +600,12 @@
                     roamingScans = new ArrayList<>();
                 }
 
-                scanResults.addAll(homeScans);
-                scanResults.addAll(roamingScans);
+                // TODO(b/118705403): Differentiate home network vs roaming network for summary info
+                if (!homeScans.isEmpty()) {
+                    scanResults.addAll(homeScans);
+                } else {
+                    scanResults.addAll(roamingScans);
+                }
 
                 if (seenFQDNs.add(config.FQDN)) {
                     int bestRssi = Integer.MIN_VALUE;
@@ -612,27 +616,27 @@
                         }
                     }
 
-                    AccessPoint accessPoint = new AccessPoint(mContext, config);
-                    accessPoint.setScanResults(scanResults);
+                    AccessPoint accessPoint =
+                            getCachedOrCreatePasspoint(scanResults, cachedAccessPoints, config);
                     accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
                     accessPoints.add(accessPoint);
                 }
             }
 
             // Add Passpoint OSU Provider AccessPoints
-            // TODO(b/118705403): filter out OSU Providers which we already have credentials from.
             Map<OsuProvider, List<ScanResult>> providersAndScans =
                     mWifiManager.getMatchingOsuProviders(cachedScanResults);
+            Set<OsuProvider> alreadyProvisioned = mWifiManager
+                    .getMatchingPasspointConfigsForOsuProviders(
+                            providersAndScans.keySet()).keySet();
             for (OsuProvider provider : providersAndScans.keySet()) {
-                AccessPoint accessPointOsu = new AccessPoint(mContext, provider);
-                // TODO(b/118705403): accessPointOsu.setScanResults(Matching ScanResult with best
-                // RSSI)
-                // TODO(b/118705403): Figure out if we would need to update an OSU AP (this will be
-                // used if we need to display it at the top of the picker as the "active" AP).
-                // Otherwise, OSU APs should ignore attempts to update the active connection
-                // info.
-                // accessPointOsu.update(connectionConfig, mLastInfo, mLastNetworkInfo);
-                accessPoints.add(accessPointOsu);
+                if (!alreadyProvisioned.contains(provider)) {
+                    AccessPoint accessPointOsu =
+                            getCachedOrCreateOsu(providersAndScans.get(provider),
+                                    cachedAccessPoints, provider);
+                    accessPointOsu.update(connectionConfig, mLastInfo, mLastNetworkInfo);
+                    accessPoints.add(accessPointOsu);
+                }
             }
 
 
@@ -685,16 +689,49 @@
     AccessPoint getCachedOrCreate(
             List<ScanResult> scanResults,
             List<AccessPoint> cache) {
-        final int N = cache.size();
-        for (int i = 0; i < N; i++) {
-            if (cache.get(i).getKey().equals(AccessPoint.getKey(scanResults.get(0)))) {
-                AccessPoint ret = cache.remove(i);
-                ret.setScanResults(scanResults);
-                return ret;
+        AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(scanResults.get(0)));
+        if (accessPoint == null) {
+            accessPoint = new AccessPoint(mContext, scanResults);
+        } else {
+            accessPoint.setScanResults(scanResults);
+        }
+        return accessPoint;
+    }
+
+    private AccessPoint getCachedOrCreatePasspoint(
+            List<ScanResult> scanResults,
+            List<AccessPoint> cache,
+            WifiConfiguration config) {
+        AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(config));
+        if (accessPoint == null) {
+            accessPoint = new AccessPoint(mContext, config);
+        }
+        accessPoint.setScanResults(scanResults);
+        return accessPoint;
+    }
+
+    private AccessPoint getCachedOrCreateOsu(
+            List<ScanResult> scanResults,
+            List<AccessPoint> cache,
+            OsuProvider provider) {
+        AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(provider));
+        if (accessPoint == null) {
+            accessPoint = new AccessPoint(mContext, provider);
+        }
+        accessPoint.setScanResults(scanResults);
+        return accessPoint;
+    }
+
+    private AccessPoint getCachedByKey(List<AccessPoint> cache, String key) {
+        ListIterator<AccessPoint> lit = cache.listIterator();
+        while (lit.hasNext()) {
+            AccessPoint currentAccessPoint = lit.next();
+            if (currentAccessPoint.getKey().equals(key)) {
+                lit.remove();
+                return currentAccessPoint;
             }
         }
-        final AccessPoint accessPoint = new AccessPoint(mContext, scanResults);
-        return accessPoint;
+        return null;
     }
 
     private void updateNetworkInfo(NetworkInfo networkInfo) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 86f0438..92ebe44 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -17,7 +17,7 @@
 
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 
-import static com.android.settingslib.Utils.STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY;
+import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -159,7 +159,7 @@
 
     @Test
     public void testIsStorageManagerEnabled_UsesSystemProperties() {
-        SystemProperties.set(STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY, "false");
+        SystemProperties.set(STORAGE_MANAGER_ENABLED_PROPERTY, "true");
         assertThat(Utils.isStorageManagerEnabled(mContext)).isTrue();
     }
 
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index afb9781..2d7471d 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1953,8 +1953,7 @@
 
         @Override
         public void onProgress(int progress) throws RemoteException {
-            // TODO(b/111441001): change max argument?
-            updateProgressInfo(progress, CAPPED_MAX);
+            updateProgressInfo(progress, 100 /* progress is already a percentage; so max = 100 */);
         }
 
         @Override
diff --git a/packages/SystemUI/res-keyguard/layout/stretchanalog_clock.xml b/packages/SystemUI/res-keyguard/layout/stretchanalog_clock.xml
new file mode 100644
index 0000000..9033fce
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/stretchanalog_clock.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<com.android.keyguard.clock.ClockLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+  >
+  <TextClock
+      android:id="@+id/digital_clock"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:letterSpacing="0.03"
+      android:singleLine="true"
+      style="@style/widget_big"
+      android:format12Hour="@string/keyguard_widget_12_hours_format"
+      android:format24Hour="@string/keyguard_widget_24_hours_format"
+  />
+  <com.android.keyguard.clock.StretchAnalogClock
+      android:id="@+id/analog_clock"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+  />
+</com.android.keyguard.clock.ClockLayout>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 41e9eba..a055950 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -46,6 +46,7 @@
     protected View mEcaView;
     protected boolean mEnableHaptics;
     private boolean mDismissing;
+    protected boolean mResumed;
     private CountDownTimer mCountdownTimer = null;
 
     // To avoid accidental lockout due to events while the device in in the pocket, ignore
@@ -263,6 +264,8 @@
 
     @Override
     public void onPause() {
+        mResumed = false;
+
         if (mCountdownTimer != null) {
             mCountdownTimer.cancel();
             mCountdownTimer = null;
@@ -276,6 +279,7 @@
 
     @Override
     public void onResume(int reason) {
+        mResumed = true;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 0ec0bf0..a8094d20 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -19,6 +19,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.keyguard.clock.BubbleClockController;
+import com.android.keyguard.clock.StretchAnalogClockController;
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.statusbar.StatusBarState;
@@ -146,6 +147,12 @@
                                 Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
                                 BubbleClockController.class.getName(),
                                 () -> BubbleClockController.build(mLayoutInflater)))
+                .withDefault(
+                        new SettingsGattedSupplier(
+                                mContentResolver,
+                                Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+                                StretchAnalogClockController.class.getName(),
+                                () -> StretchAnalogClockController.build(mLayoutInflater)))
                 .build();
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 41afa9a..3296c10 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -81,6 +81,11 @@
     protected void resetState() {
         mSecurityMessageDisplay.setMessage("");
         final boolean wasDisabled = mPasswordEntry.isEnabled();
+        // Don't set enabled password entry & showSoftInput when PasswordEntry is invisible or in
+        // pausing stage.
+        if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
+            return;
+        }
         setPasswordEntryEnabled(true);
         setPasswordEntryInputEnabled(true);
         if (wasDisabled) {
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java
new file mode 100644
index 0000000..91cec863
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+package com.android.keyguard.clock;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Analog clock where the minute hand extends off of the screen.
+ */
+public class StretchAnalogClock extends View {
+
+    private static final int DEFAULT_COLOR = Color.parseColor("#F5C983");
+    private static final float HOUR_STROKE_WIDTH = 60f;
+    private static final float MINUTE_STROKE_WIDTH = 20f;
+    private static final float CENTER_GAP_AND_CIRCLE_RADIUS = 80f;
+
+    private final Paint mHourPaint = new Paint();
+    private final Paint mMinutePaint = new Paint();
+    private Calendar mTime;
+    private TimeZone mTimeZone;
+
+    public StretchAnalogClock(Context context) {
+        this(context, null);
+    }
+
+    public StretchAnalogClock(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public StretchAnalogClock(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public StretchAnalogClock(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    /**
+     * Call when the time changes to update the clock hands.
+     */
+    public void onTimeChanged() {
+        mTime.setTimeInMillis(System.currentTimeMillis());
+        invalidate();
+    }
+
+    /**
+     * Call when the time zone has changed to update clock hands.
+     *
+     * @param timeZone The updated time zone that will be used.
+     */
+    public void onTimeZoneChanged(TimeZone timeZone) {
+        mTime.setTimeZone(timeZone);
+    }
+
+    /**
+     * Set the color of the minute hand.
+     */
+    public void setMinuteHandColor(int color) {
+        mMinutePaint.setColor(color);
+    }
+
+    private void init() {
+        mHourPaint.setColor(DEFAULT_COLOR);
+        mHourPaint.setStrokeWidth(HOUR_STROKE_WIDTH);
+        mHourPaint.setAntiAlias(true);
+        mHourPaint.setStrokeCap(Paint.Cap.ROUND);
+
+        mMinutePaint.setColor(Color.WHITE);
+        mMinutePaint.setStrokeWidth(MINUTE_STROKE_WIDTH);
+        mMinutePaint.setAntiAlias(true);
+        mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        final float centerX = getWidth() / 2f;
+        final float centerY = getHeight() / 2f;
+
+        final float minutesRotation = mTime.get(Calendar.MINUTE) * 6f;
+        final float hoursRotation = (mTime.get(Calendar.HOUR) * 30);
+
+        // Compute length of clock hands. Hour hand is 60% the length from center to edge
+        // and minute hand is twice the length to make sure it extends past screen edge.
+        double sMinuteHandLengthFactor = Math.sin(2d * Math.PI * minutesRotation / 360d);
+        float sMinuteHandLength = (float) (2d * (centerY + (centerX - centerY)
+                * sMinuteHandLengthFactor * sMinuteHandLengthFactor));
+        double sHourHandLengthFactor = Math.sin(2d * Math.PI * hoursRotation / 360d);
+        float sHourHandLength = (float) (0.6d * (centerY + (centerX - centerY)
+                * sHourHandLengthFactor * sHourHandLengthFactor));
+
+        canvas.save();
+
+        canvas.rotate(minutesRotation, centerX, centerY);
+        canvas.drawLine(
+                centerX,
+                centerY + CENTER_GAP_AND_CIRCLE_RADIUS,
+                centerX,
+                centerY - sMinuteHandLength,
+                mMinutePaint);
+
+        canvas.rotate(hoursRotation - minutesRotation, centerX, centerY);
+        canvas.drawLine(
+                centerX,
+                centerY + CENTER_GAP_AND_CIRCLE_RADIUS,
+                centerX,
+                centerY - sHourHandLength,
+                mHourPaint);
+
+        canvas.restore();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
+        onTimeChanged();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
new file mode 100644
index 0000000..0a39158
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+package com.android.keyguard.clock;
+
+import android.graphics.Paint.Style;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextClock;
+
+import com.android.keyguard.R;
+import com.android.systemui.plugins.ClockPlugin;
+
+import java.util.TimeZone;
+
+/**
+ * Controller for Stretch clock that can appear on lock screen and AOD.
+ */
+public class StretchAnalogClockController implements ClockPlugin {
+
+    /**
+     * Custom clock shown on AOD screen and behind stack scroller on lock.
+     */
+    private View mBigClockView;
+    private TextClock mDigitalClock;
+    private StretchAnalogClock mAnalogClock;
+
+    /**
+     * Small clock shown on lock screen above stack scroller.
+     */
+    private View mView;
+    private TextClock mLockClock;
+
+    /**
+     * Controller for transition to dark state.
+     */
+    private CrossFadeDarkController mDarkController;
+
+    private StretchAnalogClockController() { }
+
+    /**
+     * Create a BubbleClockController instance.
+     *
+     * @param layoutInflater Inflater used to inflate custom clock views.
+     */
+    public static StretchAnalogClockController build(LayoutInflater layoutInflater) {
+        StretchAnalogClockController controller = new StretchAnalogClockController();
+        controller.createViews(layoutInflater);
+        return controller;
+    }
+
+    private void createViews(LayoutInflater layoutInflater) {
+        mBigClockView = layoutInflater.inflate(R.layout.stretchanalog_clock, null);
+        mAnalogClock = mBigClockView.findViewById(R.id.analog_clock);
+        mDigitalClock = mBigClockView.findViewById(R.id.digital_clock);
+
+        mView = layoutInflater.inflate(R.layout.digital_clock, null);
+        mLockClock = mView.findViewById(R.id.lock_screen_clock);
+        mLockClock.setVisibility(View.GONE);
+
+        mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock);
+    }
+
+    @Override
+    public View getView() {
+        return mView;
+    }
+
+    @Override
+    public View getBigClockView() {
+        return mBigClockView;
+    }
+
+    @Override
+    public void setStyle(Style style) {}
+
+    @Override
+    public void setTextColor(int color) {
+        mLockClock.setTextColor(color);
+        mDigitalClock.setTextColor(color);
+        mAnalogClock.setMinuteHandColor(color);
+    }
+
+    @Override
+    public void dozeTimeTick() {
+        mAnalogClock.onTimeChanged();
+    }
+
+    @Override
+    public void setDarkAmount(float darkAmount) {
+        mDarkController.setDarkAmount(darkAmount);
+    }
+
+    @Override
+    public void onTimeZoneChanged(TimeZone timeZone) {
+        mAnalogClock.onTimeZoneChanged(timeZone);
+    }
+
+    @Override
+    public boolean shouldShowStatusArea() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 9422101..f79ad71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -541,7 +541,8 @@
 
     @VisibleForTesting
     void doUpdateMobileControllers() {
-        List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
+        List<SubscriptionInfo> subscriptions = mSubscriptionManager
+                .getActiveSubscriptionInfoList(true);
         if (subscriptions == null) {
             subscriptions = Collections.emptyList();
         }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java
new file mode 100644
index 0000000..8de8f3d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+package com.android.keyguard.clock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public final class StretchAnalogClockControllerTest extends SysuiTestCase {
+
+    private StretchAnalogClockController mClockController;
+
+    @Before
+    public void setUp() {
+        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+        mClockController = StretchAnalogClockController.build(layoutInflater);
+    }
+
+    @Test
+    public void setDarkAmount_fadeIn() {
+        ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
+        View smallClock = smallClockFrame.getChildAt(0);
+        // WHEN dark amount is set to AOD
+        mClockController.setDarkAmount(1f);
+        // THEN small clock should not be shown.
+        assertThat(smallClock.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void setTextColor_setDigitalClock() {
+        ViewGroup smallClock = (ViewGroup) mClockController.getView();
+        // WHEN text color is set
+        mClockController.setTextColor(42);
+        // THEN child of small clock should have text color set.
+        TextView digitalClock = (TextView) smallClock.getChildAt(0);
+        assertThat(digitalClock.getCurrentTextColor()).isEqualTo(42);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index fdbf090..c1f8885 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -182,6 +182,7 @@
             subs.add(subscription);
         }
         when(mMockSm.getActiveSubscriptionInfoList()).thenReturn(subs);
+        when(mMockSm.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subs);
         mNetworkController.doUpdateMobileControllers();
     }
 
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index 295b3d8..be9ba43 100644
--- a/packages/services/PacProcessor/Android.mk
+++ b/packages/services/PacProcessor/Android.mk
@@ -26,6 +26,6 @@
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_CERTIFICATE := platform
 
-LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor libpac
+LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor
 
 include $(BUILD_PACKAGE)
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 24fd7b9..ec6d20d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -701,6 +701,11 @@
         public void onBackKeyPressed() {
             if (sDebug) Slog.d(TAG, "onBackKeyPressed()");
             mUi.hideAll(null);
+            synchronized (mLock) {
+                final AutofillManagerServiceImpl service =
+                        getServiceForUserLocked(UserHandle.getCallingUserId());
+                service.onBackKeyPressed();
+            }
         }
 
         @Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index a6bb049..d037b08 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -187,6 +187,15 @@
     }
 
     @GuardedBy("mLock")
+    void onBackKeyPressed() {
+        final RemoteAugmentedAutofillService remoteService =
+                getRemoteAugmentedAutofillServiceLocked();
+        if (remoteService != null) {
+            remoteService.onDestroyAutofillWindowsRequest();
+        }
+    }
+
+    @GuardedBy("mLock")
     @Override // from PerUserSystemService
     protected boolean updateLocked(boolean disabled) {
         destroySessionsLocked();
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index a8ff9b0..5d8d8fa 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -109,8 +109,8 @@
     /**
      * Called by {@link Session} when it's time to destroy all augmented autofill requests.
      */
-    public void onDestroyAutofillWindowsRequest(int sessionId) {
-        scheduleAsyncRequest((s) -> s.onDestroyFillWindowRequest(sessionId));
+    public void onDestroyAutofillWindowsRequest() {
+        scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
     }
 
     // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index cf4963c..a5ef21a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2619,7 +2619,7 @@
                 currentValue);
 
         if (mAugmentedAutofillDestroyer == null) {
-            mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(id);
+            mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
         }
         return mAugmentedAutofillDestroyer;
     }
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 529430c..a7bada0 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -562,7 +562,7 @@
     private void registerTransportsFromPackage(
             String packageName, Predicate<ComponentName> transportComponentFilter) {
         try {
-            mPackageManager.getPackageInfo(packageName, 0);
+            mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId);
         } catch (PackageManager.NameNotFoundException e) {
             Slog.e(TAG, "Trying to register transports from package not found " + packageName);
             return;
@@ -599,7 +599,8 @@
             return false;
         }
         try {
-            PackageInfo packInfo = mPackageManager.getPackageInfo(transport.getPackageName(), 0);
+            PackageInfo packInfo =
+                    mPackageManager.getPackageInfoAsUser(transport.getPackageName(), 0, mUserId);
             if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
                     == 0) {
                 Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 3a8966a..e0e81ff 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -495,7 +495,7 @@
 
         mBaseStateDir = checkNotNull(baseStateDir, "baseStateDir cannot be null");
         mBaseStateDir.mkdirs();
-        if (!SELinux.restorecon(mBaseStateDir)) {
+        if (!SELinux.restoreconRecursive(mBaseStateDir)) {
             Slog.w(TAG, "SELinux restorecon failed on " + mBaseStateDir);
         }
 
@@ -504,7 +504,7 @@
         // is managed by init.rc so we don't have to create it below.
         if (userId != UserHandle.USER_SYSTEM) {
             mDataDir.mkdirs();
-            if (!SELinux.restorecon(mDataDir)) {
+            if (!SELinux.restoreconRecursive(mDataDir)) {
                 Slog.w(TAG, "SELinux restorecon failed on " + mDataDir);
             }
         }
@@ -584,7 +584,7 @@
         mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
 
         // Power management
-        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
+        mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*-" + userId);
     }
 
     void initializeBackupEnableState() {
@@ -1352,7 +1352,7 @@
     private List<PackageInfo> allAgentPackages() {
         // !!! TODO: cache this and regenerate only when necessary
         int flags = PackageManager.GET_SIGNING_CERTIFICATES;
-        List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags);
+        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(flags, mUserId);
         int numPackages = packages.size();
         for (int a = numPackages - 1; a >= 0; a--) {
             PackageInfo pkg = packages.get(a);
@@ -1366,8 +1366,8 @@
                     // we will need the shared library path, so look that up and store it here.
                     // This is used implicitly when we pass the PackageInfo object off to
                     // the Activity Manager to launch the app for backup/restore purposes.
-                    app = mPackageManager.getApplicationInfo(pkg.packageName,
-                            PackageManager.GET_SHARED_LIBRARY_FILES);
+                    app = mPackageManager.getApplicationInfoAsUser(pkg.packageName,
+                            PackageManager.GET_SHARED_LIBRARY_FILES, mUserId);
                     pkg.applicationInfo.sharedLibraryFiles = app.sharedLibraryFiles;
                     pkg.applicationInfo.sharedLibraryInfos = app.sharedLibraryInfos;
                 }
@@ -1392,7 +1392,7 @@
             notification.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES
                     | Intent.FLAG_RECEIVER_FOREGROUND);
             notification.putExtra(BACKUP_FINISHED_PACKAGE_EXTRA, packageName);
-            mContext.sendBroadcastAsUser(notification, UserHandle.OWNER);
+            mContext.sendBroadcastAsUser(notification, UserHandle.of(mUserId));
         }
 
         mProcessedPackagesJournal.addPackage(packageName);
@@ -2208,11 +2208,10 @@
     /** Used by both incremental and full restore to restore widget data. */
     public void restoreWidgetData(String packageName, byte[] widgetData) {
         // Apply the restored widget state and generate the ID update for the app
-        // TODO: http://b/22388012
         if (MORE_DEBUG) {
             Slog.i(TAG, "Incorporating restored widget data");
         }
-        AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, UserHandle.USER_SYSTEM);
+        AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, mUserId);
     }
 
     // *****************************
@@ -2291,20 +2290,6 @@
 
     /** Sent from an app's backup agent to let the service know that there's new data to backup. */
     public void dataChanged(final String packageName) {
-        final int callingUserHandle = UserHandle.getCallingUserId();
-        if (callingUserHandle != UserHandle.USER_SYSTEM) {
-            // TODO: http://b/22388012
-            // App is running under a non-owner user profile.  For now, we do not back
-            // up data from secondary user profiles.
-            // TODO: backups for all user profiles although don't add backup for profiles
-            // without adding admin control in DevicePolicyManager.
-            if (MORE_DEBUG) {
-                Slog.v(TAG, "dataChanged(" + packageName + ") ignored because it's user "
-                        + callingUserHandle);
-            }
-            return;
-        }
-
         final HashSet<String> targets = dataChangedTargets(packageName);
         if (targets == null) {
             Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
@@ -2921,7 +2906,7 @@
         try {
             int transportUid =
                     mContext.getPackageManager()
-                            .getPackageUid(transportComponent.getPackageName(), 0);
+                            .getPackageUidAsUser(transportComponent.getPackageName(), 0, mUserId);
             if (callingUid != transportUid) {
                 throw new SecurityException("Only the transport can change its description");
             }
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index c7f3315..b3d9fbc 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -68,6 +68,8 @@
 public class FullRestoreEngine extends RestoreEngine {
 
     private final UserBackupManagerService mBackupManagerService;
+    private final int mUserId;
+
     // Task in charge of monitoring timeouts
     private final BackupRestoreTask mMonitorTask;
 
@@ -146,6 +148,7 @@
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
         mIsAdbRestore = isAdbRestore;
+        mUserId = backupManagerService.getUserId();
     }
 
     public IBackupAgent getAgent() {
@@ -272,7 +275,7 @@
                                         instream, mBackupManagerService.getContext(),
                                         mDeleteObserver, mManifestSignatures,
                                         mPackagePolicies, info, installerPackageName,
-                                        bytesReadListener, mBackupManagerService.getUserId());
+                                        bytesReadListener, mUserId);
                                 // good to go; promote to ACCEPT
                                 mPackagePolicies.put(pkg, isSuccessfullyInstalled
                                         ? RestorePolicy.ACCEPT
@@ -329,9 +332,8 @@
                         }
 
                         try {
-                            mTargetApp =
-                                    mBackupManagerService.getPackageManager().getApplicationInfo(
-                                            pkg, 0);
+                            mTargetApp = mBackupManagerService.getPackageManager()
+                                    .getApplicationInfoAsUser(pkg, 0, mUserId);
 
                             // If we haven't sent any data to this app yet, we probably
                             // need to clear it first. Check that.
@@ -684,7 +686,7 @@
         String packageListString = Settings.Secure.getStringForUser(
                 mBackupManagerService.getContext().getContentResolver(),
                 Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE,
-                mBackupManagerService.getUserId());
+                mUserId);
         if (TextUtils.isEmpty(packageListString)) {
             return false;
         }
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 5284d94..d01f77b 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -360,7 +360,6 @@
 
         // If we're starting a full-system restore, set up to begin widget ID remapping
         if (mIsSystemRestore) {
-            // TODO: http://b/22388012
             AppWidgetBackupBridge.restoreStarting(mUserId);
         }
 
@@ -1079,7 +1078,6 @@
         }
 
         // Kick off any work that may be needed regarding app widget restores
-        // TODO: http://b/22388012
         AppWidgetBackupBridge.restoreFinished(mUserId);
 
         // If this was a full-system restore, record the ancestral
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 09aa421..1dae2ce 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -17,6 +17,9 @@
 package com.android.server.contentcapture;
 
 import static android.service.contentcapture.ContentCaptureService.setClientState;
+import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
+import static android.view.contentcapture.ContentCaptureSession.STATE_DUPLICATED_ID;
+import static android.view.contentcapture.ContentCaptureSession.STATE_NO_SERVICE;
 
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
@@ -36,10 +39,11 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.service.contentcapture.ContentCaptureService;
+import android.service.contentcapture.IContentCaptureServiceCallback;
 import android.service.contentcapture.SnapshotData;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Slog;
-import android.view.contentcapture.ContentCaptureSession;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -48,6 +52,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Per-user instance of {@link ContentCaptureManagerService}.
@@ -72,6 +77,9 @@
     @GuardedBy("mLock")
     private RemoteContentCaptureService mRemoteService;
 
+    private final ContentCaptureServiceRemoteCallback mRemoteServiceCallback =
+            new ContentCaptureServiceRemoteCallback();
+
     // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
 
     ContentCapturePerUserService(@NonNull ContentCaptureManagerService master,
@@ -100,10 +108,10 @@
         }
 
         if (!disabled) {
-            mRemoteService = new RemoteContentCaptureService(
-                  mMaster.getContext(),
-                  ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, mUserId, this,
-                  mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+            mRemoteService = new RemoteContentCaptureService(mMaster.getContext(),
+                    ContentCaptureService.SERVICE_INTERFACE, serviceComponentName,
+                    mRemoteServiceCallback, mUserId, this, mMaster.isBindInstantServiceAllowed(),
+                    mMaster.verbose);
         }
     }
 
@@ -165,7 +173,7 @@
         if (!isEnabledLocked()) {
             // TODO: it would be better to split in differet reasons, like
             // STATE_DISABLED_NO_SERVICE and STATE_DISABLED_BY_DEVICE_POLICY
-            setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_NO_SERVICE,
+            setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
                     /* binder= */ null);
             return;
         }
@@ -184,7 +192,7 @@
         if (existingSession != null) {
             Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
                     + ": ignoring because it already exists for " + existingSession.mActivityToken);
-            setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_DUPLICATED_ID,
+            setClientState(clientReceiver, STATE_DISABLED | STATE_DUPLICATED_ID,
                     /* binder=*/ null);
             return;
         }
@@ -197,8 +205,7 @@
             // TODO(b/119613670): log metrics
             Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
                     + ": ignoring because service is not set");
-            // TODO(b/111276913): use a new disabled state?
-            setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_NO_SERVICE,
+            setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
                     /* binder= */ null);
             return;
         }
@@ -338,4 +345,50 @@
         }
         return null;
     }
+
+    private final class ContentCaptureServiceRemoteCallback extends
+            IContentCaptureServiceCallback.Stub {
+
+        @Override
+        public void setContentCaptureWhitelist(List<String> packages,
+                List<ComponentName> activities) {
+            if (mMaster.verbose) {
+                Log.v(TAG, "setContentCaptureWhitelist(packages=" + packages + ", activities="
+                        + activities + ")");
+            }
+            // TODO(b/122595322): implement
+            // TODO(b/119613670): log metrics
+        }
+
+        @Override
+        public void setActivityContentCaptureEnabled(ComponentName activity, boolean enabled) {
+            if (mMaster.verbose) {
+                Log.v(TAG, "setActivityContentCaptureEnabled(activity=" + activity + ", enabled="
+                        + enabled + ")");
+            }
+            // TODO(b/122595322): implement
+            // TODO(b/119613670): log metrics
+        }
+
+        @Override
+        public void setPackageContentCaptureEnabled(String packageName, boolean enabled) {
+            if (mMaster.verbose) {
+                Log.v(TAG,
+                        "setPackageContentCaptureEnabled(packageName=" + packageName + ", enabled="
+                                + enabled + ")");
+            }
+            // TODO(b/122595322): implement
+            // TODO(b/119613670): log metrics
+        }
+
+        @Override
+        public void getContentCaptureDisabledActivities(IResultReceiver receiver) {
+            // TODO(b/122595322): implement
+        }
+
+        @Override
+        public void getContentCaptureDisabledPackages(IResultReceiver receiver) {
+            // TODO(b/122595322): implement
+        }
+    }
 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 8241628..12742ca 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.os.IBinder;
 import android.service.contentcapture.IContentCaptureService;
+import android.service.contentcapture.IContentCaptureServiceCallback;
 import android.service.contentcapture.SnapshotData;
 import android.text.format.DateUtils;
 import android.util.Slog;
@@ -35,12 +36,15 @@
 
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
 
+    private final IBinder mServerCallback;
+
     RemoteContentCaptureService(Context context, String serviceInterface,
-            ComponentName componentName, int userId,
+            ComponentName serviceComponentName, IContentCaptureServiceCallback callback, int userId,
             ContentCaptureServiceCallbacks callbacks, boolean bindInstantServiceAllowed,
             boolean verbose) {
-        super(context, serviceInterface, componentName, userId, callbacks,
+        super(context, serviceInterface, serviceComponentName, userId, callbacks,
                 bindInstantServiceAllowed, verbose, /* initialCapacity= */ 2);
+        mServerCallback = callback.asBinder();
 
         // Bind right away, which will trigger a onConnected() on service's
         scheduleBind();
@@ -69,7 +73,11 @@
             scheduleUnbind();
         }
         try {
-            mService.onConnectedStateChanged(state);
+            if (state) {
+                mService.onConnected(mServerCallback);
+            } else {
+                mService.onDisconnected();
+            }
         } catch (Exception e) {
             Slog.w(mTag, "Exception calling onConnectedStateChanged(" + state + "): " + e);
         }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d0666b9..d6f3e2b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4879,7 +4879,7 @@
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
                 new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
-                mContext, mTrackerHandler, new NetworkMisc(networkMisc), this);
+                mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mNMS);
         // Make sure the network capabilities reflect what the agent info says.
         nai.networkCapabilities = mixInCapabilities(nai, nc);
         final String extraInfo = networkInfo.getExtraInfo();
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 869d564..0b4c01e 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -257,6 +257,9 @@
         packageManagerInternal.setLocationPackagesProvider(
                 userId -> mContext.getResources().getStringArray(
                         com.android.internal.R.array.config_locationProviderPackageNames));
+        packageManagerInternal.setLocationExtraPackagesProvider(
+                userId -> mContext.getResources().getStringArray(
+                      com.android.internal.R.array.config_locationExtraPackageNames));
 
         // most startup is deferred until systemRunning()
     }
@@ -1041,12 +1044,13 @@
 
         @Override
         public void onSetProperties(ProviderProperties properties) {
-            // move calls coming from below LMS onto a different thread to avoid deadlock
-            runInternal(() -> {
-                synchronized (mLock) {
-                    mProperties = properties;
-                }
-            });
+            // because this does not invoke any other methods which might result in calling back
+            // into the location provider, it is safe to run this on the calling thread. it is also
+            // currently necessary to run this on the calling thread to ensure that property changes
+            // are publicly visibly immediately, ie for mock providers which are created.
+            synchronized (mLock) {
+                mProperties = properties;
+            }
         }
 
         @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index b0ca2df..aed0684 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -17,13 +17,11 @@
 package com.android.server;
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
-import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.SHUTDOWN;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
-import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_NONE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE;
@@ -40,6 +38,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
+
 import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
@@ -53,11 +52,9 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetd;
-import android.net.TetherStatsParcel;
 import android.net.INetworkManagementEventObserver;
 import android.net.ITetheringStatsProvider;
 import android.net.InterfaceConfiguration;
@@ -69,18 +66,15 @@
 import android.net.NetworkStats;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.TetherStatsParcel;
 import android.net.UidRange;
-import android.net.UidRangeParcel;
 import android.net.util.NetdService;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkActivityListener;
 import android.os.INetworkManagementService;
-import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -91,12 +85,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
-import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
-import android.telephony.PhoneStateListener;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -110,13 +99,11 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.Preconditions;
-import com.android.server.NativeDaemonConnector.Command;
-import com.android.server.NativeDaemonConnector.SensitiveArg;
+
 import com.google.android.collect.Maps;
 
 import java.io.BufferedReader;
 import java.io.DataInputStream;
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -124,15 +111,11 @@
 import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.StringTokenizer;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -2153,28 +2136,6 @@
     }
 
     @Override
-    public void startClatd(String interfaceName) throws IllegalStateException {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        try {
-            mNetdService.clatdStart(interfaceName);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void stopClatd(String interfaceName) throws IllegalStateException {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        try {
-            mNetdService.clatdStop(interfaceName);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
     public void registerNetworkActivityListener(INetworkActivityListener listener) {
         mNetworkActivityListeners.register(listener);
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 1c04a94..dd2b33a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -70,6 +70,12 @@
     static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
     static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
     static final String KEY_USE_COMPACTION = "use_compaction";
+    static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
+    static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
+    static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+    static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+    static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+    static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
 
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
     private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -101,6 +107,12 @@
     private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
     private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
     private static final boolean DEFAULT_USE_COMPACTION = false;
+    public static final int DEFAULT_COMPACT_ACTION_1 = 1;
+    public static final int DEFAULT_COMPACT_ACTION_2 = 3;
+    public static final long DEFAULT_COMPACT_THROTTLE_1 = 5000;
+    public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000;
+    public static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
+    public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000;
 
     // Maximum number of cached processes we will allow.
     public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -223,6 +235,20 @@
     // Use compaction for background apps.
     public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION;
 
+    // Action for compactAppSome.
+    public int COMPACT_ACTION_1 = DEFAULT_COMPACT_ACTION_1;
+    // Action for compactAppFull;
+    public int COMPACT_ACTION_2 = DEFAULT_COMPACT_ACTION_2;
+
+    // How long we'll skip second compactAppSome after first compactAppSome
+    public long COMPACT_THROTTLE_1 = DEFAULT_COMPACT_THROTTLE_1;
+    // How long we'll skip compactAppSome after compactAppFull
+    public long COMPACT_THROTTLE_2 = DEFAULT_COMPACT_THROTTLE_2;
+    // How long we'll skip compactAppFull after compactAppSome
+    public long COMPACT_THROTTLE_3 = DEFAULT_COMPACT_THROTTLE_3;
+    // How long we'll skip second compactAppFull after first compactAppFull
+    public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4;
+
     // Indicates whether the activity starts logging is enabled.
     // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
     volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -381,6 +407,12 @@
             TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
                     DEFAULT_TOP_TO_FGS_GRACE_DURATION);
             USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
+            COMPACT_ACTION_1 = mParser.getInt(KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
+            COMPACT_ACTION_2 = mParser.getInt(KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
+            COMPACT_THROTTLE_1 = mParser.getLong(KEY_COMPACT_THROTTLE_1, DEFAULT_COMPACT_THROTTLE_1);
+            COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2);
+            COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3);
+            COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4);
 
             updateMaxCachedProcesses();
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd6fa49..43deb11 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -32,6 +32,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
 import static android.content.pm.PackageManager.GET_PROVIDERS;
+import static android.content.pm.PackageManager.MATCH_ALL;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
@@ -318,7 +319,6 @@
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.TriFunction;
 import com.android.server.AlarmManagerInternal;
-import com.android.server.appop.AppOpsService;
 import com.android.server.AttributeCache;
 import com.android.server.DeviceIdleController;
 import com.android.server.DisplayThread;
@@ -337,6 +337,7 @@
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
 import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.appop.AppOpsService;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.pm.Installer;
@@ -658,9 +659,47 @@
 
     /**
      * When an app has restrictions on the other apps that can have associations with it,
-     * it appears here with a set of the allowed apps.
+     * it appears here with a set of the allowed apps and also track debuggability of the app.
      */
-    ArrayMap<String, ArraySet<String>> mAllowedAssociations;
+    ArrayMap<String, PackageAssociationInfo> mAllowedAssociations;
+
+    /**
+     * Tracks association information for a particular package along with debuggability.
+     * <p> Associations for a package A are allowed to package B if B is part of the
+     *     allowed associations for A or if A is debuggable.
+     */
+    private final class PackageAssociationInfo {
+        private final String mSourcePackage;
+        private final ArraySet<String> mAllowedPackageAssociations;
+        private boolean mIsDebuggable;
+
+        PackageAssociationInfo(String sourcePackage, ArraySet<String> allowedPackages,
+                boolean isDebuggable) {
+            mSourcePackage = sourcePackage;
+            mAllowedPackageAssociations = allowedPackages;
+            mIsDebuggable = isDebuggable;
+        }
+
+        /**
+         * Returns true if {@code mSourcePackage} is allowed association with
+         * {@code targetPackage}.
+         */
+        boolean isPackageAssociationAllowed(String targetPackage) {
+            return mIsDebuggable || mAllowedPackageAssociations.contains(targetPackage);
+        }
+
+        boolean isDebuggable() {
+            return mIsDebuggable;
+        }
+
+        void setDebuggable(boolean isDebuggable) {
+            mIsDebuggable = isDebuggable;
+        }
+
+        ArraySet<String> getAllowedPackageAssociations() {
+            return mAllowedPackageAssociations;
+        }
+    }
 
     /**
      * All of the processes we currently have running organized by pid.
@@ -2392,12 +2431,10 @@
      * If it does not, give it an empty set.
      */
     void requireAllowedAssociationsLocked(String packageName) {
-        if (mAllowedAssociations == null) {
-            mAllowedAssociations = new ArrayMap<>(
-                    SystemConfig.getInstance().getAllowedAssociations());
-        }
+        ensureAllowedAssociations();
         if (mAllowedAssociations.get(packageName) == null) {
-            mAllowedAssociations.put(packageName, new ArraySet<>());
+            mAllowedAssociations.put(packageName, new PackageAssociationInfo(packageName,
+                    new ArraySet<>(), /* isDebuggable = */ false));
         }
     }
 
@@ -2408,10 +2445,7 @@
      * association is implicitly allowed.
      */
     boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) {
-        if (mAllowedAssociations == null) {
-            mAllowedAssociations = new ArrayMap<>(
-                    SystemConfig.getInstance().getAllowedAssociations());
-        }
+        ensureAllowedAssociations();
         // Interactions with the system uid are always allowed, since that is the core system
         // that everyone needs to be able to interact with. Also allow reflexive associations
         // within the same uid.
@@ -2419,24 +2453,57 @@
                 || UserHandle.getAppId(uid2) == SYSTEM_UID) {
             return true;
         }
-        // We won't allow this association if either pkg1 or pkg2 has a limit on the
-        // associations that are allowed with it, and the other package is not explicitly
-        // specified as one of those associations.
-        ArraySet<String> pkgs = mAllowedAssociations.get(pkg1);
-        if (pkgs != null) {
-            if (!pkgs.contains(pkg2)) {
-                return false;
-            }
+
+        // Check for association on both source and target packages.
+        PackageAssociationInfo pai = mAllowedAssociations.get(pkg1);
+        if (pai != null && !pai.isPackageAssociationAllowed(pkg2)) {
+            return false;
         }
-        pkgs = mAllowedAssociations.get(pkg2);
-        if (pkgs != null) {
-            return pkgs.contains(pkg1);
+        pai = mAllowedAssociations.get(pkg2);
+        if (pai != null && !pai.isPackageAssociationAllowed(pkg1)) {
+            return false;
         }
         // If no explicit associations are provided in the manifest, then assume the app is
         // allowed associations with any package.
         return true;
     }
 
+    /** Sets up allowed associations for system prebuilt packages from system config (if needed). */
+    private void ensureAllowedAssociations() {
+        if (mAllowedAssociations == null) {
+            ArrayMap<String, ArraySet<String>> allowedAssociations =
+                    SystemConfig.getInstance().getAllowedAssociations();
+            mAllowedAssociations = new ArrayMap<>(allowedAssociations.size());
+            PackageManagerInternal pm = getPackageManagerInternalLocked();
+            for (int i = 0; i < allowedAssociations.size(); i++) {
+                final String pkg = allowedAssociations.keyAt(i);
+                final ArraySet<String> asc = allowedAssociations.valueAt(i);
+
+                // Query latest debuggable flag from package-manager.
+                boolean isDebuggable = false;
+                try {
+                    ApplicationInfo ai = AppGlobals.getPackageManager()
+                            .getApplicationInfo(pkg, MATCH_ALL, 0);
+                    if (ai != null) {
+                        isDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+                    }
+                } catch (RemoteException e) {
+                    /* ignore */
+                }
+                mAllowedAssociations.put(pkg, new PackageAssociationInfo(pkg, asc, isDebuggable));
+            }
+        }
+    }
+
+    /** Updates allowed associations for app info (specifically, based on debuggability).  */
+    private void updateAssociationForApp(ApplicationInfo appInfo) {
+        ensureAllowedAssociations();
+        PackageAssociationInfo pai = mAllowedAssociations.get(appInfo.packageName);
+        if (pai != null) {
+            pai.setDebuggable((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+        }
+    }
+
     @Override
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
             throws RemoteException {
@@ -10628,7 +10695,6 @@
             ProtoUtils.toDuration(proto, ActivityManagerServiceDumpProcessesProto.LAST_IDLE_TIME, mLastIdleTime, now);
             proto.write(ActivityManagerServiceDumpProcessesProto.LOW_RAM_SINCE_LAST_IDLE_MS, getLowRamTimeSinceIdle(now));
         }
-
     }
 
     void writeProcessesToGcToProto(ProtoOutputStream proto, long fieldId, String dumpPackage) {
@@ -10910,14 +10976,14 @@
     void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
-        boolean printedAnything = false;
 
         pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
         boolean printed = false;
         if (mAllowedAssociations != null) {
             for (int i = 0; i < mAllowedAssociations.size(); i++) {
                 final String pkg = mAllowedAssociations.keyAt(i);
-                final ArraySet<String> asc = mAllowedAssociations.valueAt(i);
+                final ArraySet<String> asc =
+                        mAllowedAssociations.valueAt(i).getAllowedPackageAssociations();
                 boolean printedHeader = false;
                 for (int j = 0; j < asc.size(); j++) {
                     if (dumpPackage == null || pkg.equals(dumpPackage)
@@ -10926,7 +10992,6 @@
                             pw.println("  Allowed associations (by restricted package):");
                             printed = true;
                             needSep = true;
-                            printedAnything = true;
                         }
                         if (!printedHeader) {
                             pw.print("  * ");
@@ -10938,6 +11003,9 @@
                         pw.println(asc.valueAt(j));
                     }
                 }
+                if (mAllowedAssociations.valueAt(i).isDebuggable()) {
+                    pw.println("      (debuggable)");
+                }
             }
         }
         if (!printed) {
@@ -14519,6 +14587,7 @@
                                     + " ssp=" + ssp + " data=" + data);
                             return ActivityManager.BROADCAST_SUCCESS;
                         }
+                        updateAssociationForApp(aInfo);
                         mAtmInternal.onPackageReplaced(aInfo);
                         mServices.updateServiceApplicationInfoLocked(aInfo);
                         sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index aee16c3..fe27c49 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -44,7 +44,11 @@
      */
     final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>();
 
-    /*
+    static final int COMPACT_PROCESS_SOME = 1;
+    static final int COMPACT_PROCESS_FULL = 2;
+    static final int COMPACT_PROCESS_MSG = 1;
+
+    /**
      * This thread must be moved to the system background cpuset.
      * If that doesn't happen, it's probably going to draw a lot of power.
      * However, this has to happen after the first updateOomAdjLocked, because
@@ -53,13 +57,22 @@
      */
     final ServiceThread mCompactionThread;
 
-    static final int COMPACT_PROCESS_SOME = 1;
-    static final int COMPACT_PROCESS_FULL = 2;
-    static final int COMPACT_PROCESS_MSG = 1;
-    final Handler mCompactionHandler;
+    final private Handler mCompactionHandler;
 
-    final ActivityManagerService mAm;
-    final ActivityManagerConstants mConstants;
+    final private ActivityManagerService mAm;
+    final private ActivityManagerConstants mConstants;
+
+    final private String COMPACT_ACTION_FILE = "file";
+    final private String COMPACT_ACTION_ANON = "anon";
+    final private String COMPACT_ACTION_FULL = "full";
+
+    final private String compactActionSome;
+    final private String compactActionFull;
+
+    final private long throttleSomeSome;
+    final private long throttleSomeFull;
+    final private long throttleFullSome;
+    final private long throttleFullFull;
 
     public AppCompactor(ActivityManagerService am) {
         mAm = am;
@@ -69,6 +82,41 @@
                 THREAD_PRIORITY_FOREGROUND, true);
         mCompactionThread.start();
         mCompactionHandler = new MemCompactionHandler(this);
+
+        switch(mConstants.COMPACT_ACTION_1) {
+            case 1:
+                compactActionSome = COMPACT_ACTION_FILE;
+                break;
+            case 2:
+                compactActionSome = COMPACT_ACTION_ANON;
+                break;
+            case 3:
+                compactActionSome = COMPACT_ACTION_FULL;
+                break;
+            default:
+                compactActionSome = COMPACT_ACTION_FILE;
+                break;
+        }
+
+        switch(mConstants.COMPACT_ACTION_2) {
+            case 1:
+                compactActionFull = COMPACT_ACTION_FILE;
+                break;
+            case 2:
+                compactActionFull = COMPACT_ACTION_ANON;
+                break;
+            case 3:
+                compactActionFull = COMPACT_ACTION_FULL;
+                break;
+            default:
+                compactActionFull = COMPACT_ACTION_FULL;
+                break;
+        }
+
+        throttleSomeSome = mConstants.COMPACT_THROTTLE_1;
+        throttleSomeFull = mConstants.COMPACT_THROTTLE_2;
+        throttleFullSome = mConstants.COMPACT_THROTTLE_3;
+        throttleFullFull = mConstants.COMPACT_THROTTLE_4;
     }
 
     // Must be called while holding AMS lock.
@@ -128,18 +176,16 @@
                 }
 
                 // basic throttling
+                // use the ActivityManagerConstants knobs to determine whether current/prevous
+                // compaction combo should be throtted or not
                 if (pendingAction == COMPACT_PROCESS_SOME) {
-                    // if we're compacting some, then compact if >10s after last full
-                    // or >5s after last some
-                    if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 5000)) ||
-                        (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000))) {
+                    if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleSomeSome)) ||
+                        (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleSomeFull))) {
                         return;
                     }
                 } else {
-                    // if we're compacting full, then compact if >10s after last full
-                    // or >.5s after last some
-                    if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 500)) ||
-                        (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000))) {
+                    if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleFullSome)) ||
+                        (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleFullFull))) {
                         return;
                     }
                 }
@@ -151,9 +197,9 @@
                     long[] rssBefore = Process.getRss(pid);
                     FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
                     if (pendingAction == COMPACT_PROCESS_SOME) {
-                        action = "file";
+                        action = compactActionSome;
                     } else {
-                        action = "all";
+                        action = compactActionFull;
                     }
                     fos.write(action.getBytes());
                     fos.close();
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 6596d27..9d9b1cf 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -16,8 +16,9 @@
 
 package com.android.server.connectivity;
 
-import android.net.InterfaceConfiguration;
 import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
@@ -59,6 +60,7 @@
         NetworkInfo.State.SUSPENDED,
     };
 
+    private final INetd mNetd;
     private final INetworkManagementService mNMService;
 
     // The network we're running on, and its type.
@@ -76,7 +78,8 @@
     private String mIface;
     private State mState = State.IDLE;
 
-    public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) {
+    public Nat464Xlat(NetworkAgentInfo nai, INetd netd, INetworkManagementService nmService) {
+        mNetd = netd;
         mNMService = nmService;
         mNetwork = nai;
     }
@@ -140,7 +143,7 @@
             return;
         }
         try {
-            mNMService.startClatd(baseIface);
+            mNetd.clatdStart(baseIface);
         } catch(RemoteException|IllegalStateException e) {
             Slog.e(TAG, "Error starting clatd on " + baseIface, e);
         }
@@ -162,7 +165,7 @@
      */
     private void enterStoppingState() {
         try {
-            mNMService.stopClatd(mBaseIface);
+            mNetd.clatdStop(mBaseIface);
         } catch(RemoteException|IllegalStateException e) {
             Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
         }
@@ -204,7 +207,7 @@
             Slog.e(TAG, "startClat: Can't start clat on null interface");
             return;
         }
-        // TODO: should we only do this if mNMService.startClatd() succeeds?
+        // TODO: should we only do this if mNetd.clatdStart() succeeds?
         Slog.i(TAG, "Starting clatd on " + baseIface);
         enterStartingState(baseIface);
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 54c89aa..9ea73fb 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import android.content.Context;
+import android.net.INetd;
 import android.net.INetworkMonitor;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -239,12 +240,15 @@
     private static final String TAG = ConnectivityService.class.getSimpleName();
     private static final boolean VDBG = false;
     private final ConnectivityService mConnService;
+    private final INetd mNetd;
+    private final INetworkManagementService mNMS;
     private final Context mContext;
     private final Handler mHandler;
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
-            NetworkMisc misc, ConnectivityService connService) {
+            NetworkMisc misc, ConnectivityService connService, INetd netd,
+            INetworkManagementService nms) {
         this.messenger = messenger;
         asyncChannel = ac;
         network = net;
@@ -253,6 +257,8 @@
         networkCapabilities = nc;
         currentScore = score;
         mConnService = connService;
+        mNetd = netd;
+        mNMS = nms;
         mContext = context;
         mHandler = handler;
         networkMisc = misc;
@@ -587,18 +593,18 @@
 
     public void updateClat(INetworkManagementService netd) {
         if (Nat464Xlat.requiresClat(this)) {
-            maybeStartClat(netd);
+            maybeStartClat();
         } else {
             maybeStopClat();
         }
     }
 
     /** Ensure clat has started for this network. */
-    public void maybeStartClat(INetworkManagementService netd) {
+    public void maybeStartClat() {
         if (clatd != null && clatd.isStarted()) {
             return;
         }
-        clatd = new Nat464Xlat(netd, this);
+        clatd = new Nat464Xlat(this, mNetd, mNMS);
         clatd.start();
     }
 
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index bfcc629..aaf9cbc 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -141,10 +141,7 @@
             final long runtime = nowUptime - startUptime;
 
 
-            if (startUptime == 0) {
-                wtf("Job " + jobId + " start uptime not found: "
-                        + " params=" + jobParametersToString(params));
-            } else if (runtime > 60 * 1000) {
+            if (runtime > 60 * 1000) {
                 // WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
                 // (1 minute threshold.)
                 // Also don't wtf when it's not ready to sync.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2340b77..cb3f91b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -792,6 +792,16 @@
         }
     }
 
+    private void setVirtualDisplayStateInternal(IBinder appToken, boolean isOn) {
+        synchronized (mSyncRoot) {
+            if (mVirtualDisplayAdapter == null) {
+                return;
+            }
+
+            mVirtualDisplayAdapter.setVirtualDisplayStateLocked(appToken, isOn);
+        }
+    }
+
     private void registerDefaultDisplayAdapters() {
         // Register default display adapters.
         synchronized (mSyncRoot) {
@@ -1930,6 +1940,16 @@
         }
 
         @Override // Binder call
+        public void setVirtualDisplayState(IVirtualDisplayCallback callback, boolean isOn) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                setVirtualDisplayStateInternal(callback.asBinder(), isOn);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 5aa585f..1ca8dd3 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -147,6 +147,13 @@
         return device;
     }
 
+    void setVirtualDisplayStateLocked(IBinder appToken, boolean isOn) {
+        VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
+        if (device != null) {
+            device.setDisplayState(isOn);
+        }
+    }
+
     /**
      * Returns the next unique index for the uniqueIdPrefix
      */
@@ -206,6 +213,7 @@
         private int mPendingChanges;
         private int mUniqueIndex;
         private Display.Mode mMode;
+        private boolean mIsDisplayOn;
 
         public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
                 int ownerUid, String ownerPackageName,
@@ -226,6 +234,7 @@
             mDisplayState = Display.STATE_UNKNOWN;
             mPendingChanges |= PENDING_SURFACE_CHANGE;
             mUniqueIndex = uniqueIndex;
+            mIsDisplayOn = surface != null;
         }
 
         @Override
@@ -304,6 +313,14 @@
             }
         }
 
+        void setDisplayState(boolean isOn) {
+            if (mIsDisplayOn != isOn) {
+                mIsDisplayOn = isOn;
+                mInfo = null;
+                sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
+            }
+        }
+
         public void stopLocked() {
             setSurfaceLocked(null);
             mStopped = true;
@@ -375,7 +392,9 @@
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
                         DisplayDeviceInfo.TOUCH_NONE : DisplayDeviceInfo.TOUCH_VIRTUAL;
-                mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
+
+                mInfo.state = mIsDisplayOn ? Display.STATE_ON : Display.STATE_OFF;
+
                 mInfo.ownerUid = mOwnerUid;
                 mInfo.ownerPackageName = mOwnerPackageName;
             }
diff --git a/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java
index 18d328d..137833c 100644
--- a/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java
@@ -51,6 +51,13 @@
         }
         switch (cmd.getOpcode()) {
             case Constants.MESSAGE_FEATURE_ABORT:
+                if ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_INITIATE_ARC) {
+                    audioSystem().setArcStatus(false);
+                    finish();
+                    return true;
+                } else {
+                    return false;
+                }
             case Constants.MESSAGE_REPORT_ARC_TERMINATED:
                 audioSystem().setArcStatus(false);
                 finish();
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index ff029c1..297a418 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -17,6 +17,7 @@
 package com.android.server.hdmi;
 
 import android.annotation.IntDef;
+import android.annotation.StringDef;
 import android.hardware.hdmi.HdmiDeviceInfo;
 
 import java.lang.annotation.Retention;
@@ -222,6 +223,15 @@
     static final int AUDIO_CODEC_WMAPRO = 0xE; // Support WMA-Pro
     static final int AUDIO_CODEC_MAX = 0xF;
 
+    @StringDef({
+        AUDIO_DEVICE_ARC_IN,
+        AUDIO_DEVICE_SPDIF,
+    })
+    public @interface AudioDevice {}
+
+    static final String AUDIO_DEVICE_ARC_IN = "ARC_IN";
+    static final String AUDIO_DEVICE_SPDIF = "SPDIF";
+
     // Bit mask used to get the routing path of the top level device.
     // When &'d with the path 1.2.2.0 (0x1220), for instance, gives 1.0.0.0.
     static final int ROUTING_PATH_TOP_MASK = 0xF000;
diff --git a/services/core/java/com/android/server/hdmi/DetectTvSystemAudioModeSupportAction.java b/services/core/java/com/android/server/hdmi/DetectTvSystemAudioModeSupportAction.java
index 0495ff8..7187319 100644
--- a/services/core/java/com/android/server/hdmi/DetectTvSystemAudioModeSupportAction.java
+++ b/services/core/java/com/android/server/hdmi/DetectTvSystemAudioModeSupportAction.java
@@ -50,8 +50,10 @@
             if (mState != STATE_WAITING_FOR_FEATURE_ABORT) {
                 return false;
             }
-            finishAction(false);
-            return true;
+            if ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE) {
+                finishAction(false);
+                return true;
+            }
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index ba21b78..df0dc77 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.util.Slog;
 
@@ -51,6 +52,10 @@
     private static final int STATE_WAITING_FOR_OSD_NAME = 3;
     // State in which the action is waiting for gathering vendor id of non-local devices.
     private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
+    // State in which the action is waiting for devices to be ready.
+    private static final int STATE_WAITING_FOR_DEVICES = 5;
+    // State in which the action is waiting for gathering power status of non-local devices.
+    private static final int STATE_WAITING_FOR_POWER = 6;
 
     /**
      * Interface used to report result of device discovery.
@@ -72,6 +77,7 @@
         private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
         private int mPortId = Constants.INVALID_PORT_ID;
         private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
+        private int mPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
         private String mDisplayName = "";
         private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE;
 
@@ -81,7 +87,7 @@
 
         private HdmiDeviceInfo toHdmiDeviceInfo() {
             return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
-                    mVendorId, mDisplayName);
+                    mVendorId, mDisplayName, mPowerStatus);
         }
     }
 
@@ -89,7 +95,20 @@
     private final DeviceDiscoveryCallback mCallback;
     private int mProcessedDeviceCount = 0;
     private int mTimeoutRetry = 0;
-    private boolean mIsTvDevice = source().mService.isTvDevice();
+    private boolean mIsTvDevice = localDevice().mService.isTvDevice();
+    private final int mDelayPeriod;
+
+    /**
+     * Constructor.
+     *
+     * @param source an instance of {@link HdmiCecLocalDevice}.
+     * @param delay delay action for this period between query Physical Address and polling
+     */
+    DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback, int delay) {
+        super(source);
+        mCallback = Preconditions.checkNotNull(callback);
+        mDelayPeriod = delay;
+    }
 
     /**
      * Constructor.
@@ -97,8 +116,7 @@
      * @param source an instance of {@link HdmiCecLocalDevice}.
      */
     DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
-        super(source);
-        mCallback = Preconditions.checkNotNull(callback);
+        this(source, callback, 0);
     }
 
     @Override
@@ -117,7 +135,11 @@
 
                 Slog.v(TAG, "Device detected: " + ackedAddress);
                 allocateDevices(ackedAddress);
-                startPhysicalAddressStage();
+                if (mDelayPeriod > 0) {
+                    startToDelayAction();
+                } else {
+                    startPhysicalAddressStage();
+                }
             }
         }, Constants.POLL_ITERATION_REVERSE_ORDER
             | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
@@ -131,6 +153,13 @@
         }
     }
 
+    private void startToDelayAction() {
+        Slog.v(TAG, "Waiting for connected devices to be ready");
+        mState = STATE_WAITING_FOR_DEVICES;
+
+        checkAndProceedStage();
+    }
+
     private void startPhysicalAddressStage() {
         Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
         mProcessedDeviceCount = 0;
@@ -159,6 +188,11 @@
         addTimer(mState, HdmiConfig.TIMEOUT_MS);
     }
 
+    private void delayActionWithTimePeriod(int timeDelay) {
+        mActionTimer.clearTimerMessage();
+        addTimer(mState, timeDelay);
+    }
+
     private void startOsdNameStage() {
         Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
         mProcessedDeviceCount = 0;
@@ -207,6 +241,29 @@
         addTimer(mState, HdmiConfig.TIMEOUT_MS);
     }
 
+    private void startPowerStatusStage() {
+        Slog.v(TAG, "Start [Power Status Stage]:" + mDevices.size());
+        mProcessedDeviceCount = 0;
+        mState = STATE_WAITING_FOR_POWER;
+
+        checkAndProceedStage();
+    }
+
+    private void queryPowerStatus(int address) {
+        if (!verifyValidLogicalAddress(address)) {
+            checkAndProceedStage();
+            return;
+        }
+
+        mActionTimer.clearTimerMessage();
+
+        if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_POWER_STATUS)) {
+            return;
+        }
+        sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address));
+        addTimer(mState, HdmiConfig.TIMEOUT_MS);
+    }
+
     private boolean mayProcessMessageIfCached(int address, int opcode) {
         HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
         if (message != null) {
@@ -245,6 +302,16 @@
                     return true;
                 }
                 return false;
+            case STATE_WAITING_FOR_POWER:
+                if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
+                    handleReportPowerStatus(cmd);
+                    return true;
+                } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)
+                        && ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_REPORT_POWER_STATUS)) {
+                    handleReportPowerStatus(cmd);
+                    return true;
+                }
+                return false;
             case STATE_WAITING_FOR_DEVICE_POLLING:
                 // Fall through.
             default:
@@ -266,6 +333,7 @@
         current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
         current.mPortId = getPortId(current.mPhysicalAddress);
         current.mDeviceType = params[2] & 0xFF;
+        current.mDisplayName = HdmiUtils.getDefaultDeviceName(current.mDeviceType);
 
         // TODO(amyjojo): check if non-TV device needs to update cec switch info.
         // This is to manager CEC device separately in case they don't have address.
@@ -329,6 +397,26 @@
         checkAndProceedStage();
     }
 
+    private void handleReportPowerStatus(HdmiCecMessage cmd) {
+        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+        if (current.mLogicalAddress != cmd.getSource()) {
+            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:"
+                    + cmd.getSource());
+            return;
+        }
+
+        if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
+            byte[] params = cmd.getParams();
+            int powerStatus = params[0] & 0xFF;
+            current.mPowerStatus = powerStatus;
+        }
+
+        increaseProcessedDeviceCount();
+        checkAndProceedStage();
+    }
+
     private void increaseProcessedDeviceCount() {
         mProcessedDeviceCount++;
         mTimeoutRetry = 0;
@@ -372,6 +460,9 @@
                     startVendorIdStage();
                     return;
                 case STATE_WAITING_FOR_VENDOR_ID:
+                    startPowerStatusStage();
+                    return;
+                case STATE_WAITING_FOR_POWER:
                     wrapUpAndFinish();
                     return;
                 default:
@@ -385,6 +476,9 @@
     private void sendQueryCommand() {
         int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
         switch (mState) {
+            case STATE_WAITING_FOR_DEVICES:
+                delayActionWithTimePeriod(mDelayPeriod);
+                return;
             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
                 queryPhysicalAddress(address);
                 return;
@@ -394,6 +488,9 @@
             case STATE_WAITING_FOR_VENDOR_ID:
                 queryVendorId(address);
                 return;
+            case STATE_WAITING_FOR_POWER:
+                queryPowerStatus(address);
+                return;
             default:
                 return;
         }
@@ -405,13 +502,24 @@
             return;
         }
 
+        if (mState == STATE_WAITING_FOR_DEVICES) {
+            startPhysicalAddressStage();
+            return;
+        }
         if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
             sendQueryCommand();
             return;
         }
         mTimeoutRetry = 0;
         Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
-        removeDevice(mProcessedDeviceCount);
+        if (mState != STATE_WAITING_FOR_POWER && mState != STATE_WAITING_FOR_OSD_NAME) {
+            // We don't need to remove the device info if the power status is unknown.
+            // Some device does not have preferred OSD name and does not respond to Give OSD name.
+            // Like LG TV. We can give it default device name and not remove it.
+            removeDevice(mProcessedDeviceCount);
+        } else {
+            increaseProcessedDeviceCount();
+        }
         checkAndProceedStage();
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index e777ce8..86be585 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -77,7 +77,7 @@
 
     private static final int NUM_LOGICAL_ADDRESS = 16;
 
-    private static final int MAX_CEC_MESSAGE_HISTORY = 20;
+    private static final int MAX_CEC_MESSAGE_HISTORY = 200;
 
     // Predicate for whether the given logical address is remote device's one or not.
     private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
@@ -682,7 +682,7 @@
 
     void dump(final IndentingPrintWriter pw) {
         for (int i = 0; i < mLocalDevices.size(); ++i) {
-            pw.println("HdmiCecLocalDevice #" + i + ":");
+            pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":");
             pw.increaseIndent();
             mLocalDevices.valueAt(i).dump(pw);
             pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 32dc0261..eb4e662 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -66,6 +66,7 @@
     protected final int mDeviceType;
     protected int mAddress;
     protected int mPreferredAddress;
+    @GuardedBy("mLock")
     protected HdmiDeviceInfo mDeviceInfo;
     protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
     protected int mLastKeyRepeatCount = 0;
@@ -717,16 +718,18 @@
         return mDeviceType;
     }
 
-    @ServiceThreadOnly
+    @GuardedBy("mLock")
     HdmiDeviceInfo getDeviceInfo() {
-        assertRunOnServiceThread();
-        return mDeviceInfo;
+        synchronized (mLock) {
+            return mDeviceInfo;
+        }
     }
 
-    @ServiceThreadOnly
+    @GuardedBy("mLock")
     void setDeviceInfo(HdmiDeviceInfo info) {
-        assertRunOnServiceThread();
-        mDeviceInfo = info;
+        synchronized (mLock) {
+            mDeviceInfo = info;
+        }
     }
 
     // Returns true if the logical address is same as the argument.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 0e4e334..71075f3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -26,22 +26,30 @@
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.tv.TvContract;
 import android.os.SystemProperties;
+import android.provider.Settings.Global;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.Constants.AudioCodec;
 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 
 import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.stream.Collectors;
+
 
 /**
  * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
@@ -77,23 +85,24 @@
     // processing.
     private final HashMap<Integer, String> mTvInputs = new HashMap<>();
 
+    // Copy of mDeviceInfos to guarantee thread-safety.
+    @GuardedBy("mLock")
+    private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
+
     // Map-like container of all cec devices.
     // device id is used as key of container.
     private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
 
     protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mSystemAudioControlFeatureEnabled = true;
-        // TODO(amyjojo) make System Audio Control controllable by users
-        /*mSystemAudioControlFeatureEnabled =
-        mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);*/
-        // TODO(amyjojo): make the map ro property.
-        mTvInputs.put(Constants.CEC_SWITCH_HDMI1,
-                "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
-        mTvInputs.put(Constants.CEC_SWITCH_HDMI2,
-                "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
-        mTvInputs.put(Constants.CEC_SWITCH_HDMI3,
-                "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
+        mRoutingControlFeatureEnabled =
+            mService.readBooleanSetting(Global.HDMI_CEC_SWITCH_ENABLED, false);
+        mSystemAudioControlFeatureEnabled =
+            mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
+        // TODO(amyjojo): Maintain a portId to TvinputId map.
+        mTvInputs.put(2, "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
+        mTvInputs.put(4, "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
+        mTvInputs.put(1, "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
     }
 
     /**
@@ -167,6 +176,7 @@
             removeDeviceInfo(deviceInfo.getId());
         }
         mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
+        updateSafeDeviceInfoList();
         return oldDeviceInfo;
     }
 
@@ -184,6 +194,7 @@
         if (deviceInfo != null) {
             mDeviceInfos.remove(id);
         }
+        updateSafeDeviceInfoList();
         return deviceInfo;
     }
 
@@ -200,6 +211,24 @@
         return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
     }
 
+    @ServiceThreadOnly
+    private void updateSafeDeviceInfoList() {
+        assertRunOnServiceThread();
+        List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
+        synchronized (mLock) {
+            mSafeAllDeviceInfos = copiedDevices;
+        }
+    }
+
+    @GuardedBy("mLock")
+    List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
+        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
+        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
+            infoList.add(info);
+        }
+        return infoList;
+    }
+
     private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
         mService.invokeDeviceEventListeners(info, status);
     }
@@ -240,6 +269,10 @@
     @ServiceThreadOnly
     protected void onAddressAllocated(int logicalAddress, int reason) {
         assertRunOnServiceThread();
+        if (reason == mService.INITIATED_BY_ENABLE_CEC) {
+            mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
+                    getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
+        }
         mService.sendCecCommand(
                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
                         mAddress, mService.getPhysicalAddress(), mDeviceType));
@@ -259,7 +292,10 @@
 
     @Override
     protected int findKeyReceiverAddress() {
-        return Constants.ADDR_TV;
+        if (getActiveSource().isValid()) {
+            return getActiveSource().logicalAddress;
+        }
+        return Constants.ADDR_INVALID;
     }
 
     @VisibleForTesting
@@ -267,7 +303,7 @@
             int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
         if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
                 || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
-                && lastSystemAudioControlStatus)) {
+                && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
             addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
         }
     }
@@ -400,8 +436,11 @@
     @ServiceThreadOnly
     protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
-
-        reportAudioStatus(message);
+        if (isSystemAudioControlFeatureEnabled()) {
+            reportAudioStatus(message.getSource());
+        } else {
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+        }
         return true;
     }
 
@@ -478,14 +517,91 @@
 
     private byte[] getSupportedShortAudioDescriptors(
             AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) {
-        // TODO(b/80297701) implement
-        return new byte[] {};
+        ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
+        for (@AudioCodec int audioFormatCode : audioFormatCodes) {
+            byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode);
+            if (sad != null) {
+                if (sad.length == 3) {
+
+                    sads.add(sad);
+                } else {
+                    HdmiLogger.warning(
+                            "Dropping Short Audio Descriptor with length %d for requested codec %x",
+                            sad.length, audioFormatCode);
+                }
+            }
+        }
+        // Short Audio Descriptors are always 3 bytes long.
+        byte[] bytes = new byte[sads.size() * 3];
+        int index = 0;
+        for (byte[] sad : sads) {
+            System.arraycopy(sad, 0, bytes, index, 3);
+            index += 3;
+        }
+        return bytes;
+    }
+
+    /**
+     * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
+     * audioFormatCode is not supported.
+     */
+    @Nullable
+    private byte[] getSupportedShortAudioDescriptor(
+            AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) {
+        switch (audioFormatCode) {
+            case Constants.AUDIO_CODEC_NONE: {
+                return null;
+            }
+            case Constants.AUDIO_CODEC_LPCM: {
+                return getLpcmShortAudioDescriptor(deviceInfo);
+            }
+            // TODO(b/80297701): implement the rest of the codecs
+            case Constants.AUDIO_CODEC_DD:
+            case Constants.AUDIO_CODEC_MPEG1:
+            case Constants.AUDIO_CODEC_MP3:
+            case Constants.AUDIO_CODEC_MPEG2:
+            case Constants.AUDIO_CODEC_AAC:
+            case Constants.AUDIO_CODEC_DTS:
+            case Constants.AUDIO_CODEC_ATRAC:
+            case Constants.AUDIO_CODEC_ONEBITAUDIO:
+            case Constants.AUDIO_CODEC_DDP:
+            case Constants.AUDIO_CODEC_DTSHD:
+            case Constants.AUDIO_CODEC_TRUEHD:
+            case Constants.AUDIO_CODEC_DST:
+            case Constants.AUDIO_CODEC_WMAPRO:
+            default: {
+                return null;
+            }
+        }
+    }
+
+    @Nullable
+    private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) {
+        // TODO(b/80297701): implement
+        return null;
     }
 
     @Nullable
     private AudioDeviceInfo getSystemAudioDeviceInfo() {
-        // TODO(b/80297701) implement
-        // Get the audio device used for system audio mode.
+        AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class);
+        if (audioManager == null) {
+            HdmiLogger.error(
+                    "Error getting system audio device because AudioManager not available.");
+            return null;
+        }
+        AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        HdmiLogger.debug("Found %d audio input devices", devices.length);
+        for (AudioDeviceInfo device : devices) {
+            HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort());
+            HdmiLogger.debug("Supported encodings are %s",
+                    Arrays.stream(device.getEncodings()).mapToObj(
+                            AudioFormat::toLogFriendlyEncoding
+                    ).collect(Collectors.joining(", ")));
+            // TODO(b/80297701) use the actual device type that system audio mode is connected to.
+            if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) {
+                return device;
+            }
+        }
         return null;
     }
 
@@ -583,17 +699,20 @@
             .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
     }
 
-    private void reportAudioStatus(HdmiCecMessage message) {
+    void reportAudioStatus(int source) {
         assertRunOnServiceThread();
 
         int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
         boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
         int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC);
         int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+        HdmiLogger.debug("Reporting volume %i (%i-%i) as CEC volume %i", volume,
+                minVolume, maxVolume, scaledVolume);
 
         mService.sendCecCommand(
                 HdmiCecMessageBuilder.buildReportAudioStatus(
-                        mAddress, message.getSource(), scaledVolume, mute));
+                        mAddress, source, scaledVolume, mute));
     }
 
     /**
@@ -633,7 +752,7 @@
      */
     private void setSystemAudioMode(boolean newSystemAudioMode) {
         int targetPhysicalAddress = getActiveSource().physicalAddress;
-        int port = getLocalPortFromPhysicalAddress(targetPhysicalAddress);
+        int port = mService.pathToPortId(targetPhysicalAddress);
         if (newSystemAudioMode && port >= 0) {
             switchToAudioInput();
         }
@@ -641,16 +760,18 @@
         // PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE is false when device never needs to be muted.
         boolean currentMuteStatus =
                 mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
-        if (SystemProperties.getBoolean(
-                Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
-                && currentMuteStatus == newSystemAudioMode) {
-            mService.getAudioManager()
-                    .adjustStreamVolume(
-                            AudioManager.STREAM_MUSIC,
-                            newSystemAudioMode
-                                    ? AudioManager.ADJUST_UNMUTE
-                                    : AudioManager.ADJUST_MUTE,
-                                    0);
+        if (currentMuteStatus == newSystemAudioMode) {
+            if (SystemProperties.getBoolean(
+                    Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
+                            || newSystemAudioMode) {
+                mService.getAudioManager()
+                        .adjustStreamVolume(
+                                AudioManager.STREAM_MUSIC,
+                                newSystemAudioMode
+                                        ? AudioManager.ADJUST_UNMUTE
+                                        : AudioManager.ADJUST_MUTE,
+                                0);
+            }
         }
         updateAudioManagerForSystemAudio(newSystemAudioMode);
         synchronized (mLock) {
@@ -688,6 +809,13 @@
         HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
     }
 
+    void onSystemAduioControlFeatureSupportChanged(boolean enabled) {
+        setSystemAudioControlFeatureEnabled(enabled);
+        if (enabled) {
+            addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
+        }
+    }
+
     @ServiceThreadOnly
     void setSystemAudioControlFeatureEnabled(boolean enabled) {
         assertRunOnServiceThread();
@@ -697,6 +825,14 @@
     }
 
     @ServiceThreadOnly
+    void setRoutingControlFeatureEnables(boolean enabled) {
+        assertRunOnServiceThread();
+        synchronized (mLock) {
+            mRoutingControlFeatureEnabled = enabled;
+        }
+    }
+
+    @ServiceThreadOnly
     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         // TODO: validate port ID
@@ -817,7 +953,7 @@
 
     @Override
     protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
-        int port = getLocalPortFromPhysicalAddress(physicalAddress);
+        int port = mService.pathToPortId(physicalAddress);
         if (isSystemAudioActivated() && port < 0) {
             // If system audio mode is on and the new active source is not under the current device,
             // Will switch to ARC input.
@@ -831,6 +967,10 @@
     }
 
     protected void routeToInputFromPortId(int portId) {
+        if (!isRoutingControlFeatureEnabled()) {
+            HdmiLogger.debug("Routing Control Feature is not enabled.");
+            return;
+        }
         if (mArcIntentUsed) {
             routeToTvInputFromPortId(portId);
         } else {
@@ -885,7 +1025,7 @@
 
     @Override
     protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
-        int port = getLocalPortFromPhysicalAddress(physicalAddress);
+        int port = mService.pathToPortId(physicalAddress);
         // Routing change or information sent from switches under the current device can be ignored.
         if (port > 0) {
             return;
@@ -918,8 +1058,7 @@
             return;
         }
 
-        int routingInformationPath =
-                getActivePathOnSwitchFromActivePortId(getRoutingPort());
+        int routingInformationPath = mService.portIdToPath(getRoutingPort());
         // If current device is already the leaf of the whole HDMI system, will do nothing.
         if (routingInformationPath == mService.getPhysicalAddress()) {
             HdmiLogger.debug("Current device can't assign valid physical address"
@@ -954,6 +1093,10 @@
     @ServiceThreadOnly
     private void launchDeviceDiscovery() {
         assertRunOnServiceThread();
+        if (hasAction(DeviceDiscoveryAction.class)) {
+            Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
+            removeAction(DeviceDiscoveryAction.class);
+        }
         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
                 new DeviceDiscoveryCallback() {
                     @Override
@@ -977,5 +1120,25 @@
             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
         }
         mDeviceInfos.clear();
+        updateSafeDeviceInfoList();
     }
+
+    @Override
+    protected void dump(IndentingPrintWriter pw) {
+        pw.println("HdmiCecLocalDeviceAudioSystem:");
+        pw.increaseIndent();
+        pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
+        pw.println("isRoutingFeatureEnabled " + isRoutingControlFeatureEnabled());
+        pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
+        pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport);
+        pw.println("mArcEstablished: " + mArcEstablished);
+        pw.println("mArcIntentUsed: " + mArcIntentUsed);
+        pw.println("mRoutingPort: " + getRoutingPort());
+        pw.println("mLocalActivePort: " + getLocalActivePort());
+        HdmiUtils.dumpMap(pw, "mTvInputs:", mTvInputs);
+        HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos);
+        pw.decreaseIndent();
+        super.dump(pw);
+    }
+
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 07db971..32288de 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -77,6 +77,10 @@
     @ServiceThreadOnly
     protected void onAddressAllocated(int logicalAddress, int reason) {
         assertRunOnServiceThread();
+        if (reason == mService.INITIATED_BY_ENABLE_CEC) {
+            mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
+                    getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
+        }
         mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
                 mAddress, mService.getPhysicalAddress(), mDeviceType));
         mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index a95f7f1..ae008b4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -66,6 +66,10 @@
     @LocalActivePort
     protected int mLocalActivePort = Constants.CEC_SWITCH_HOME;
 
+    // Whether the Routing Coutrol feature is enabled or not. False by default.
+    @GuardedBy("mLock")
+    protected boolean mRoutingControlFeatureEnabled;
+
     protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
         super(service, deviceType);
     }
@@ -123,7 +127,9 @@
         }
         setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
         updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
-        switchInputOnReceivingNewActivePath(physicalAddress);
+        if (isRoutingControlFeatureEnabled()) {
+            switchInputOnReceivingNewActivePath(physicalAddress);
+        }
         return true;
     }
 
@@ -153,6 +159,10 @@
     @ServiceThreadOnly
     protected boolean handleRoutingChange(HdmiCecMessage message) {
         assertRunOnServiceThread();
+        if (!isRoutingControlFeatureEnabled()) {
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+            return true;
+        }
         int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
         // if the current device is a pure playback device
         if (!mIsSwitchDevice
@@ -168,6 +178,10 @@
     @ServiceThreadOnly
     protected boolean handleRoutingInformation(HdmiCecMessage message) {
         assertRunOnServiceThread();
+        if (!isRoutingControlFeatureEnabled()) {
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+            return true;
+        }
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
         // if the current device is a pure playback device
         if (!mIsSwitchDevice
@@ -204,7 +218,7 @@
     // This method should only be called when the device can be the active source.
     protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
         mService.setAndBroadcastActiveSource(
-                message, physicalAddress, getDeviceInfo().getDeviceType());
+                physicalAddress, getDeviceInfo().getDeviceType(), message.getSource());
     }
 
     @ServiceThreadOnly
@@ -279,6 +293,12 @@
         }
     }
 
+    boolean isRoutingControlFeatureEnabled() {
+        synchronized (mLock) {
+            return mRoutingControlFeatureEnabled;
+        }
+    }
+
     // Check if the device is trying to switch to the same input that is active right now.
     // This can help avoid redundant port switching.
     protected boolean isSwitchingToTheSameInput(@LocalActivePort int activePort) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index c005615..f8b3962 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -17,6 +17,7 @@
 package com.android.server.hdmi;
 
 import android.annotation.Nullable;
+
 import libcore.util.EmptyArray;
 
 import java.util.Arrays;
@@ -111,12 +112,11 @@
     @Override
     public String toString() {
         StringBuffer s = new StringBuffer();
-        s.append(String.format("<%s> src: %d, dst: %d",
-                opcodeToString(mOpcode), mSource, mDestination));
+        s.append(String.format("<%s> %X%X:%02X",
+                opcodeToString(mOpcode), mSource, mDestination, mOpcode));
         if (mParams.length > 0) {
-            s.append(", params:");
             for (byte data : mParams) {
-                s.append(String.format(" %02X", data));
+                s.append(String.format(":%02X", data));
             }
         }
         return s.toString();
@@ -133,7 +133,7 @@
             case Constants.MESSAGE_TUNER_STEP_DECREMENT:
                 return "Tuner Step Decrement";
             case Constants.MESSAGE_TUNER_DEVICE_STATUS:
-                return "Tuner Device Staus";
+                return "Tuner Device Status";
             case Constants.MESSAGE_GIVE_TUNER_DEVICE_STATUS:
                 return "Give Tuner Device Status";
             case Constants.MESSAGE_RECORD_ON:
@@ -207,7 +207,7 @@
             case Constants.MESSAGE_DEVICE_VENDOR_ID:
                 return "Device Vendor Id";
             case Constants.MESSAGE_VENDOR_COMMAND:
-                return "Vendor Commandn";
+                return "Vendor Command";
             case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_DOWN:
                 return "Vendor Remote Button Down";
             case Constants.MESSAGE_VENDOR_REMOTE_BUTTON_UP:
@@ -215,7 +215,7 @@
             case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
                 return "Give Device Vendor Id";
             case Constants.MESSAGE_MENU_REQUEST:
-                return "Menu REquest";
+                return "Menu Request";
             case Constants.MESSAGE_MENU_STATUS:
                 return "Menu Status";
             case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
@@ -247,7 +247,7 @@
             case Constants.MESSAGE_SET_EXTERNAL_TIMER:
                 return "Set External Timer";
             case Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR:
-                return "Repot Short Audio Descriptor";
+                return "Report Short Audio Descriptor";
             case Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR:
                 return "Request Short Audio Descriptor";
             case Constants.MESSAGE_INITIATE_ARC:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 2d6e762..486faf3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -20,12 +20,14 @@
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
 
 import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
+import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
 import static com.android.server.hdmi.Constants.DISABLED;
 import static com.android.server.hdmi.Constants.ENABLED;
 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
 import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
+import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
 
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -184,9 +186,10 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             assertRunOnServiceThread();
+            boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
             switch (intent.getAction()) {
                 case Intent.ACTION_SCREEN_OFF:
-                    if (isPowerOnOrTransient()) {
+                    if (isPowerOnOrTransient() && !isReboot) {
                         onStandby(STANDBY_SCREEN_OFF);
                     }
                     break;
@@ -202,7 +205,7 @@
                     }
                     break;
                 case Intent.ACTION_SHUTDOWN:
-                    if (isPowerOnOrTransient()) {
+                    if (isPowerOnOrTransient() && !isReboot) {
                         onStandby(STANDBY_SHUTDOWN);
                     }
                     break;
@@ -345,6 +348,10 @@
     @Nullable
     private Looper mIoLooper;
 
+    // Thread safe physical address
+    @GuardedBy("mLock")
+    private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
+
     // Last input port before switching to the MHL port. Should switch back to this port
     // when the mobile device sends the request one touch play with off.
     // Gets invalidated if we go to other port/input.
@@ -564,7 +571,8 @@
                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
                 Global.MHL_INPUT_SWITCHING_ENABLED,
-                Global.MHL_POWER_CHARGE_ENABLED
+                Global.MHL_POWER_CHARGE_ENABLED,
+                Global.HDMI_CEC_SWITCH_ENABLED
         };
         for (String s : settings) {
             resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
@@ -605,6 +613,24 @@
                     if (isTvDeviceEnabled()) {
                         tv().setSystemAudioControlFeatureEnabled(enabled);
                     }
+                    if (isAudioSystemDevice()) {
+                        if (audioSystem() == null) {
+                            Slog.e(TAG, "Audio System device has not registered yet."
+                                    + " Can't turn system audio mode on.");
+                            break;
+                        }
+                        audioSystem().onSystemAduioControlFeatureSupportChanged(enabled);
+                    }
+                    break;
+                case Global.HDMI_CEC_SWITCH_ENABLED:
+                    if (isAudioSystemDevice()) {
+                        if (audioSystem() == null) {
+                            Slog.w(TAG, "Switch device has not registered yet."
+                                    + " Can't turn routing on.");
+                            break;
+                        }
+                        audioSystem().setRoutingControlFeatureEnables(enabled);
+                    }
                     break;
                 case Global.MHL_INPUT_SWITCHING_ENABLED:
                     setMhlInputChangeEnabled(enabled);
@@ -734,6 +760,10 @@
         assertRunOnServiceThread();
         HdmiPortInfo[] cecPortInfo = null;
 
+        synchronized (mLock) {
+            mPhysicalAddress = getPhysicalAddress();
+        }
+
         // CEC HAL provides majority of the info while MHL does only MHL support flag for
         // each port. Return empty array if CEC HAL didn't provide the info.
         if (mCecController != null) {
@@ -827,7 +857,10 @@
     int pathToPortId(int path) {
         int mask = 0xF000;
         int finalMask = 0xF000;
-        int physicalAddress = getPhysicalAddress();
+        int physicalAddress;
+        synchronized (mLock) {
+            physicalAddress = mPhysicalAddress;
+        }
         int maskedAddress = physicalAddress;
 
         while (maskedAddress != 0) {
@@ -1135,7 +1168,7 @@
         String displayName = Build.MODEL;
         return new HdmiDeviceInfo(logicalAddress,
                 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
-                getVendorId(), displayName);
+                getVendorId(), displayName, powerStatus);
     }
 
     @ServiceThreadOnly
@@ -1354,6 +1387,33 @@
             HdmiCecLocalDeviceTv tv = tv();
             if (tv == null) {
                 Slog.w(TAG, "Local tv device not available");
+                if (isPlaybackDevice()) {
+                    // if playback device itself is the active source,
+                    // return its own device info.
+                    if (playback() != null && playback().mIsActiveSource) {
+                        return playback().getDeviceInfo();
+                    }
+                    // Otherwise get the active source and look for it from the device list
+                    ActiveSource activeSource = mActiveSource;
+                    // If the active source is not set yet, return null
+                    if (!activeSource.isValid()) {
+                        return null;
+                    }
+                    if (audioSystem() != null) {
+                        HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+                        for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) {
+                            if (info.getLogicalAddress() == activeSource.logicalAddress) {
+                                return info;
+                            }
+                        }
+                    }
+                    // If the device info is not in the list yet, return a device info with minimum
+                    // information from mActiveSource.
+                    return new HdmiDeviceInfo(activeSource.logicalAddress,
+                        activeSource.physicalAddress, pathToPortId(activeSource.physicalAddress),
+                        HdmiUtils.getTypeFromAddress(activeSource.logicalAddress), 0,
+                        HdmiUtils.getDefaultDeviceName(activeSource.logicalAddress));
+                }
                 return null;
             }
             ActiveSource activeSource = tv.getActiveSource();
@@ -1532,6 +1592,14 @@
         }
 
         @Override
+        public int getPhysicalAddress() {
+            enforceAccessPermission();
+            synchronized (mLock) {
+                return mPhysicalAddress;
+            }
+        }
+
+        @Override
         public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
             enforceAccessPermission();
             runOnServiceThread(new Runnable() {
@@ -1588,14 +1656,62 @@
         public List<HdmiDeviceInfo> getDeviceList() {
             enforceAccessPermission();
             HdmiCecLocalDeviceTv tv = tv();
-            synchronized (mLock) {
-                return (tv == null)
+            if (tv != null) {
+                synchronized (mLock) {
+                    return tv.getSafeCecDevicesLocked();
+                }
+            } else {
+                HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+                synchronized (mLock) {
+                    return (audioSystem == null)
                         ? Collections.<HdmiDeviceInfo>emptyList()
-                        : tv.getSafeCecDevicesLocked();
+                        : audioSystem.getSafeCecDevicesLocked();
+                }
             }
         }
 
         @Override
+        public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    Slog.w(TAG, "Device "
+                            + logicalAddress + " power status is " + powerStatus
+                            + " before standby command sent out");
+                    sendCecCommand(HdmiCecMessageBuilder.buildStandby(
+                            getRemoteControlSourceAddress(), logicalAddress));
+                }
+            });
+        }
+
+        @Override
+        public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
+            // TODO(amyjojo): implement the method
+        }
+
+        @Override
+        // TODO(AMYJOJO): add a result callback
+        public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+                            getRemoteControlSourceAddress(), physicalAddress);
+                    if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) {
+                        if (getSwitchDevice() != null) {
+                            getSwitchDevice().handleSetStreamPath(setStreamPath);
+                        } else {
+                            Slog.e(TAG, "Can't get the correct local device to handle routing.");
+                        }
+                    }
+                    sendCecCommand(setStreamPath);
+                }
+            });
+        }
+
+        @Override
         public void setSystemAudioVolume(final int oldIndex, final int newIndex,
                 final int maxIndex) {
             enforceAccessPermission();
@@ -1834,14 +1950,32 @@
                         Slog.w(TAG, "audio system is not in system audio mode");
                         return;
                     }
-                    int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+                    audioSystem().reportAudioStatus(Constants.ADDR_TV);
+                }
+            });
+        }
 
-                    sendCecCommand(HdmiCecMessageBuilder
-                            .buildReportAudioStatus(
-                                    device.getDeviceInfo().getLogicalAddress(),
-                                    Constants.ADDR_TV,
-                                    scaledVolume,
-                                    isMute));
+        @Override
+        public void setSystemAudioModeOnForAudioOnlySource() {
+            enforceAccessPermission();
+            runOnServiceThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (!isAudioSystemDevice()) {
+                        Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
+                        return;
+                    }
+                    if (audioSystem() == null) {
+                        Slog.e(TAG, "Audio System local device is not registered");
+                        return;
+                    }
+                    if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
+                        Slog.e(TAG, "System Audio Mode is not supported.");
+                        return;
+                    }
+                    sendCecCommand(
+                            HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                                    audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
                 }
             });
         }
@@ -1851,30 +1985,54 @@
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
 
-            pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
             pw.println("mProhibitMode: " + mProhibitMode);
-            if (mCecController != null) {
-                pw.println("mCecController: ");
-                pw.increaseIndent();
-                mCecController.dump(pw);
-                pw.decreaseIndent();
-            }
+            pw.println("mPowerStatus: " + mPowerStatus);
+
+            // System settings
+            pw.println("System_settings:");
+            pw.increaseIndent();
+            pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
+            pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
+            pw.decreaseIndent();
 
             pw.println("mMhlController: ");
             pw.increaseIndent();
             mMhlController.dump(pw);
             pw.decreaseIndent();
 
-            pw.println("mPortInfo: ");
-            pw.increaseIndent();
-            for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
-                pw.println("- " + hdmiPortInfo);
+            HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
+            if (mCecController != null) {
+                pw.println("mCecController: ");
+                pw.increaseIndent();
+                mCecController.dump(pw);
+                pw.decreaseIndent();
             }
-            pw.decreaseIndent();
-            pw.println("mPowerStatus: " + mPowerStatus);
         }
     }
 
+    // Get the source address to send out commands to devices connected to the current device
+    // when other services interact with HdmiControlService.
+    private int getRemoteControlSourceAddress() {
+        if (isAudioSystemDevice()) {
+            return audioSystem().getDeviceInfo().getLogicalAddress();
+        } else if (isPlaybackDevice()) {
+            return playback().getDeviceInfo().getLogicalAddress();
+        }
+        return ADDR_UNREGISTERED;
+    }
+
+    // Get the switch device to do CEC routing control
+    @Nullable
+    private HdmiCecLocalDeviceSource getSwitchDevice() {
+        if (isAudioSystemDevice()) {
+            return audioSystem();
+        }
+        if (isPlaybackDevice()) {
+            return playback();
+        }
+        return null;
+    }
+
     @ServiceThreadOnly
     private void oneTouchPlay(final IHdmiControlCallback callback) {
         assertRunOnServiceThread();
@@ -2549,14 +2707,14 @@
     // For example, when receiving broadcast messages, all the device types will call this
     // method but only one of them will be the Active Source.
     protected void setAndBroadcastActiveSource(
-            HdmiCecMessage message, int physicalAddress, int deviceType) {
+            int physicalAddress, int deviceType, int source) {
         // If the device has both playback and audio system logical addresses,
         // playback will claim active source. Otherwise audio system will.
         if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
             HdmiCecLocalDevicePlayback playback = playback();
             playback.setIsActiveSource(true);
             playback.wakeUpIfActiveSource();
-            playback.maySendActiveSource(message.getSource());
+            playback.maySendActiveSource(source);
             setActiveSource(playback.mAddress, physicalAddress);
         }
 
@@ -2567,7 +2725,7 @@
             } else {
                 audioSystem.setIsActiveSource(true);
                 audioSystem.wakeUpIfActiveSource();
-                audioSystem.maySendActiveSource(message.getSource());
+                audioSystem.maySendActiveSource(source);
                 setActiveSource(audioSystem.mAddress, physicalAddress);
             }
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 2a8117f..11e557c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -20,9 +20,13 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.util.IndentingPrintWriter;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+
 
 /**
  * Various utilities to handle HDMI CEC messages.
@@ -317,4 +321,103 @@
                 info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(),
                 info.getVendorId(), info.getDisplayName(), newPowerStatus);
     }
+
+    /**
+     * Dump a {@link SparseArray} to the print writer.
+     *
+     * <p>The dump is formatted:
+     * <pre>
+     *     name:
+     *        key = value
+     *        key = value
+     *        ...
+     * </pre>
+     */
+    static <T> void dumpSparseArray(IndentingPrintWriter pw, String name,
+            SparseArray<T> sparseArray) {
+        printWithTrailingColon(pw, name);
+        pw.increaseIndent();
+        int size = sparseArray.size();
+        for (int i = 0; i < size; i++) {
+            int key = sparseArray.keyAt(i);
+            T value = sparseArray.get(key);
+            pw.printPair(Integer.toString(key), value);
+            pw.println();
+        }
+        pw.decreaseIndent();
+    }
+
+    private static void printWithTrailingColon(IndentingPrintWriter pw, String name) {
+        pw.println(name.endsWith(":") ? name : name.concat(":"));
+    }
+
+    /**
+     * Dump a {@link Map} to the print writer.
+     *
+     * <p>The dump is formatted:
+     * <pre>
+     *     name:
+     *        key = value
+     *        key = value
+     *        ...
+     * </pre>
+     */
+    static <K, V> void dumpMap(IndentingPrintWriter pw, String name, Map<K, V> map) {
+        printWithTrailingColon(pw, name);
+        pw.increaseIndent();
+        for (Map.Entry<K, V> entry: map.entrySet()) {
+            pw.printPair(entry.getKey().toString(), entry.getValue());
+            pw.println();
+        }
+        pw.decreaseIndent();
+    }
+
+    /**
+     * Dump a {@link Map} to the print writer.
+     *
+     * <p>The dump is formatted:
+     * <pre>
+     *     name:
+     *        value
+     *        value
+     *        ...
+     * </pre>
+     */
+    static <T> void dumpIterable(IndentingPrintWriter pw, String name, Iterable<T> values) {
+        printWithTrailingColon(pw, name);
+        pw.increaseIndent();
+        for (T value : values) {
+            pw.println(value);
+        }
+        pw.decreaseIndent();
+    }
+
+    // Device configuration of its supported Codecs and their Short Audio Descriptors.
+    public static class DeviceConfig {
+        /** Name of the device. Should be {@link Constants.AudioDevice}. **/
+        public final String name;
+        /** List of a {@link CodecSad}. **/
+        public final List<CodecSad> supportedCodecs;
+
+        private DeviceConfig(String name, List<CodecSad> supportedCodecs) {
+            this.name = name;
+            this.supportedCodecs = supportedCodecs;
+        }
+    }
+
+    // Short Audio Descriptor of a specific Codec
+    public static class CodecSad {
+        /** Audio Codec. Should be {@link Constants.AudioCodec}. **/
+        public final int audioCodec;
+        /**
+         * Three-byte Short Audio Descriptor. See HDMI Specification 1.4b CEC 13.15.3 and
+         * ANSI-CTA-861-F-FINAL 7.5.2 Audio Data Block for more details.
+         */
+        public final byte[] sad;
+
+        public CodecSad(int audioCodec, byte[] sad) {
+            this.audioCodec = audioCodec;
+            this.sad = sad;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 41bf01f..354d8d1 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -92,6 +92,9 @@
         if (source.mService.audioSystem() != null) {
             source = source.mService.audioSystem();
         }
+        if (source.getLocalActivePort() != Constants.CEC_SWITCH_HOME) {
+            source.switchInputOnReceivingNewActivePath(getSourcePath());
+        }
         source.setRoutingPort(Constants.CEC_SWITCH_HOME);
         source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
     }
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index ac2dbdf..c16d1b4 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -26,7 +26,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
+import android.app.IUidObserver;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
 import android.content.BroadcastReceiver;
@@ -38,12 +41,14 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -69,6 +74,11 @@
  * bucket, it will be eligible to run. When a job's bucket changes, its new quota is immediately
  * applied to it.
  *
+ * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
+ * freely when an app enters the foreground state and are restricted when the app leaves the
+ * foreground state. However, jobs that are started while the app is in the TOP state are not
+ * restricted regardless of the app's state change.
+ *
  * Test: atest com.android.server.job.controllers.QuotaControllerTest
  */
 public final class QuotaController extends StateController {
@@ -97,6 +107,12 @@
             data.put(packageName, obj);
         }
 
+        public void clear() {
+            for (int i = 0; i < mData.size(); ++i) {
+                mData.valueAt(i).clear();
+            }
+        }
+
         /** Removes all the data for the user, if there was any. */
         public void delete(int userId) {
             mData.delete(userId);
@@ -119,6 +135,11 @@
             return null;
         }
 
+        /** @see SparseArray#indexOfKey */
+        public int indexOfKey(int userId) {
+            return mData.indexOfKey(userId);
+        }
+
         /** Returns the userId at the given index. */
         public int keyAt(int index) {
             return mData.keyAt(index);
@@ -294,6 +315,17 @@
     /** Cached calculation results for each app, with the standby buckets as the array indices. */
     private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>();
 
+    /** List of UIDs currently in the foreground. */
+    private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
+
+    /**
+     * List of jobs that started while the UID was in the TOP state. There will be no more than
+     * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
+     * fine.
+     */
+    private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
+
+    private final ActivityManagerInternal mActivityManagerInternal;
     private final AlarmManager mAlarmManager;
     private final ChargingTracker mChargeTracker;
     private final Handler mHandler;
@@ -343,6 +375,29 @@
                 }
             };
 
+    private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override
+        public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+            mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget();
+        }
+
+        @Override
+        public void onUidGone(int uid, boolean disabled) {
+        }
+
+        @Override
+        public void onUidActive(int uid) {
+        }
+
+        @Override
+        public void onUidIdle(int uid, boolean disabled) {
+        }
+
+        @Override
+        public void onUidCachedChanged(int uid, boolean cached) {
+        }
+    };
+
     /**
      * The rolling window size for each standby bucket. Within each window, an app will have 10
      * minutes to run its jobs.
@@ -363,12 +418,15 @@
     private static final int MSG_CLEAN_UP_SESSIONS = 1;
     /** Check if a package is now within its quota. */
     private static final int MSG_CHECK_PACKAGE = 2;
+    /** Process state for a UID has changed. */
+    private static final int MSG_UID_PROCESS_STATE_CHANGED = 3;
 
     public QuotaController(JobSchedulerService service) {
         super(service);
         mHandler = new QcHandler(mContext.getMainLooper());
         mChargeTracker = new ChargingTracker();
         mChargeTracker.startTracking();
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
         // Set up the app standby bucketing tracker
@@ -376,6 +434,14 @@
                 UsageStatsManagerInternal.class);
         usageStats.addAppIdleStateChangeListener(new StandbyTracker());
 
+        try {
+            ActivityManager.getService().registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_PROCSTATE,
+                    ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
+
         onConstantsUpdatedLocked();
     }
 
@@ -399,11 +465,15 @@
         if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
         final int userId = jobStatus.getSourceUserId();
         final String packageName = jobStatus.getSourcePackageName();
+        final int uid = jobStatus.getSourceUid();
         Timer timer = mPkgTimers.get(userId, packageName);
         if (timer == null) {
-            timer = new Timer(userId, packageName);
+            timer = new Timer(uid, userId, packageName);
             mPkgTimers.add(userId, packageName, timer);
         }
+        if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) {
+            mTopStartedJobs.add(jobStatus);
+        }
         timer.startTrackingJob(jobStatus);
     }
 
@@ -421,6 +491,7 @@
             if (jobs != null) {
                 jobs.remove(jobStatus);
             }
+            mTopStartedJobs.remove(jobStatus);
         }
     }
 
@@ -511,6 +582,7 @@
             mInQuotaAlarmListeners.delete(userId, packageName);
         }
         mExecutionStatsCache.delete(userId, packageName);
+        mForegroundUids.delete(uid);
     }
 
     @Override
@@ -522,6 +594,20 @@
         mExecutionStatsCache.delete(userId);
     }
 
+    private boolean isUidInForeground(int uid) {
+        if (UserHandle.isCore(uid)) {
+            return true;
+        }
+        synchronized (mLock) {
+            return mForegroundUids.get(uid);
+        }
+    }
+
+    /** @return true if the job was started while the app was in the TOP state. */
+    private boolean isTopStartedJob(@NonNull final JobStatus jobStatus) {
+        return mTopStartedJobs.contains(jobStatus);
+    }
+
     /**
      * Returns an appropriate standby bucket for the job, taking into account any standby
      * exemptions.
@@ -537,9 +623,15 @@
 
     private boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
         final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
-        // Jobs for the active app should always be able to run.
-        return jobStatus.uidActive || isWithinQuotaLocked(
-                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+        Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+        // A job is within quota if one of the following is true:
+        //   1. it was started while the app was in the TOP state
+        //   2. the app is currently in the foreground
+        //   3. the app overall is within its quota
+        return isTopStartedJob(jobStatus)
+                || isUidInForeground(jobStatus.getSourceUid())
+                || isWithinQuotaLocked(
+                      jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
     }
 
     private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
@@ -800,7 +892,7 @@
         final boolean isCharging = mChargeTracker.isCharging();
         if (DEBUG) Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
         // Deal with Timers first.
-        mPkgTimers.forEach((t) -> t.onChargingChanged(nowElapsed, isCharging));
+        mPkgTimers.forEach((t) -> t.onStateChanged(nowElapsed, isCharging));
         // Now update jobs.
         maybeUpdateAllConstraintsLocked();
     }
@@ -837,10 +929,15 @@
         boolean changed = false;
         for (int i = jobs.size() - 1; i >= 0; --i) {
             final JobStatus js = jobs.valueAt(i);
-            if (js.uidActive) {
-                // Jobs for the active app should always be able to run.
+            if (isTopStartedJob(js)) {
+                // Job was started while the app was in the TOP state so we should allow it to
+                // finish.
                 changed |= js.setQuotaConstraintSatisfied(true);
-            } else if (realStandbyBucket == getEffectiveStandbyBucket(js)) {
+            } else if (realStandbyBucket != ACTIVE_INDEX
+                    && realStandbyBucket == getEffectiveStandbyBucket(js)) {
+                // An app in the ACTIVE bucket may be out of quota while the job could be in quota
+                // for some reason. Therefore, avoid setting the real value here and check each job
+                // individually.
                 changed |= js.setQuotaConstraintSatisfied(realInQuota);
             } else {
                 // This job is somehow exempted. Need to determine its own quota status.
@@ -854,7 +951,7 @@
             maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
         } else {
             QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
-            if (alarmListener != null) {
+            if (alarmListener != null && alarmListener.isWaiting()) {
                 mAlarmManager.cancel(alarmListener);
                 // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
                 alarmListener.setTriggerTime(0);
@@ -863,6 +960,56 @@
         return changed;
     }
 
+    private class UidConstraintUpdater implements Consumer<JobStatus> {
+        private final UserPackageMap<Integer> mToScheduleStartAlarms = new UserPackageMap<>();
+        public boolean wasJobChanged;
+
+        @Override
+        public void accept(JobStatus jobStatus) {
+            wasJobChanged |= jobStatus.setQuotaConstraintSatisfied(isWithinQuotaLocked(jobStatus));
+            final int userId = jobStatus.getSourceUserId();
+            final String packageName = jobStatus.getSourcePackageName();
+            final int realStandbyBucket = jobStatus.getStandbyBucket();
+            if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
+                QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
+                if (alarmListener != null && alarmListener.isWaiting()) {
+                    mAlarmManager.cancel(alarmListener);
+                    // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
+                    alarmListener.setTriggerTime(0);
+                }
+            } else {
+                mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
+            }
+        }
+
+        void postProcess() {
+            for (int u = 0; u < mToScheduleStartAlarms.numUsers(); ++u) {
+                final int userId = mToScheduleStartAlarms.keyAt(u);
+                for (int p = 0; p < mToScheduleStartAlarms.numPackagesForUser(userId); ++p) {
+                    final String packageName = mToScheduleStartAlarms.keyAt(u, p);
+                    final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName);
+                    maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket);
+                }
+            }
+        }
+
+        void reset() {
+            wasJobChanged = false;
+            mToScheduleStartAlarms.clear();
+        }
+    }
+
+    private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater();
+
+    private boolean maybeUpdateConstraintForUidLocked(final int uid) {
+        mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints);
+
+        mUpdateUidConstraints.postProcess();
+        boolean changed = mUpdateUidConstraints.wasJobChanged;
+        mUpdateUidConstraints.reset();
+        return changed;
+    }
+
     /**
      * Maybe schedule a non-wakeup alarm for the next time this package will have quota to run
      * again. This should only be called if the package is already out of quota.
@@ -1052,6 +1199,7 @@
 
     private final class Timer {
         private final Package mPkg;
+        private final int mUid;
 
         // List of jobs currently running for this app that started when the app wasn't in the
         // foreground.
@@ -1059,16 +1207,18 @@
         private long mStartTimeElapsed;
         private int mBgJobCount;
 
-        Timer(int userId, String packageName) {
+        Timer(int uid, int userId, String packageName) {
             mPkg = new Package(userId, packageName);
+            mUid = uid;
         }
 
         void startTrackingJob(@NonNull JobStatus jobStatus) {
-            if (jobStatus.uidActive) {
-                // We intentionally don't pay attention to fg state changes after a job has started.
+            if (isTopStartedJob(jobStatus)) {
+                // We intentionally don't pay attention to fg state changes after a TOP job has
+                // started.
                 if (DEBUG) {
                     Slog.v(TAG,
-                            "Timer ignoring " + jobStatus.toShortString() + " because uidActive");
+                            "Timer ignoring " + jobStatus.toShortString() + " because isTop");
                 }
                 return;
             }
@@ -1076,7 +1226,7 @@
             synchronized (mLock) {
                 // Always track jobs, even when charging.
                 mRunningBgJobs.add(jobStatus);
-                if (!mChargeTracker.isCharging()) {
+                if (shouldTrackLocked()) {
                     mBgJobCount++;
                     if (mRunningBgJobs.size() == 1) {
                         // Started tracking the first job.
@@ -1142,6 +1292,10 @@
             }
         }
 
+        boolean isRunning(JobStatus jobStatus) {
+            return mRunningBgJobs.contains(jobStatus);
+        }
+
         long getCurrentDuration(long nowElapsed) {
             synchronized (mLock) {
                 return !isActive() ? 0 : nowElapsed - mStartTimeElapsed;
@@ -1154,17 +1308,21 @@
             }
         }
 
-        void onChargingChanged(long nowElapsed, boolean isCharging) {
+        private boolean shouldTrackLocked() {
+            return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
+        }
+
+        void onStateChanged(long nowElapsed, boolean isQuotaFree) {
             synchronized (mLock) {
-                if (isCharging) {
+                if (isQuotaFree) {
                     emitSessionLocked(nowElapsed);
-                } else {
+                } else if (shouldTrackLocked()) {
                     // Start timing from unplug.
                     if (mRunningBgJobs.size() > 0) {
                         mStartTimeElapsed = nowElapsed;
                         // NOTE: this does have the unfortunate consequence that if the device is
-                        // repeatedly plugged in and unplugged, the job count for a package may be
-                        // artificially high.
+                        // repeatedly plugged in and unplugged, or an app changes foreground state
+                        // very frequently, the job count for a package may be artificially high.
                         mBgJobCount = mRunningBgJobs.size();
                         // Starting the timer means that all cached execution stats are now
                         // incorrect.
@@ -1371,6 +1529,38 @@
                         }
                         break;
                     }
+                    case MSG_UID_PROCESS_STATE_CHANGED: {
+                        final int uid = msg.arg1;
+                        final int procState = msg.arg2;
+                        final int userId = UserHandle.getUserId(uid);
+                        final long nowElapsed = sElapsedRealtimeClock.millis();
+
+                        synchronized (mLock) {
+                            boolean isQuotaFree;
+                            if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+                                mForegroundUids.put(uid, true);
+                                isQuotaFree = true;
+                            } else {
+                                mForegroundUids.delete(uid);
+                                isQuotaFree = false;
+                            }
+                            // Update Timers first.
+                            final int userIndex = mPkgTimers.indexOfKey(userId);
+                            if (userIndex != -1) {
+                                final int numPkgs = mPkgTimers.numPackagesForUser(userId);
+                                for (int p = 0; p < numPkgs; ++p) {
+                                    Timer t = mPkgTimers.valueAt(userIndex, p);
+                                    if (t != null) {
+                                        t.onStateChanged(nowElapsed, isQuotaFree);
+                                    }
+                                }
+                            }
+                            if (maybeUpdateConstraintForUidLocked(uid)) {
+                                mStateChangedListener.onControllerStateChanged();
+                            }
+                        }
+                        break;
+                    }
                 }
             }
         }
@@ -1420,6 +1610,12 @@
 
     @VisibleForTesting
     @NonNull
+    SparseBooleanArray getForegroundUids() {
+        return mForegroundUids;
+    }
+
+    @VisibleForTesting
+    @NonNull
     Handler getHandler() {
         return mHandler;
     }
@@ -1450,6 +1646,10 @@
         pw.println("In parole: " + mInParole);
         pw.println();
 
+        pw.print("Foreground UIDs: ");
+        pw.println(mForegroundUids.toString());
+        pw.println();
+
         mTrackedJobs.forEach((jobs) -> {
             for (int j = 0; j < jobs.size(); j++) {
                 final JobStatus js = jobs.valueAt(j);
@@ -1460,6 +1660,9 @@
                 js.printUniqueId(pw);
                 pw.print(" from ");
                 UserHandle.formatUid(pw, js.getSourceUid());
+                if (mTopStartedJobs.contains(js)) {
+                    pw.print(" (TOP)");
+                }
                 pw.println();
 
                 pw.increaseIndent();
@@ -1511,6 +1714,11 @@
         proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
         proto.write(StateControllerProto.QuotaController.IS_IN_PAROLE, mInParole);
 
+        for (int i = 0; i < mForegroundUids.size(); ++i) {
+            proto.write(StateControllerProto.QuotaController.FOREGROUND_UIDS,
+                    mForegroundUids.keyAt(i));
+        }
+
         mTrackedJobs.forEach((jobs) -> {
             for (int j = 0; j < jobs.size(); j++) {
                 final JobStatus js = jobs.valueAt(j);
@@ -1526,6 +1734,8 @@
                 proto.write(
                         StateControllerProto.QuotaController.TrackedJob.EFFECTIVE_STANDBY_BUCKET,
                         getEffectiveStandbyBucket(js));
+                proto.write(StateControllerProto.QuotaController.TrackedJob.IS_TOP_STARTED_JOB,
+                        mTopStartedJobs.contains(js));
                 proto.write(StateControllerProto.QuotaController.TrackedJob.HAS_QUOTA,
                         js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
                 proto.write(StateControllerProto.QuotaController.TrackedJob.REMAINING_QUOTA_MS,
diff --git a/services/core/java/com/android/server/location/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java
index 29465ad..9e33943 100644
--- a/services/core/java/com/android/server/location/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/GnssConfiguration.java
@@ -29,7 +29,10 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
@@ -66,6 +69,7 @@
             "USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL";
     private static final String CONFIG_GPS_LOCK = "GPS_LOCK";
     private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC";
+    public static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS";
 
     // Limit on NI emergency mode time extension after emergency sessions ends
     private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300;  // 5 minute maximum
@@ -171,6 +175,32 @@
     }
 
     /**
+     * Returns the list of proxy apps from the value of config parameter NFW_PROXY_APPS or
+     * {@Collections.EMPTY_LIST} if no value is provided.
+     */
+    List<String> getProxyApps() {
+        // Space separated list of Android proxy app package names.
+        String proxyAppsStr = mProperties.getProperty(CONFIG_NFW_PROXY_APPS);
+        if (TextUtils.isEmpty(proxyAppsStr)) {
+            return Collections.EMPTY_LIST;
+        }
+
+        String[] proxyAppsArray = proxyAppsStr.trim().split("\\s+");
+        if (proxyAppsArray.length == 0) {
+            return Collections.EMPTY_LIST;
+        }
+
+        // TODO(b/122856486): Validate proxy app names so that a system app or some popular app
+        //                    with location permission is not specified as a proxy app.
+        ArrayList proxyApps = new ArrayList(proxyAppsArray.length);
+        for (String proxyApp : proxyAppsArray) {
+            proxyApps.add(proxyApp);
+        }
+
+        return proxyApps;
+    }
+
+    /**
      * Updates the GNSS HAL satellite blacklist.
      */
     void setSatelliteBlacklist(int[] constellations, int[] svids) {
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index dfb98c3..d346ddc 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -372,6 +372,7 @@
     private boolean mSuplEsEnabled = false;
 
     private final Context mContext;
+    private final Looper mLooper;
     private final LocationExtras mLocationExtras = new LocationExtras();
     private final GnssStatusListenerHelper mGnssStatusListenerHelper;
     private final GnssSatelliteBlacklistHelper mGnssSatelliteBlacklistHelper;
@@ -382,6 +383,7 @@
     private final NtpTimeHelper mNtpTimeHelper;
     private final GnssBatchingProvider mGnssBatchingProvider;
     private final GnssGeofenceProvider mGnssGeofenceProvider;
+    private GnssVisibilityControl mGnssVisibilityControl;
 
     // Handler for processing events
     private Handler mHandler;
@@ -556,6 +558,9 @@
         mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT);
         mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec());
         mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1;
+        if (mGnssVisibilityControl != null) {
+            mGnssVisibilityControl.updateProxyApps(mGnssConfiguration.getProxyApps());
+        }
     }
 
     public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
@@ -563,6 +568,7 @@
         super(locationProviderManager);
 
         mContext = context;
+        mLooper = looper;
 
         // Create a wake lock
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -1869,6 +1875,27 @@
         }
     }
 
+    // Implements method nfwNotifyCb() in IGnssVisibilityControlCallback.hal.
+    @NativeEntryPoint
+    private void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
+            String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
+            boolean inEmergencyMode, boolean isCachedLocation) {
+        if (mGnssVisibilityControl == null) {
+            Log.e(TAG, "reportNfwNotification: mGnssVisibilityControl is not initialized.");
+            return;
+        }
+
+        mGnssVisibilityControl.reportNfwNotification(proxyAppPackageName, protocolStack,
+                otherProtocolStackName, requestor, requestorId, responseType, inEmergencyMode,
+                isCachedLocation);
+    }
+
+    // Implements method isInEmergencySession() in IGnssVisibilityControlCallback.hal.
+    @NativeEntryPoint
+    boolean isInEmergencySession() {
+        return mNIHandler.getInEmergency();
+    }
+
     private void sendMessage(int message, int arg, Object obj) {
         // hold a wake lock until this message is delivered
         // note that this assumes the message will not be removed from the queue before
@@ -1957,6 +1984,10 @@
                 native_cleanup();
             }
 
+            if (native_is_gnss_visibility_control_supported()) {
+                mGnssVisibilityControl = new GnssVisibilityControl(mContext, mLooper);
+            }
+
             // load default GPS configuration
             // (this configuration might change in the future based on SIM changes)
             reloadGpsProperties();
@@ -2114,6 +2145,8 @@
 
     private static native boolean native_is_supported();
 
+    private static native boolean native_is_gnss_visibility_control_supported();
+
     private static native void native_init_once();
 
     private native boolean native_init();
diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
new file mode 100644
index 0000000..845aa9d
--- /dev/null
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -0,0 +1,362 @@
+/*
+ * 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.
+ */
+
+package com.android.server.location;
+
+import android.annotation.SuppressLint;
+import android.app.AppOpsManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Handles GNSS non-framework location access user visibility and control.
+ *
+ * The state of the GnssVisibilityControl object must be accessed/modified through the Handler
+ * thread only.
+ */
+class GnssVisibilityControl {
+    private static final String TAG = "GnssVisibilityControl";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // Constants related to non-framework (NFW) location access permission proxy apps.
+    private static final String NFW_PROXY_APP_PKG_ACTIVITY_NAME_SUFFIX =
+            ".NonFrameworkLocationAccessActivity";
+    private static final String NFW_INTENT_ACTION_NFW_LOCATION_ACCESS_SUFFIX =
+            ".intent.action.NON_FRAMEWORK_LOCATION_ACCESS";
+    private static final String NFW_INTENT_TYPE = "text/plain";
+
+    private static final String LOCATION_PERMISSION_NAME =
+            "android.permission.ACCESS_FINE_LOCATION";
+
+    // Wakelocks
+    private static final String WAKELOCK_KEY = TAG;
+    private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
+    private final PowerManager.WakeLock mWakeLock;
+
+    private final AppOpsManager mAppOps;
+    private final PackageManager mPackageManager;
+
+    private final Handler mHandler;
+    private final Context mContext;
+
+    // Number of non-framework location access proxy apps is expected to be small (< 5).
+    private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7;
+    private HashMap<String, Boolean> mProxyAppToLocationPermissions = new HashMap<>(
+            HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS);
+
+    private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener =
+            uid -> postEvent(() -> handlePermissionsChanged(uid));
+
+    GnssVisibilityControl(Context context, Looper looper) {
+        mContext = context;
+        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
+        mHandler = new Handler(looper);
+        mAppOps = mContext.getSystemService(AppOpsManager.class);
+        mPackageManager = mContext.getPackageManager();
+        // TODO(b/122855984): Handle global location settings on/off.
+        // TODO(b/122856189): Handle roaming case.
+    }
+
+    void updateProxyApps(List<String> nfwLocationAccessProxyApps) {
+        // NOTE: This class doesn't explicitly register and listen for SIM_STATE_CHANGED event
+        //       but rather piggy backs on the GnssLocationProvider SIM_STATE_CHANGED handling
+        //       so that the order of processing is preserved. GnssLocationProvider should
+        //       first load the new config parameters for the new SIM and then call this method.
+        postEvent(() -> handleSubscriptionOrSimChanged(nfwLocationAccessProxyApps));
+    }
+
+    void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
+            String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
+            boolean inEmergencyMode, boolean isCachedLocation) {
+        postEvent(() -> handleNfwNotification(
+                new NfwNotification(proxyAppPackageName, protocolStack, otherProtocolStackName,
+                        requestor, requestorId, responseType, inEmergencyMode, isCachedLocation)));
+    }
+
+    private void handleSubscriptionOrSimChanged(List<String> nfwLocationAccessProxyApps) {
+        if (nfwLocationAccessProxyApps.isEmpty()) {
+            // Stop listening for app permission changes. Clear the app list in GNSS HAL.
+            if (!mProxyAppToLocationPermissions.isEmpty()) {
+                mPackageManager.removeOnPermissionsChangeListener(mOnPermissionsChangedListener);
+                mProxyAppToLocationPermissions.clear();
+                updateNfwLocationAccessProxyAppsInGnssHal();
+            }
+            return;
+        }
+
+        if (mProxyAppToLocationPermissions.isEmpty()) {
+            mPackageManager.addOnPermissionsChangeListener(mOnPermissionsChangedListener);
+        } else {
+            mProxyAppToLocationPermissions.clear();
+        }
+
+        for (String proxApp : nfwLocationAccessProxyApps) {
+            mProxyAppToLocationPermissions.put(proxApp, hasLocationPermission(proxApp));
+        }
+
+        updateNfwLocationAccessProxyAppsInGnssHal();
+    }
+
+    // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal
+    private static class NfwNotification {
+        private static final String KEY_PROTOCOL_STACK = "ProtocolStack";
+        private static final String KEY_OTHER_PROTOCOL_STACK_NAME = "OtherProtocolStackName";
+        private static final String KEY_REQUESTOR = "Requestor";
+        private static final String KEY_REQUESTOR_ID = "RequestorId";
+        private static final String KEY_RESPONSE_TYPE = "ResponseType";
+        private static final String KEY_IN_EMERGENCY_MODE = "InEmergencyMode";
+        private static final String KEY_IS_CACHED_LOCATION = "IsCachedLocation";
+
+        // This must match with NfwResponseType enum in IGnssVisibilityControlCallback.hal.
+        private static final byte NFW_RESPONSE_TYPE_REJECTED = 0;
+        private static final byte NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED = 1;
+        private static final byte NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED = 2;
+
+        private final String mProxyAppPackageName;
+        private final byte mProtocolStack;
+        private final String mOtherProtocolStackName;
+        private final byte mRequestor;
+        private final String mRequestorId;
+        private final byte mResponseType;
+        private final boolean mInEmergencyMode;
+        private final boolean mIsCachedLocation;
+
+        NfwNotification(String proxyAppPackageName, byte protocolStack,
+                String otherProtocolStackName, byte requestor, String requestorId,
+                byte responseType, boolean inEmergencyMode, boolean isCachedLocation) {
+            mProxyAppPackageName = proxyAppPackageName;
+            mProtocolStack = protocolStack;
+            mOtherProtocolStackName = otherProtocolStackName;
+            mRequestor = requestor;
+            mRequestorId = requestorId;
+            mResponseType = responseType;
+            mInEmergencyMode = inEmergencyMode;
+            mIsCachedLocation = isCachedLocation;
+        }
+
+        void copyFieldsToIntent(Intent intent) {
+            intent.putExtra(KEY_PROTOCOL_STACK, mProtocolStack);
+            if (!TextUtils.isEmpty(mOtherProtocolStackName)) {
+                intent.putExtra(KEY_OTHER_PROTOCOL_STACK_NAME, mOtherProtocolStackName);
+            }
+            intent.putExtra(KEY_REQUESTOR, mRequestor);
+            if (!TextUtils.isEmpty(mRequestorId)) {
+                intent.putExtra(KEY_REQUESTOR_ID, mRequestorId);
+            }
+            intent.putExtra(KEY_RESPONSE_TYPE, mResponseType);
+            intent.putExtra(KEY_IN_EMERGENCY_MODE, mInEmergencyMode);
+            if (mResponseType == NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED) {
+                intent.putExtra(KEY_IS_CACHED_LOCATION, mIsCachedLocation);
+            }
+        }
+
+        @SuppressLint("DefaultLocale")
+        public String toString() {
+            return String.format(
+                    "[Notification] proxyAppPackageName: %s, protocolStack: %d"
+                            + ", otherProtocolStackName: %s, requestor: %d, requestorId: %s"
+                            + ", responseType: %d, inEmergencyMode: %b, isCachedLocation: %b",
+                    mProxyAppPackageName, mProtocolStack, mOtherProtocolStackName,
+                    mRequestor, mRequestorId, mResponseType, mInEmergencyMode, mIsCachedLocation);
+        }
+
+        String getResponseTypeAsString() {
+            switch (mResponseType) {
+                case NFW_RESPONSE_TYPE_REJECTED:
+                    return "REJECTED";
+                case NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED:
+                    return "ACCEPTED_NO_LOCATION_PROVIDED";
+                case NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED:
+                    return "ACCEPTED_LOCATION_PROVIDED";
+                default:
+                    return "<Unknown>";
+            }
+        }
+    }
+
+    private void handlePermissionsChanged(int uid) {
+        if (mProxyAppToLocationPermissions.isEmpty()) {
+            return;
+        }
+
+        for (Map.Entry<String, Boolean> entry : mProxyAppToLocationPermissions.entrySet()) {
+            // Cannot cache uid since the application could be uninstalled and reinstalled.
+            final String proxyApp = entry.getKey();
+            final Integer nfwProxyAppUid = getApplicationUid(proxyApp);
+            if (nfwProxyAppUid == null || nfwProxyAppUid != uid) {
+                continue;
+            }
+
+            final boolean isLocationPermissionEnabled = hasLocationPermission(proxyApp);
+            if (isLocationPermissionEnabled != entry.getValue()) {
+                Log.i(TAG, "Location permission setting is changed to "
+                        + (isLocationPermissionEnabled ? "enabled" : "disabled")
+                        + " for non-framework location access proxy app "
+                        + proxyApp);
+                entry.setValue(isLocationPermissionEnabled);
+                updateNfwLocationAccessProxyAppsInGnssHal();
+                return;
+            }
+        }
+    }
+
+    private Integer getApplicationUid(String pkgName) {
+        try {
+            return mPackageManager.getApplicationInfo(pkgName, 0).uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DEBUG) {
+                Log.d(TAG, "Non-framework location access proxy app "
+                        + pkgName + " is not found.");
+            }
+            return null;
+        }
+    }
+
+    private boolean hasLocationPermission(String pkgName) {
+        return mPackageManager.checkPermission(LOCATION_PERMISSION_NAME, pkgName)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private void updateNfwLocationAccessProxyAppsInGnssHal() {
+        // Get a count of proxy apps with location permission enabled to array creation size.
+        int countLocationPermissionEnabledProxyApps = 0;
+        for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) {
+            if (hasLocationPermissionEnabled) {
+                ++countLocationPermissionEnabledProxyApps;
+            }
+        }
+
+        int i = 0;
+        String[] locationPermissionEnabledProxyApps =
+                new String[countLocationPermissionEnabledProxyApps];
+        for (Map.Entry<String, Boolean> entry : mProxyAppToLocationPermissions.entrySet()) {
+            final String proxyApp = entry.getKey();
+            final boolean hasLocationPermissionEnabled = entry.getValue();
+            if (hasLocationPermissionEnabled) {
+                locationPermissionEnabledProxyApps[i++] = proxyApp;
+            }
+        }
+
+        String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps);
+        Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: "
+                + proxyAppsStr);
+        boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps);
+        if (!result) {
+            Log.e(TAG, "Failed to update non-framework location access proxy apps in the"
+                    + " GNSS HAL to: " + proxyAppsStr);
+        }
+    }
+
+    private void handleNfwNotification(NfwNotification nfwNotification) {
+        if (DEBUG) Log.d(TAG, nfwNotification.toString());
+
+        final String proxyAppPackageName = nfwNotification.mProxyAppPackageName;
+        if (TextUtils.isEmpty(proxyAppPackageName)) {
+            Log.e(TAG, "ProxyAppPackageName field is not set. Not sending intent to proxy app for "
+                    + nfwNotification);
+            return;
+        }
+
+        Boolean isLocationPermissionEnabled = mProxyAppToLocationPermissions.get(
+                proxyAppPackageName);
+        if (isLocationPermissionEnabled == null) {
+            // App is not in the configured list.
+            Log.e(TAG, "Could not find proxy app with name: " + proxyAppPackageName + " in the "
+                    + "value specified for config parameter: "
+                    + GnssConfiguration.CONFIG_NFW_PROXY_APPS + ". Not sending intent to proxy app"
+                    + " for " + nfwNotification);
+            return;
+        }
+
+        // Send intent to non-framework location proxy app with notification information.
+        final Intent intent = new Intent(
+                proxyAppPackageName + NFW_INTENT_ACTION_NFW_LOCATION_ACCESS_SUFFIX);
+        final String proxAppActivityName =
+                proxyAppPackageName + NFW_PROXY_APP_PKG_ACTIVITY_NAME_SUFFIX;
+        intent.setClassName(proxyAppPackageName, proxAppActivityName);
+        intent.setType(NFW_INTENT_TYPE);
+        nfwNotification.copyFieldsToIntent(intent);
+
+        // Check if the proxy app is still installed.
+        final Integer clsAppUid = getApplicationUid(proxyAppPackageName);
+        if (clsAppUid == null || intent.resolveActivity(mPackageManager) == null) {
+            Log.i(TAG, "Proxy application " + proxyAppPackageName + " and/or activity "
+                    + proxAppActivityName + " is not found. Not sending"
+                    + " intent to proxy app for " + nfwNotification);
+            return;
+        }
+
+        // Display location icon attributed to this proxy app.
+        mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, clsAppUid, proxyAppPackageName);
+
+        // Log proxy app permission mismatch between framework and GNSS HAL.
+        boolean isLocationRequestAccepted =
+                nfwNotification.mResponseType != NfwNotification.NFW_RESPONSE_TYPE_REJECTED;
+        if (isLocationPermissionEnabled != isLocationRequestAccepted) {
+            Log.w(TAG, "Permission mismatch. Framework proxy app " + proxyAppPackageName
+                    + " location permission is set to " + isLocationPermissionEnabled
+                    + " but GNSS non-framework location access response type is "
+                    + nfwNotification.getResponseTypeAsString() + " for " + nfwNotification);
+        }
+
+        // Notify proxy app.
+        try {
+            if (DEBUG) {
+                Log.d(TAG, "Sending non-framework location access notification intent: " + intent);
+            }
+            mContext.startActivityAsUser(intent, UserHandle.getUserHandleForUid(clsAppUid));
+        } catch (ActivityNotFoundException e) {
+            Log.w(TAG, "Activity not found. Failed to send non-framework location access"
+                    + " notification intent to proxy app activity: " + proxAppActivityName);
+        }
+    }
+
+    private void postEvent(Runnable event) {
+        // Hold a wake lock until this message is delivered.
+        // Note that this assumes the message will not be removed from the queue before
+        // it is handled (otherwise the wake lock would be leaked).
+        mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
+        if (!mHandler.post(runEventAndReleaseWakeLock(event))) {
+            mWakeLock.release();
+        }
+    }
+
+    private Runnable runEventAndReleaseWakeLock(Runnable event) {
+        return () -> {
+            try {
+                event.run();
+            } finally {
+                mWakeLock.release();
+            }
+        };
+    }
+
+    private native boolean native_enable_nfw_location_access(String[] proxyApps);
+}
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index 86bc9f3..fe91c63 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -52,7 +52,6 @@
         mExtras = null;
 
         setProperties(properties);
-        setEnabled(true);
     }
 
     /** Sets the enabled state of this mock provider. */
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index d611a17..7fffe8e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -80,7 +80,7 @@
     private final ControllerStub mController;
     private final SessionStub mSession;
     private final SessionCb mSessionCb;
-    private final MediaSessionService mService;
+    private final MediaSessionService.ServiceImpl mService;
     private final Context mContext;
 
     private final Object mLock = new Object();
@@ -120,7 +120,8 @@
     private String mMetadataDescription;
 
     public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
-            SessionCallbackLink cb, String tag, MediaSessionService service, Looper handlerLooper) {
+            SessionCallbackLink cb, String tag, MediaSessionService.ServiceImpl service,
+            Looper handlerLooper) {
         mOwnerPid = ownerPid;
         mOwnerUid = ownerUid;
         mUserId = userId;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index ba7b87e..d20ed8c 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,79 +16,15 @@
 
 package com.android.server.media;
 
-import static android.os.UserHandle.USER_ALL;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.INotificationManager;
-import android.app.KeyguardManager;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.media.AudioManager;
-import android.media.AudioManagerInternal;
-import android.media.AudioPlaybackConfiguration;
-import android.media.AudioSystem;
-import android.media.IAudioService;
-import android.media.IRemoteVolumeController;
-import android.media.MediaController2;
-import android.media.Session2CommandGroup;
 import android.media.Session2Token;
-import android.media.session.IActiveSessionsListener;
-import android.media.session.ICallback;
-import android.media.session.IOnMediaKeyListener;
-import android.media.session.IOnVolumeKeyLongPressListener;
-import android.media.session.ISession;
-import android.media.session.ISession2TokensListener;
-import android.media.session.ISessionManager;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.SessionCallbackLink;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.speech.RecognizerIntent;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
 
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.DumpUtils;
-import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
 import com.android.server.Watchdog.Monitor;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -97,2040 +33,124 @@
 public class MediaSessionService extends SystemService implements Monitor {
     private static final String TAG = "MediaSessionService";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    // Leave log for key event always.
-    private static final boolean DEBUG_KEY_EVENT = true;
 
-    private static final int WAKELOCK_TIMEOUT = 5000;
-    private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
-
-    private final SessionManagerImpl mSessionManagerImpl;
-    private final MessageHandler mHandler = new MessageHandler();
-    private final PowerManager.WakeLock mMediaEventWakeLock;
-    private final int mLongPressTimeout;
-    private final INotificationManager mNotificationManager;
-    private final Object mLock = new Object();
-    // Keeps the full user id for each user.
-    @GuardedBy("mLock")
-    private final SparseIntArray mFullUserIds = new SparseIntArray();
-    @GuardedBy("mLock")
-    private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
-    @GuardedBy("mLock")
-    private final ArrayList<SessionsListenerRecord> mSessionsListeners
-            = new ArrayList<SessionsListenerRecord>();
-    // Map user id as index to list of Session2Tokens
-    // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
-    //       one place.
-    @GuardedBy("mLock")
-    private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
-    @GuardedBy("mLock")
-    private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
-            new ArrayList<>();
-
-    private KeyguardManager mKeyguardManager;
-    private IAudioService mAudioService;
-    private AudioManagerInternal mAudioManagerInternal;
-    private ActivityManager mActivityManager;
-    private ContentResolver mContentResolver;
-    private SettingsObserver mSettingsObserver;
-    private boolean mHasFeatureLeanback;
-
-    // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
-    // It's always not null after the MediaSessionService is started.
-    private FullUserRecord mCurrentFullUserRecord;
-    private MediaSessionRecord mGlobalPrioritySession;
-    private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
-
-    // Used to notify system UI when remote volume was changed. TODO find a
-    // better way to handle this.
-    private IRemoteVolumeController mRvc;
+    private final ServiceImpl mImpl;
 
     public MediaSessionService(Context context) {
         super(context);
-        mSessionManagerImpl = new SessionManagerImpl();
-        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
-        mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
-        mNotificationManager = INotificationManager.Stub.asInterface(
-                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        mImpl = new MediaSessionServiceImpl(context);
     }
 
     @Override
     public void onStart() {
-        publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
+        publishBinderService(Context.MEDIA_SESSION_SERVICE, mImpl.getServiceBinder());
         Watchdog.getInstance().addMonitor(this);
-        mKeyguardManager =
-                (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
-        mAudioService = getAudioService();
-        mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
-        mActivityManager =
-                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
-        mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
-        mAudioPlayerStateMonitor.registerListener(
-                (config, isRemoved) -> {
-                    if (isRemoved || !config.isActive() || config.getPlayerType()
-                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
-                        return;
-                    }
-                    synchronized (mLock) {
-                        FullUserRecord user = getFullUserRecordLocked(
-                                UserHandle.getUserId(config.getClientUid()));
-                        if (user != null) {
-                            user.mPriorityStack.updateMediaButtonSessionIfNeeded();
-                        }
-                    }
-                }, null /* handler */);
-        mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
-        mContentResolver = getContext().getContentResolver();
-        mSettingsObserver = new SettingsObserver();
-        mSettingsObserver.observe();
-        mHasFeatureLeanback = getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_LEANBACK);
 
-        updateUser();
+        mImpl.onStart();
     }
 
-    private IAudioService getAudioService() {
-        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
-        return IAudioService.Stub.asInterface(b);
+    @Override
+    public void onStartUser(int userId) {
+        mImpl.onStartUser(userId);
     }
 
-    private boolean isGlobalPriorityActiveLocked() {
-        return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
+    @Override
+    public void onSwitchUser(int userId) {
+        mImpl.onSwitchUser(userId);
     }
 
+    // Called when the user with the userId is removed.
+    @Override
+    public void onStopUser(int userId) {
+        mImpl.onStopUser(userId);
+    }
+
+    @Override
+    public void monitor() {
+        mImpl.monitor();
+    }
+
+    /**
+     * Updates session.
+     */
     public void updateSession(MediaSessionRecord record) {
-        synchronized (mLock) {
-            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
-            if (user == null) {
-                Log.w(TAG, "Unknown session updated. Ignoring.");
-                return;
-            }
-            if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
-                if (DEBUG_KEY_EVENT) {
-                    Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
-                }
-                user.pushAddressedPlayerChangedLocked();
-            } else {
-                if (!user.mPriorityStack.contains(record)) {
-                    Log.w(TAG, "Unknown session updated. Ignoring.");
-                    return;
-                }
-                user.mPriorityStack.onSessionStateChange(record);
-            }
-            mHandler.postSessionsChanged(record.getUserId());
-        }
+        mImpl.updateSession(record);
     }
 
+    /**
+     * Sets global priority session.
+     */
     public void setGlobalPrioritySession(MediaSessionRecord record) {
-        synchronized (mLock) {
-            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
-            if (mGlobalPrioritySession != record) {
-                Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
-                        + " to " + record);
-                mGlobalPrioritySession = record;
-                if (user != null && user.mPriorityStack.contains(record)) {
-                    // Handle the global priority session separately.
-                    // Otherwise, it can be the media button session regardless of the active state
-                    // because it or other system components might have been the lastly played media
-                    // app.
-                    user.mPriorityStack.removeSession(record);
-                }
-            }
-        }
-    }
-
-    private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
-        List<MediaSessionRecord> records = new ArrayList<>();
-        if (userId == USER_ALL) {
-            int size = mUserRecords.size();
-            for (int i = 0; i < size; i++) {
-                records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
-            }
-        } else {
-            FullUserRecord user = getFullUserRecordLocked(userId);
-            if (user == null) {
-                Log.w(TAG, "getSessions failed. Unknown user " + userId);
-                return records;
-            }
-            records.addAll(user.mPriorityStack.getActiveSessions(userId));
-        }
-
-        // Return global priority session at the first whenever it's asked.
-        if (isGlobalPriorityActiveLocked()
-                && (userId == USER_ALL || userId == mGlobalPrioritySession.getUserId())) {
-            records.add(0, mGlobalPrioritySession);
-        }
-        return records;
+        mImpl.setGlobalPrioritySession(record);
     }
 
     List<Session2Token> getSession2TokensLocked(int userId) {
-        List<Session2Token> list = new ArrayList<>();
-        if (userId == USER_ALL) {
-            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
-                list.addAll(mSession2TokensPerUser.valueAt(i));
-            }
-        } else {
-            list.addAll(mSession2TokensPerUser.get(userId));
-        }
-        return list;
+        return mImpl.getSession2TokensLocked(userId);
     }
 
     /**
      * Tells the system UI that volume has changed on an active remote session.
      */
     public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
-        if (mRvc == null || !session.isActive()) {
-            return;
-        }
-        try {
-            mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
-        } catch (Exception e) {
-            Log.wtf(TAG, "Error sending volume change to system UI.", e);
-        }
+        mImpl.notifyRemoteVolumeChanged(flags, session);
     }
 
+    /**
+     * Called when session playstate is changed.
+     */
     public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
-        synchronized (mLock) {
-            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
-            if (user == null || !user.mPriorityStack.contains(record)) {
-                Log.d(TAG, "Unknown session changed playback state. Ignoring.");
-                return;
-            }
-            user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
-        }
+        mImpl.onSessionPlaystateChanged(record, oldState, newState);
     }
 
+    /**
+     * Called when session playback type is changed.
+     */
     public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
-        synchronized (mLock) {
-            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
-            if (user == null || !user.mPriorityStack.contains(record)) {
-                Log.d(TAG, "Unknown session changed playback type. Ignoring.");
-                return;
-            }
-            pushRemoteVolumeUpdateLocked(record.getUserId());
-        }
-    }
-
-    @Override
-    public void onStartUser(int userId) {
-        if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
-        updateUser();
-    }
-
-    @Override
-    public void onSwitchUser(int userId) {
-        if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
-        updateUser();
-    }
-
-    // Called when the user with the userId is removed.
-    @Override
-    public void onStopUser(int userId) {
-        if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
-        synchronized (mLock) {
-            // TODO: Also handle removing user in updateUser() because adding/switching user is
-            //       handled in updateUser().
-            FullUserRecord user = getFullUserRecordLocked(userId);
-            if (user != null) {
-                if (user.mFullUserId == userId) {
-                    user.destroySessionsForUserLocked(USER_ALL);
-                    mUserRecords.remove(userId);
-                } else {
-                    user.destroySessionsForUserLocked(userId);
-                }
-            }
-            mSession2TokensPerUser.remove(userId);
-            updateUser();
-        }
-    }
-
-    @Override
-    public void monitor() {
-        synchronized (mLock) {
-            // Check for deadlock
-        }
+        mImpl.onSessionPlaybackTypeChanged(record);
     }
 
     protected void enforcePhoneStatePermission(int pid, int uid) {
-        if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
-        }
+        mImpl.enforcePhoneStatePermission(pid, uid);
     }
 
     void sessionDied(MediaSessionRecord session) {
-        synchronized (mLock) {
-            destroySessionLocked(session);
-        }
+        mImpl.sessionDied(session);
     }
 
     void destroySession(MediaSessionRecord session) {
-        synchronized (mLock) {
-            destroySessionLocked(session);
-        }
-    }
-
-    private void updateUser() {
-        synchronized (mLock) {
-            UserManager manager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
-            mFullUserIds.clear();
-            List<UserInfo> allUsers = manager.getUsers();
-            if (allUsers != null) {
-                for (UserInfo userInfo : allUsers) {
-                    if (userInfo.isManagedProfile()) {
-                        mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
-                    } else {
-                        mFullUserIds.put(userInfo.id, userInfo.id);
-                        if (mUserRecords.get(userInfo.id) == null) {
-                            mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
-                        }
-                    }
-                    if (mSession2TokensPerUser.get(userInfo.id) == null) {
-                        mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
-                    }
-                }
-            }
-            // Ensure that the current full user exists.
-            int currentFullUserId = ActivityManager.getCurrentUser();
-            mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
-            if (mCurrentFullUserRecord == null) {
-                Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
-                mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
-                mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
-                if (mSession2TokensPerUser.get(currentFullUserId) == null) {
-                    mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
-                }
-            }
-            mFullUserIds.put(currentFullUserId, currentFullUserId);
-        }
-    }
-
-    private void updateActiveSessionListeners() {
-        synchronized (mLock) {
-            for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
-                SessionsListenerRecord listener = mSessionsListeners.get(i);
-                try {
-                    enforceMediaPermissions(listener.componentName, listener.pid, listener.uid,
-                            listener.userId);
-                } catch (SecurityException e) {
-                    Log.i(TAG, "ActiveSessionsListener " + listener.componentName
-                            + " is no longer authorized. Disconnecting.");
-                    mSessionsListeners.remove(i);
-                    try {
-                        listener.listener
-                                .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
-                    } catch (Exception e1) {
-                        // ignore
-                    }
-                }
-            }
-        }
-    }
-
-    /*
-     * When a session is removed several things need to happen.
-     * 1. We need to remove it from the relevant user.
-     * 2. We need to remove it from the priority stack.
-     * 3. We need to remove it from all sessions.
-     * 4. If this is the system priority session we need to clear it.
-     * 5. We need to unlink to death from the cb binder
-     * 6. We need to tell the session to do any final cleanup (onDestroy)
-     */
-    private void destroySessionLocked(MediaSessionRecord session) {
-        if (DEBUG) {
-            Log.d(TAG, "Destroying " + session);
-        }
-        FullUserRecord user = getFullUserRecordLocked(session.getUserId());
-        if (mGlobalPrioritySession == session) {
-            mGlobalPrioritySession = null;
-            if (session.isActive() && user != null) {
-                user.pushAddressedPlayerChangedLocked();
-            }
-        } else {
-            if (user != null) {
-                user.mPriorityStack.removeSession(session);
-            }
-        }
-
-        try {
-            session.getCallback().getBinder().unlinkToDeath(session, 0);
-        } catch (Exception e) {
-            // ignore exceptions while destroying a session.
-        }
-        session.onDestroy();
-        mHandler.postSessionsChanged(session.getUserId());
-    }
-
-    private void enforcePackageName(String packageName, int uid) {
-        if (TextUtils.isEmpty(packageName)) {
-            throw new IllegalArgumentException("packageName may not be empty");
-        }
-        String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
-        final int packageCount = packages.length;
-        for (int i = 0; i < packageCount; i++) {
-            if (packageName.equals(packages[i])) {
-                return;
-            }
-        }
-        throw new IllegalArgumentException("packageName is not owned by the calling process");
-    }
-
-    /**
-     * Checks a caller's authorization to register an IRemoteControlDisplay.
-     * Authorization is granted if one of the following is true:
-     * <ul>
-     * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
-     * permission</li>
-     * <li>the caller's listener is one of the enabled notification listeners
-     * for the caller's user</li>
-     * </ul>
-     */
-    private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
-            int resolvedUserId) {
-        if (isCurrentVolumeController(pid, uid)) return;
-        if (getContext()
-                .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
-                    != PackageManager.PERMISSION_GRANTED
-                && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
-                        resolvedUserId)) {
-            throw new SecurityException("Missing permission to control media.");
-        }
-    }
-
-    private boolean isCurrentVolumeController(int pid, int uid) {
-        return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
-                pid, uid) == PackageManager.PERMISSION_GRANTED;
-    }
-
-    private void enforceSystemUiPermission(String action, int pid, int uid) {
-        if (!isCurrentVolumeController(pid, uid)) {
-            throw new SecurityException("Only system ui may " + action);
-        }
-    }
-
-    /**
-     * This checks if the component is an enabled notification listener for the
-     * specified user. Enabled components may only operate on behalf of the user
-     * they're running as.
-     *
-     * @param compName The component that is enabled.
-     * @param userId The user id of the caller.
-     * @param forUserId The user id they're making the request on behalf of.
-     * @return True if the component is enabled, false otherwise
-     */
-    private boolean isEnabledNotificationListener(ComponentName compName, int userId,
-            int forUserId) {
-        if (userId != forUserId) {
-            // You may not access another user's content as an enabled listener.
-            return false;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Checking if enabled notification listener " + compName);
-        }
-        if (compName != null) {
-            try {
-                return mNotificationManager.isNotificationListenerAccessGrantedForUser(
-                        compName, userId);
-            } catch(RemoteException e) {
-                Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
-            }
-        }
-        return false;
-    }
-
-    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
-            String callerPackageName, SessionCallbackLink cb, String tag) throws RemoteException {
-        synchronized (mLock) {
-            return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
-        }
-    }
-
-    /*
-     * When a session is created the following things need to happen.
-     * 1. Its callback binder needs a link to death
-     * 2. It needs to be added to all sessions.
-     * 3. It needs to be added to the priority stack.
-     * 4. It needs to be added to the relevant user record.
-     */
-    private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
-            String callerPackageName, SessionCallbackLink cb, String tag) {
-        FullUserRecord user = getFullUserRecordLocked(userId);
-        if (user == null) {
-            Log.wtf(TAG, "Request from invalid user: " +  userId);
-            throw new RuntimeException("Session request from invalid user.");
-        }
-
-        final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
-                callerPackageName, cb, tag, this, mHandler.getLooper());
-        try {
-            cb.getBinder().linkToDeath(session, 0);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Media Session owner died prematurely.", e);
-        }
-
-        user.mPriorityStack.addSession(session);
-        mHandler.postSessionsChanged(userId);
-
-        if (DEBUG) {
-            Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
-        }
-        return session;
-    }
-
-    private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
-        for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
-            if (mSessionsListeners.get(i).listener.asBinder() == listener.asBinder()) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private int findIndexOfSession2TokensListenerLocked(ISession2TokensListener listener) {
-        for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
-            if (mSession2TokensListenerRecords.get(i).listener.asBinder() == listener.asBinder()) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-
-    private void pushSessionsChanged(int userId) {
-        synchronized (mLock) {
-            FullUserRecord user = getFullUserRecordLocked(userId);
-            if (user == null) {
-                Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
-                return;
-            }
-            List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
-            int size = records.size();
-            ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
-            for (int i = 0; i < size; i++) {
-                tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
-            }
-            pushRemoteVolumeUpdateLocked(userId);
-            for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
-                SessionsListenerRecord record = mSessionsListeners.get(i);
-                if (record.userId == USER_ALL || record.userId == userId) {
-                    try {
-                        record.listener.onActiveSessionsChanged(tokens);
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
-                                e);
-                        mSessionsListeners.remove(i);
-                    }
-                }
-            }
-        }
-    }
-
-    private void pushRemoteVolumeUpdateLocked(int userId) {
-        if (mRvc != null) {
-            try {
-                FullUserRecord user = getFullUserRecordLocked(userId);
-                if (user == null) {
-                    Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
-                    return;
-                }
-                MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
-                mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
-            } catch (RemoteException e) {
-                Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
-            }
-        }
+        mImpl.destroySession(session);
     }
 
     void pushSession2TokensChangedLocked(int userId) {
-        List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
-        List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
-
-        for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
-            Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
-            try {
-                if (listenerRecord.userId == USER_ALL) {
-                    listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
-                } else if (listenerRecord.userId == userId) {
-                    listenerRecord.listener.onSession2TokensChanged(session2Tokens);
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
-                mSession2TokensListenerRecords.remove(i);
-            }
-        }
+        mImpl.pushSession2TokensChangedLocked(userId);
     }
 
     /**
-     * Called when the media button receiver for the {@param record} is changed.
-     *
-     * @param record the media session whose media button receiver is updated.
+     * Called when media button receiver changed.
      */
     public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
-        synchronized (mLock) {
-            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
-            MediaSessionRecord mediaButtonSession =
-                    user.mPriorityStack.getMediaButtonSession();
-            if (record == mediaButtonSession) {
-                user.rememberMediaButtonReceiverLocked(mediaButtonSession);
-            }
-        }
+        mImpl.onMediaButtonReceiverChanged(record);
     }
 
-    private String getCallingPackageName(int uid) {
-        String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
-        if (packages != null && packages.length > 0) {
-            return packages[0];
-        }
-        return "";
-    }
-
-    private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
-        if (mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
-            return;
-        }
-        try {
-            mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
-        }
-    }
-
-    private FullUserRecord getFullUserRecordLocked(int userId) {
-        int fullUserId = mFullUserIds.get(userId, -1);
-        if (fullUserId < 0) {
-            return null;
-        }
-        return mUserRecords.get(fullUserId);
-    }
-
-    /**
-     * Information about a full user and its corresponding managed profiles.
-     *
-     * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
-     * them when he/she presses a media/volume button. So keeping media sessions for them in one
-     * place makes more sense and increases the readability.</p>
-     * <p>The contents of this object is guarded by {@link #mLock}.
-     */
-    final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
-        public static final int COMPONENT_TYPE_INVALID = 0;
-        public static final int COMPONENT_TYPE_BROADCAST = 1;
-        public static final int COMPONENT_TYPE_ACTIVITY = 2;
-        public static final int COMPONENT_TYPE_SERVICE = 3;
-        private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
-
-        private final int mFullUserId;
-        private final MediaSessionStack mPriorityStack;
-        private PendingIntent mLastMediaButtonReceiver;
-        private ComponentName mRestoredMediaButtonReceiver;
-        private int mRestoredMediaButtonReceiverComponentType;
-        private int mRestoredMediaButtonReceiverUserId;
-
-        private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
-        private int mOnVolumeKeyLongPressListenerUid;
-        private KeyEvent mInitialDownVolumeKeyEvent;
-        private int mInitialDownVolumeStream;
-        private boolean mInitialDownMusicOnly;
-
-        private IOnMediaKeyListener mOnMediaKeyListener;
-        private int mOnMediaKeyListenerUid;
-        private ICallback mCallback;
-
-        public FullUserRecord(int fullUserId) {
-            mFullUserId = fullUserId;
-            mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
-            // Restore the remembered media button receiver before the boot.
-            String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver,
-                    Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
-            if (mediaButtonReceiverInfo == null) {
-                return;
-            }
-            String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
-            if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
-                return;
-            }
-            mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
-            mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
-            if (tokens.length == 3) {
-                mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]);
-            } else {
-                mRestoredMediaButtonReceiverComponentType =
-                        getComponentType(mRestoredMediaButtonReceiver);
-            }
-        }
-
-        public void destroySessionsForUserLocked(int userId) {
-            List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
-            for (MediaSessionRecord session : sessions) {
-                MediaSessionService.this.destroySessionLocked(session);
-            }
-        }
-
-        public void dumpLocked(PrintWriter pw, String prefix) {
-            pw.print(prefix + "Record for full_user=" + mFullUserId);
-            // Dump managed profile user ids associated with this user.
-            int size = mFullUserIds.size();
-            for (int i = 0; i < size; i++) {
-                if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
-                        && mFullUserIds.valueAt(i) == mFullUserId) {
-                    pw.print(", profile_user=" + mFullUserIds.keyAt(i));
-                }
-            }
-            pw.println();
-            String indent = prefix + "  ";
-            pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
-            pw.println(indent + "Volume key long-press listener package: " +
-                    getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
-            pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
-            pw.println(indent + "Media key listener package: " +
-                    getCallingPackageName(mOnMediaKeyListenerUid));
-            pw.println(indent + "Callback: " + mCallback);
-            pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
-            pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
-            pw.println(indent + "Restored MediaButtonReceiverComponentType: "
-                    + mRestoredMediaButtonReceiverComponentType);
-            mPriorityStack.dump(pw, indent);
-            pw.println(indent + "Session2Tokens:");
-            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
-                List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
-                if (list == null || list.size() == 0) {
-                    continue;
-                }
-                for (Session2Token token : list) {
-                    pw.println(indent + "  " + token);
-                }
-            }
-        }
-
-        @Override
-        public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
-                MediaSessionRecord newMediaButtonSession) {
-            if (DEBUG_KEY_EVENT) {
-                Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
-            }
-            synchronized (mLock) {
-                if (oldMediaButtonSession != null) {
-                    mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
-                }
-                if (newMediaButtonSession != null) {
-                    rememberMediaButtonReceiverLocked(newMediaButtonSession);
-                    mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
-                }
-                pushAddressedPlayerChangedLocked();
-            }
-        }
-
-        // Remember media button receiver and keep it in the persistent storage.
-        public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
-            PendingIntent receiver = record.getMediaButtonReceiver();
-            mLastMediaButtonReceiver = receiver;
-            mRestoredMediaButtonReceiver = null;
-            mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
-
-            String mediaButtonReceiverInfo = "";
-            if (receiver != null) {
-                ComponentName component = receiver.getIntent().getComponent();
-                if (component != null
-                        && record.getPackageName().equals(component.getPackageName())) {
-                    String componentName = component.flattenToString();
-                    int componentType = getComponentType(component);
-                    mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM,
-                            componentName, String.valueOf(record.getUserId()),
-                            String.valueOf(componentType));
-                }
-            }
-            Settings.Secure.putStringForUser(mContentResolver,
-                    Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo,
-                    mFullUserId);
-        }
-
-        private void pushAddressedPlayerChangedLocked() {
-            if (mCallback == null) {
-                return;
-            }
-            try {
-                MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
-                if (mediaButtonSession != null) {
-                    mCallback.onAddressedPlayerChangedToMediaSession(
-                            new MediaSession.Token(mediaButtonSession.getControllerBinder()));
-                } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
-                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
-                            mCurrentFullUserRecord.mLastMediaButtonReceiver
-                                    .getIntent().getComponent());
-                } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
-                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
-                            mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
-            }
-        }
-
-        private MediaSessionRecord getMediaButtonSessionLocked() {
-            return isGlobalPriorityActiveLocked()
-                    ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
-        }
-
-        private int getComponentType(@Nullable ComponentName componentName) {
-            if (componentName == null) {
-                return COMPONENT_TYPE_INVALID;
-            }
-            PackageManager pm = getContext().getPackageManager();
-            try {
-                ActivityInfo activityInfo = pm.getActivityInfo(componentName,
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                | PackageManager.GET_ACTIVITIES);
-                if (activityInfo != null) {
-                    return COMPONENT_TYPE_ACTIVITY;
-                }
-            } catch (NameNotFoundException e) {
-            }
-            try {
-                ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                | PackageManager.GET_SERVICES);
-                if (serviceInfo != null) {
-                    return COMPONENT_TYPE_SERVICE;
-                }
-            } catch (NameNotFoundException e) {
-            }
-            // Pick legacy behavior for BroadcastReceiver or unknown.
-            return COMPONENT_TYPE_BROADCAST;
-        }
-    }
-
-    final class SessionsListenerRecord implements IBinder.DeathRecipient {
-        public final IActiveSessionsListener listener;
-        public final ComponentName componentName;
-        public final int userId;
-        public final int pid;
-        public final int uid;
-
-        public SessionsListenerRecord(IActiveSessionsListener listener,
-                ComponentName componentName,
-                int userId, int pid, int uid) {
-            this.listener = listener;
-            this.componentName = componentName;
-            this.userId = userId;
-            this.pid = pid;
-            this.uid = uid;
-        }
-
-        @Override
-        public void binderDied() {
-            synchronized (mLock) {
-                mSessionsListeners.remove(this);
-            }
-        }
-    }
-
-    final class Session2TokensListenerRecord implements IBinder.DeathRecipient {
-        public final ISession2TokensListener listener;
-        public final int userId;
-
-        Session2TokensListenerRecord(ISession2TokensListener listener,
-                int userId) {
-            this.listener = listener;
-            this.userId = userId;
-        }
-
-        @Override
-        public void binderDied() {
-            synchronized (mLock) {
-                mSession2TokensListenerRecords.remove(this);
-            }
-        }
-    }
-
-    final class SettingsObserver extends ContentObserver {
-        private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
-                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
-        private SettingsObserver() {
-            super(null);
-        }
-
-        private void observe() {
-            mContentResolver.registerContentObserver(mSecureSettingsUri,
-                    false, this, USER_ALL);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateActiveSessionListeners();
-        }
-    }
-
-    class SessionManagerImpl extends ISessionManager.Stub {
-        private static final String EXTRA_WAKELOCK_ACQUIRED =
-                "android.media.AudioService.WAKELOCK_ACQUIRED";
-        private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
-
-        private boolean mVoiceButtonDown = false;
-        private boolean mVoiceButtonHandled = false;
-
-        @Override
-        public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
-                int userId) throws RemoteException {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                enforcePackageName(packageName, uid);
-                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
-                        false /* allowAll */, true /* requireFull */, "createSession", packageName);
-                if (cb == null) {
-                    throw new IllegalArgumentException("Controller callback cannot be null");
-                }
-                return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
-                        .getSessionBinder();
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void notifySession2Created(Session2Token sessionToken) throws RemoteException {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                if (DEBUG) {
-                    Log.d(TAG, "Session2 is created " + sessionToken);
-                }
-                if (uid != sessionToken.getUid()) {
-                    throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
-                            + " but actually=" + sessionToken.getUid());
-                }
-                Controller2Callback callback = new Controller2Callback(sessionToken);
-                // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
-                //       it's closed.
-                // TODO: Keep controller as well for better readability
-                //       because the GC behavior isn't straightforward.
-                MediaController2 controller = new MediaController2(getContext(), sessionToken,
-                        new HandlerExecutor(mHandler), callback);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public List<IBinder> getSessions(ComponentName componentName, int userId) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-
-            try {
-                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
-                ArrayList<IBinder> binders = new ArrayList<IBinder>();
-                synchronized (mLock) {
-                    List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
-                    for (MediaSessionRecord record : records) {
-                        binders.add(record.getControllerBinder().asBinder());
-                    }
-                }
-                return binders;
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public List<Session2Token> getSession2Tokens(int userId) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-
-            try {
-                // Check that they can make calls on behalf of the user and
-                // get the final user id
-                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
-                        true /* allowAll */, true /* requireFull */, "getSession2Tokens",
-                        null /* optional packageName */);
-                List<Session2Token> result;
-                synchronized (mLock) {
-                    result = getSession2TokensLocked(resolvedUserId);
-                }
-                return result;
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void addSessionsListener(IActiveSessionsListener listener,
-                ComponentName componentName, int userId) throws RemoteException {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-
-            try {
-                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
-                synchronized (mLock) {
-                    int index = findIndexOfSessionsListenerLocked(listener);
-                    if (index != -1) {
-                        Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
-                        return;
-                    }
-                    SessionsListenerRecord record = new SessionsListenerRecord(listener,
-                            componentName, resolvedUserId, pid, uid);
-                    try {
-                        listener.asBinder().linkToDeath(record, 0);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
-                        return;
-                    }
-                    mSessionsListeners.add(record);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void removeSessionsListener(IActiveSessionsListener listener)
-                throws RemoteException {
-            synchronized (mLock) {
-                int index = findIndexOfSessionsListenerLocked(listener);
-                if (index != -1) {
-                    SessionsListenerRecord record = mSessionsListeners.remove(index);
-                    try {
-                        record.listener.asBinder().unlinkToDeath(record, 0);
-                    } catch (Exception e) {
-                        // ignore exceptions, the record is being removed
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void addSession2TokensListener(ISession2TokensListener listener,
-                int userId) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-
-            try {
-                // Check that they can make calls on behalf of the user and get the final user id.
-                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
-                        true /* allowAll */, true /* requireFull */, "addSession2TokensListener",
-                        null /* optional packageName */);
-                synchronized (mLock) {
-                    int index = findIndexOfSession2TokensListenerLocked(listener);
-                    if (index >= 0) {
-                        Log.w(TAG, "addSession2TokensListener is already added, ignoring");
-                        return;
-                    }
-                    mSession2TokensListenerRecords.add(
-                            new Session2TokensListenerRecord(listener, resolvedUserId));
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void removeSession2TokensListener(ISession2TokensListener listener) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-
-            try {
-                synchronized (mLock) {
-                    int index = findIndexOfSession2TokensListenerLocked(listener);
-                    if (index >= 0) {
-                        Session2TokensListenerRecord listenerRecord =
-                                mSession2TokensListenerRecords.remove(index);
-                        try {
-                            listenerRecord.listener.asBinder().unlinkToDeath(listenerRecord, 0);
-                        } catch (Exception e) {
-                            // Ignore exception.
-                        }
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        /**
-         * Handles the dispatching of the media button events to one of the
-         * registered listeners, or if there was none, broadcast an
-         * ACTION_MEDIA_BUTTON intent to the rest of the system.
-         *
-         * @param packageName The caller package
-         * @param asSystemService {@code true} if the event sent to the session as if it was come
-         *          from the system service instead of the app process. This helps sessions to
-         *          distinguish between the key injection by the app and key events from the
-         *          hardware devices. Should be used only when the volume key events aren't handled
-         *          by foreground activity. {@code false} otherwise to tell session about the real
-         *          caller.
-         * @param keyEvent a non-null KeyEvent whose key code is one of the
-         *            supported media buttons
-         * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
-         *            while this key event is dispatched.
-         */
-        @Override
-        public void dispatchMediaKeyEvent(String packageName, boolean asSystemService,
-                KeyEvent keyEvent, boolean needWakeLock) {
-            if (keyEvent == null || !KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
-                Log.w(TAG, "Attempted to dispatch null or non-media key event.");
-                return;
-            }
-
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                if (DEBUG) {
-                    Log.d(TAG, "dispatchMediaKeyEvent, pkg=" + packageName + " pid=" + pid
-                            + ", uid=" + uid + ", asSystem=" + asSystemService + ", event="
-                            + keyEvent);
-                }
-                if (!isUserSetupComplete()) {
-                    // Global media key handling can have the side-effect of starting new
-                    // activities which is undesirable while setup is in progress.
-                    Slog.i(TAG, "Not dispatching media key event because user "
-                            + "setup is in progress.");
-                    return;
-                }
-
-                synchronized (mLock) {
-                    boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
-                    if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
-                        // Prevent dispatching key event through reflection while the global
-                        // priority session is active.
-                        Slog.i(TAG, "Only the system can dispatch media key event "
-                                + "to the global priority session.");
-                        return;
-                    }
-                    if (!isGlobalPriorityActive) {
-                        if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
-                            if (DEBUG_KEY_EVENT) {
-                                Log.d(TAG, "Send " + keyEvent + " to the media key listener");
-                            }
-                            try {
-                                mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
-                                        new MediaKeyListenerResultReceiver(packageName, pid, uid,
-                                                asSystemService, keyEvent, needWakeLock));
-                                return;
-                            } catch (RemoteException e) {
-                                Log.w(TAG, "Failed to send " + keyEvent
-                                        + " to the media key listener");
-                            }
-                        }
-                    }
-                    if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
-                        handleVoiceKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
-                                needWakeLock);
-                    } else {
-                        dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
-                                keyEvent, needWakeLock);
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void setCallback(ICallback callback) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
-                    throw new SecurityException("Only Bluetooth service processes can set"
-                            + " Callback");
-                }
-                synchronized (mLock) {
-                    int userId = UserHandle.getUserId(uid);
-                    FullUserRecord user = getFullUserRecordLocked(userId);
-                    if (user == null || user.mFullUserId != userId) {
-                        Log.w(TAG, "Only the full user can set the callback"
-                                + ", userId=" + userId);
-                        return;
-                    }
-                    user.mCallback = callback;
-                    Log.d(TAG, "The callback " + user.mCallback
-                            + " is set by " + getCallingPackageName(uid));
-                    if (user.mCallback == null) {
-                        return;
-                    }
-                    try {
-                        user.mCallback.asBinder().linkToDeath(
-                                new IBinder.DeathRecipient() {
-                                    @Override
-                                    public void binderDied() {
-                                        synchronized (mLock) {
-                                            user.mCallback = null;
-                                        }
-                                    }
-                                }, 0);
-                        user.pushAddressedPlayerChangedLocked();
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "Failed to set callback", e);
-                        user.mCallback = null;
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
-                if (getContext().checkPermission(
-                        android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
-                            != PackageManager.PERMISSION_GRANTED) {
-                    throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
-                            " permission.");
-                }
-
-                synchronized (mLock) {
-                    int userId = UserHandle.getUserId(uid);
-                    FullUserRecord user = getFullUserRecordLocked(userId);
-                    if (user == null || user.mFullUserId != userId) {
-                        Log.w(TAG, "Only the full user can set the volume key long-press listener"
-                                + ", userId=" + userId);
-                        return;
-                    }
-                    if (user.mOnVolumeKeyLongPressListener != null &&
-                            user.mOnVolumeKeyLongPressListenerUid != uid) {
-                        Log.w(TAG, "The volume key long-press listener cannot be reset"
-                                + " by another app , mOnVolumeKeyLongPressListener="
-                                + user.mOnVolumeKeyLongPressListenerUid
-                                + ", uid=" + uid);
-                        return;
-                    }
-
-                    user.mOnVolumeKeyLongPressListener = listener;
-                    user.mOnVolumeKeyLongPressListenerUid = uid;
-
-                    Log.d(TAG, "The volume key long-press listener "
-                            + listener + " is set by " + getCallingPackageName(uid));
-
-                    if (user.mOnVolumeKeyLongPressListener != null) {
-                        try {
-                            user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
-                                    new IBinder.DeathRecipient() {
-                                        @Override
-                                        public void binderDied() {
-                                            synchronized (mLock) {
-                                                user.mOnVolumeKeyLongPressListener = null;
-                                            }
-                                        }
-                                    }, 0);
-                        } catch (RemoteException e) {
-                            Log.w(TAG, "Failed to set death recipient "
-                                    + user.mOnVolumeKeyLongPressListener);
-                            user.mOnVolumeKeyLongPressListener = null;
-                        }
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                // Enforce SET_MEDIA_KEY_LISTENER permission.
-                if (getContext().checkPermission(
-                        android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
-                            != PackageManager.PERMISSION_GRANTED) {
-                    throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
-                            " permission.");
-                }
-
-                synchronized (mLock) {
-                    int userId = UserHandle.getUserId(uid);
-                    FullUserRecord user = getFullUserRecordLocked(userId);
-                    if (user == null || user.mFullUserId != userId) {
-                        Log.w(TAG, "Only the full user can set the media key listener"
-                                + ", userId=" + userId);
-                        return;
-                    }
-                    if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
-                        Log.w(TAG, "The media key listener cannot be reset by another app. "
-                                + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
-                                + ", uid=" + uid);
-                        return;
-                    }
-
-                    user.mOnMediaKeyListener = listener;
-                    user.mOnMediaKeyListenerUid = uid;
-
-                    Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
-                            + " is set by " + getCallingPackageName(uid));
-
-                    if (user.mOnMediaKeyListener != null) {
-                        try {
-                            user.mOnMediaKeyListener.asBinder().linkToDeath(
-                                    new IBinder.DeathRecipient() {
-                                        @Override
-                                        public void binderDied() {
-                                            synchronized (mLock) {
-                                                user.mOnMediaKeyListener = null;
-                                            }
-                                        }
-                                    }, 0);
-                        } catch (RemoteException e) {
-                            Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
-                            user.mOnMediaKeyListener = null;
-                        }
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        /**
-         * Handles the dispatching of the volume button events to one of the
-         * registered listeners. If there's a volume key long-press listener and
-         * there's no active global priority session, long-pressess will be sent to the
-         * long-press listener instead of adjusting volume.
-         *
-         * @param packageName The caller's package name, obtained by Context#getPackageName()
-         * @param opPackageName The caller's op package name, obtained by Context#getOpPackageName()
-         * @param asSystemService {@code true} if the event sent to the session as if it was come
-         *          from the system service instead of the app process. This helps sessions to
-         *          distinguish between the key injection by the app and key events from the
-         *          hardware devices. Should be used only when the volume key events aren't handled
-         *          by foreground activity. {@code false} otherwise to tell session about the real
-         *          caller.
-         * @param keyEvent a non-null KeyEvent whose key code is one of the
-         *            {@link KeyEvent#KEYCODE_VOLUME_UP},
-         *            {@link KeyEvent#KEYCODE_VOLUME_DOWN},
-         *            or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
-         * @param stream stream type to adjust volume.
-         * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
-         */
-        @Override
-        public void dispatchVolumeKeyEvent(String packageName, String opPackageName,
-                boolean asSystemService, KeyEvent keyEvent, int stream, boolean musicOnly) {
-            if (keyEvent == null ||
-                    (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
-                             && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
-                             && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
-                Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
-                return;
-            }
-
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-
-            if (DEBUG_KEY_EVENT) {
-                Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + ", pid=" + pid + ", uid="
-                        + uid + ", asSystem=" + asSystemService + ", event=" + keyEvent);
-            }
-
-            try {
-                synchronized (mLock) {
-                    if (isGlobalPriorityActiveLocked()
-                            || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
-                        dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
-                                asSystemService, keyEvent, stream, musicOnly);
-                    } else {
-                        // TODO: Consider the case when both volume up and down keys are pressed
-                        //       at the same time.
-                        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
-                            if (keyEvent.getRepeatCount() == 0) {
-                                // Keeps the copy of the KeyEvent because it can be reused.
-                                mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
-                                        KeyEvent.obtain(keyEvent);
-                                mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
-                                mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
-                                mHandler.sendMessageDelayed(
-                                        mHandler.obtainMessage(
-                                                MessageHandler.MSG_VOLUME_INITIAL_DOWN,
-                                                mCurrentFullUserRecord.mFullUserId, 0),
-                                        mLongPressTimeout);
-                            }
-                            if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
-                                mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
-                                if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
-                                    dispatchVolumeKeyLongPressLocked(
-                                            mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
-                                    // Mark that the key is already handled.
-                                    mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
-                                }
-                                dispatchVolumeKeyLongPressLocked(keyEvent);
-                            }
-                        } else { // if up
-                            mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
-                            if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
-                                    && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
-                                            .getDownTime() == keyEvent.getDownTime()) {
-                                // Short-press. Should change volume.
-                                dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
-                                        asSystemService,
-                                        mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
-                                        mCurrentFullUserRecord.mInitialDownVolumeStream,
-                                        mCurrentFullUserRecord.mInitialDownMusicOnly);
-                                dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
-                                        asSystemService, keyEvent, stream, musicOnly);
-                            } else {
-                                dispatchVolumeKeyLongPressLocked(keyEvent);
-                            }
-                        }
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        private void dispatchVolumeKeyEventLocked(String packageName, String opPackageName, int pid,
-                int uid, boolean asSystemService, KeyEvent keyEvent, int stream,
-                boolean musicOnly) {
-            boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
-            boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
-            int direction = 0;
-            boolean isMute = false;
-            switch (keyEvent.getKeyCode()) {
-                case KeyEvent.KEYCODE_VOLUME_UP:
-                    direction = AudioManager.ADJUST_RAISE;
-                    break;
-                case KeyEvent.KEYCODE_VOLUME_DOWN:
-                    direction = AudioManager.ADJUST_LOWER;
-                    break;
-                case KeyEvent.KEYCODE_VOLUME_MUTE:
-                    isMute = true;
-                    break;
-            }
-            if (down || up) {
-                int flags = AudioManager.FLAG_FROM_KEY;
-                if (musicOnly) {
-                    // This flag is used when the screen is off to only affect active media.
-                    flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
-                } else {
-                    // These flags are consistent with the home screen
-                    if (up) {
-                        flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
-                    } else {
-                        flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
-                    }
-                }
-                if (direction != 0) {
-                    // If this is action up we want to send a beep for non-music events
-                    if (up) {
-                        direction = 0;
-                    }
-                    dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
-                            asSystemService, stream, direction, flags);
-                } else if (isMute) {
-                    if (down && keyEvent.getRepeatCount() == 0) {
-                        dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
-                                asSystemService, stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void dispatchAdjustVolume(String packageName, String opPackageName,
-                int suggestedStream, int delta, int flags) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mLock) {
-                    dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid, false,
-                            suggestedStream, delta, flags);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void setRemoteVolumeController(IRemoteVolumeController rvc) {
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                enforceSystemUiPermission("listen for volume changes", pid, uid);
-                mRvc = rvc;
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public boolean isGlobalPriorityActive() {
-            synchronized (mLock) {
-                return isGlobalPriorityActiveLocked();
-            }
-        }
-
-        @Override
-        public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
-
-            pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
-            pw.println();
-
-            synchronized (mLock) {
-                pw.println(mSessionsListeners.size() + " sessions listeners.");
-                pw.println("Global priority session is " + mGlobalPrioritySession);
-                if (mGlobalPrioritySession != null) {
-                    mGlobalPrioritySession.dump(pw, "  ");
-                }
-                pw.println("User Records:");
-                int count = mUserRecords.size();
-                for (int i = 0; i < count; i++) {
-                    mUserRecords.valueAt(i).dumpLocked(pw, "");
-                }
-                mAudioPlayerStateMonitor.dump(getContext(), pw, "");
-            }
-        }
-
-        /**
-         * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
-         * permission or an enabled notification listener)
-         *
-         * @param controllerPackageName package name of the controller app
-         * @param controllerPid pid of the controller app
-         * @param controllerUid uid of the controller app
-         */
-        @Override
-        public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
-                throws RemoteException {
-            final int uid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            try {
-                // Don't perform sanity check between controllerPackageName and controllerUid.
-                // When an (activity|service) runs on the another apps process by specifying
-                // android:process in the AndroidManifest.xml, then PID and UID would have the
-                // running process' information instead of the (activity|service) that has created
-                // MediaController.
-                // Note that we can use Context#getOpPackageName() instead of
-                // Context#getPackageName() for getting package name that matches with the PID/UID,
-                // but it doesn't tell which package has created the MediaController, so useless.
-                return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
-                        controllerPid, controllerUid);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        // For MediaSession
-        private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
-                final int uid) {
-            String packageName = null;
-            if (componentName != null) {
-                // If they gave us a component name verify they own the
-                // package
-                packageName = componentName.getPackageName();
-                enforcePackageName(packageName, uid);
-            }
-            // Check that they can make calls on behalf of the user and
-            // get the final user id
-            int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
-                    true /* allowAll */, true /* requireFull */, "getSessions", packageName);
-            // Check if they have the permissions or their component is
-            // enabled for the user they're calling from.
-            enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
-            return resolvedUserId;
-        }
-
-        private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
-                int pid, int uid) throws RemoteException {
-            // Allow API calls from the System UI
-            if (isCurrentVolumeController(pid, uid)) {
-                return true;
-            }
-
-            // Check if it's system server or has MEDIA_CONTENT_CONTROL.
-            // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
-            // check here.
-            if (uid == Process.SYSTEM_UID || getContext().checkPermission(
-                    android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
-                    == PackageManager.PERMISSION_GRANTED) {
-                return true;
-            } else if (DEBUG) {
-                Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
-            }
-
-            // You may not access another user's content as an enabled listener.
-            final int userId = UserHandle.getUserId(uid);
-            if (resolvedUserId != userId) {
-                return false;
-            }
-
-            // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
-            //               String pkgName) to notification team for optimization
-            final List<ComponentName> enabledNotificationListeners =
-                    mNotificationManager.getEnabledNotificationListeners(userId);
-            if (enabledNotificationListeners != null) {
-                for (int i = 0; i < enabledNotificationListeners.size(); i++) {
-                    if (TextUtils.equals(packageName,
-                            enabledNotificationListeners.get(i).getPackageName())) {
-                        return true;
-                    }
-                }
-            }
-            if (DEBUG) {
-                Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
-                        + "notification listener");
-            }
-            return false;
-        }
-
-        private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
-                int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
-            MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
-                    : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
-
-            boolean preferSuggestedStream = false;
-            if (isValidLocalStreamType(suggestedStream)
-                    && AudioSystem.isStreamActive(suggestedStream, 0)) {
-                preferSuggestedStream = true;
-            }
-            if (DEBUG_KEY_EVENT) {
-                Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
-                        + flags + ", suggestedStream=" + suggestedStream
-                        + ", preferSuggestedStream=" + preferSuggestedStream);
-            }
-            if (session == null || preferSuggestedStream) {
-                if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
-                        && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "No active session to adjust, skipping media only volume event");
-                    }
-                    return;
-                }
-
-                // Execute mAudioService.adjustSuggestedStreamVolume() on
-                // handler thread of MediaSessionService.
-                // This will release the MediaSessionService.mLock sooner and avoid
-                // a potential deadlock between MediaSessionService.mLock and
-                // ActivityManagerService lock.
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        final String callingOpPackageName;
-                        final int callingUid;
-                        if (asSystemService) {
-                            callingOpPackageName = getContext().getOpPackageName();
-                            callingUid = Process.myUid();
-                        } else {
-                            callingOpPackageName = opPackageName;
-                            callingUid = uid;
-                        }
-                        try {
-                            mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream,
-                                    direction, flags, callingOpPackageName, callingUid);
-                        } catch (SecurityException | IllegalArgumentException e) {
-                            Log.e(TAG, "Cannot adjust volume: direction=" + direction
-                                    + ", suggestedStream=" + suggestedStream + ", flags=" + flags
-                                    + ", packageName=" + packageName + ", uid=" + uid
-                                    + ", asSystemService=" + asSystemService, e);
-                        }
-                    }
-                });
-            } else {
-                session.adjustVolume(packageName, opPackageName, pid, uid, null, asSystemService,
-                        direction, flags, true);
-            }
-        }
-
-        private void handleVoiceKeyEventLocked(String packageName, int pid, int uid,
-                boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
-            int action = keyEvent.getAction();
-            boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
-            if (action == KeyEvent.ACTION_DOWN) {
-                if (keyEvent.getRepeatCount() == 0) {
-                    mVoiceButtonDown = true;
-                    mVoiceButtonHandled = false;
-                } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
-                    mVoiceButtonHandled = true;
-                    startVoiceInput(needWakeLock);
-                }
-            } else if (action == KeyEvent.ACTION_UP) {
-                if (mVoiceButtonDown) {
-                    mVoiceButtonDown = false;
-                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
-                        // Resend the down then send this event through
-                        KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
-                        dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
-                                downEvent, needWakeLock);
-                        dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
-                                keyEvent, needWakeLock);
-                    }
-                }
-            }
-        }
-
-        private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
-                boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
-            MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
-            if (session != null) {
-                if (DEBUG_KEY_EVENT) {
-                    Log.d(TAG, "Sending " + keyEvent + " to " + session);
-                }
-                if (needWakeLock) {
-                    mKeyEventReceiver.aquireWakeLockLocked();
-                }
-                // If we don't need a wakelock use -1 as the id so we won't release it later.
-                session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
-                        needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
-                        mKeyEventReceiver);
-                if (mCurrentFullUserRecord.mCallback != null) {
-                    try {
-                        mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
-                                keyEvent, new MediaSession.Token(session.getControllerBinder()));
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "Failed to send callback", e);
-                    }
-                }
-            } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
-                    || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
-                if (needWakeLock) {
-                    mKeyEventReceiver.aquireWakeLockLocked();
-                }
-                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-                mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-                // TODO: Find a way to also send PID/UID in secure way.
-                String callerPackageName =
-                        (asSystemService) ? getContext().getPackageName() : packageName;
-                mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
-                try {
-                    if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
-                        PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
-                        if (DEBUG_KEY_EVENT) {
-                            Log.d(TAG, "Sending " + keyEvent
-                                    + " to the last known PendingIntent " + receiver);
-                        }
-                        receiver.send(getContext(),
-                                needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
-                                mediaButtonIntent, mKeyEventReceiver, mHandler);
-                        if (mCurrentFullUserRecord.mCallback != null) {
-                            ComponentName componentName = mCurrentFullUserRecord
-                                    .mLastMediaButtonReceiver.getIntent().getComponent();
-                            if (componentName != null) {
-                                mCurrentFullUserRecord.mCallback
-                                        .onMediaKeyEventDispatchedToMediaButtonReceiver(
-                                                keyEvent, componentName);
-                            }
-                        }
-                    } else {
-                        ComponentName receiver =
-                                mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
-                        int componentType = mCurrentFullUserRecord
-                                .mRestoredMediaButtonReceiverComponentType;
-                        UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord
-                                .mRestoredMediaButtonReceiverUserId);
-                        if (DEBUG_KEY_EVENT) {
-                            Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
-                                    + receiver + ", type=" + componentType);
-                        }
-                        mediaButtonIntent.setComponent(receiver);
-                        try {
-                            switch (componentType) {
-                                case FullUserRecord.COMPONENT_TYPE_ACTIVITY:
-                                    getContext().startActivityAsUser(mediaButtonIntent, userHandle);
-                                    break;
-                                case FullUserRecord.COMPONENT_TYPE_SERVICE:
-                                    getContext().startForegroundServiceAsUser(mediaButtonIntent,
-                                            userHandle);
-                                    break;
-                                default:
-                                    // Legacy behavior for other cases.
-                                    getContext().sendBroadcastAsUser(mediaButtonIntent, userHandle);
-                            }
-                        } catch (Exception e) {
-                            Log.w(TAG, "Error sending media button to the restored intent "
-                                    + receiver + ", type=" + componentType, e);
-                        }
-                        if (mCurrentFullUserRecord.mCallback != null) {
-                            mCurrentFullUserRecord.mCallback
-                                    .onMediaKeyEventDispatchedToMediaButtonReceiver(
-                                            keyEvent, receiver);
-                        }
-                    }
-                } catch (CanceledException e) {
-                    Log.i(TAG, "Error sending key event to media button receiver "
-                            + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to send callback", e);
-                }
-            }
-        }
-
-        private void startVoiceInput(boolean needWakeLock) {
-            Intent voiceIntent = null;
-            // select which type of search to launch:
-            // - screen on and device unlocked: action is ACTION_WEB_SEARCH
-            // - device locked or screen off: action is
-            // ACTION_VOICE_SEARCH_HANDS_FREE
-            // with EXTRA_SECURE set to true if the device is securely locked
-            PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
-            boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
-            if (!isLocked && pm.isScreenOn()) {
-                voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
-                Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
-            } else {
-                voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
-                voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
-                        isLocked && mKeyguardManager.isKeyguardSecure());
-                Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
-            }
-            // start the search activity
-            if (needWakeLock) {
-                mMediaEventWakeLock.acquire();
-            }
-            try {
-                if (voiceIntent != null) {
-                    voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                    if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
-                    getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
-                }
-            } catch (ActivityNotFoundException e) {
-                Log.w(TAG, "No activity for search: " + e);
-            } finally {
-                if (needWakeLock) {
-                    mMediaEventWakeLock.release();
-                }
-            }
-        }
-
-        private boolean isVoiceKey(int keyCode) {
-            return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
-                    || (!mHasFeatureLeanback && keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        }
-
-        private boolean isUserSetupComplete() {
-            return Settings.Secure.getIntForUser(getContext().getContentResolver(),
-                    Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
-        }
-
-        // we only handle public stream types, which are 0-5
-        private boolean isValidLocalStreamType(int streamType) {
-            return streamType >= AudioManager.STREAM_VOICE_CALL
-                    && streamType <= AudioManager.STREAM_NOTIFICATION;
-        }
-
-        private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
-            private final String mPackageName;
-            private final int mPid;
-            private final int mUid;
-            private final boolean mAsSystemService;
-            private final KeyEvent mKeyEvent;
-            private final boolean mNeedWakeLock;
-            private boolean mHandled;
-
-            private MediaKeyListenerResultReceiver(String packageName, int pid, int uid,
-                    boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
-                super(mHandler);
-                mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
-                mPackageName = packageName;
-                mPid = pid;
-                mUid = uid;
-                mAsSystemService = asSystemService;
-                mKeyEvent = keyEvent;
-                mNeedWakeLock = needWakeLock;
-            }
-
-            @Override
-            public void run() {
-                Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
-                dispatchMediaKeyEvent();
-            }
-
-            @Override
-            protected void onReceiveResult(int resultCode, Bundle resultData) {
-                if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
-                    mHandled = true;
-                    mHandler.removeCallbacks(this);
-                    return;
-                }
-                dispatchMediaKeyEvent();
-            }
-
-            private void dispatchMediaKeyEvent() {
-                if (mHandled) {
-                    return;
-                }
-                mHandled = true;
-                mHandler.removeCallbacks(this);
-                synchronized (mLock) {
-                    if (!isGlobalPriorityActiveLocked()
-                            && isVoiceKey(mKeyEvent.getKeyCode())) {
-                        handleVoiceKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
-                                mKeyEvent, mNeedWakeLock);
-                    } else {
-                        dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
-                                mKeyEvent, mNeedWakeLock);
-                    }
-                }
-            }
-        }
-
-        private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
-
-        class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
-                PendingIntent.OnFinished {
-            private final Handler mHandler;
-            private int mRefCount = 0;
-            private int mLastTimeoutId = 0;
-
-            public KeyEventWakeLockReceiver(Handler handler) {
-                super(handler);
-                mHandler = handler;
-            }
-
-            public void onTimeout() {
-                synchronized (mLock) {
-                    if (mRefCount == 0) {
-                        // We've already released it, so just return
-                        return;
-                    }
-                    mLastTimeoutId++;
-                    mRefCount = 0;
-                    releaseWakeLockLocked();
-                }
-            }
-
-            public void aquireWakeLockLocked() {
-                if (mRefCount == 0) {
-                    mMediaEventWakeLock.acquire();
-                }
-                mRefCount++;
-                mHandler.removeCallbacks(this);
-                mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
-
-            }
-
-            @Override
-            public void run() {
-                onTimeout();
-            }
-
-            @Override
-            protected void onReceiveResult(int resultCode, Bundle resultData) {
-                if (resultCode < mLastTimeoutId) {
-                    // Ignore results from calls that were before the last
-                    // timeout, just in case.
-                    return;
-                } else {
-                    synchronized (mLock) {
-                        if (mRefCount > 0) {
-                            mRefCount--;
-                            if (mRefCount == 0) {
-                                releaseWakeLockLocked();
-                            }
-                        }
-                    }
-                }
-            }
-
-            private void releaseWakeLockLocked() {
-                mMediaEventWakeLock.release();
-                mHandler.removeCallbacks(this);
-            }
-
-            @Override
-            public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
-                    String resultData, Bundle resultExtras) {
-                onReceiveResult(resultCode, null);
-            }
-        };
-
-        BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent == null) {
-                    return;
-                }
-                Bundle extras = intent.getExtras();
-                if (extras == null) {
-                    return;
-                }
-                synchronized (mLock) {
-                    if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
-                            && mMediaEventWakeLock.isHeld()) {
-                        mMediaEventWakeLock.release();
-                    }
-                }
-            }
-        };
-    }
-
-    final class MessageHandler extends Handler {
-        private static final int MSG_SESSIONS_CHANGED = 1;
-        private static final int MSG_VOLUME_INITIAL_DOWN = 2;
-        private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_SESSIONS_CHANGED:
-                    pushSessionsChanged((int) msg.obj);
-                    break;
-                case MSG_VOLUME_INITIAL_DOWN:
-                    synchronized (mLock) {
-                        FullUserRecord user = mUserRecords.get((int) msg.arg1);
-                        if (user != null && user.mInitialDownVolumeKeyEvent != null) {
-                            dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
-                            // Mark that the key is already handled.
-                            user.mInitialDownVolumeKeyEvent = null;
-                        }
-                    }
-                    break;
-            }
-        }
-
-        public void postSessionsChanged(int userId) {
-            // Use object instead of the arguments when posting message to remove pending requests.
-            Integer userIdInteger = mIntegerCache.get(userId);
-            if (userIdInteger == null) {
-                userIdInteger = Integer.valueOf(userId);
-                mIntegerCache.put(userId, userIdInteger);
-            }
-            removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
-            obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
-        }
-    }
-
-    private class Controller2Callback extends MediaController2.ControllerCallback {
-        private final Session2Token mToken;
-
-        Controller2Callback(Session2Token token) {
-            mToken = token;
-        }
-
-        @Override
-        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).add(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).remove(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
+    abstract static class ServiceImpl {
+        public abstract void onStart();
+        public abstract void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session);
+        public abstract void onSessionPlaystateChanged(
+                MediaSessionRecord record, int oldState, int newState);
+        public abstract void onSessionPlaybackTypeChanged(MediaSessionRecord record);
+        public abstract void onStartUser(int userId);
+        public abstract void onSwitchUser(int userId);
+        public abstract void monitor();
+        public abstract void onMediaButtonReceiverChanged(MediaSessionRecord record);
+        protected abstract void enforcePhoneStatePermission(int pid, int uid);
+        abstract void updateSession(MediaSessionRecord record);
+        abstract void setGlobalPrioritySession(MediaSessionRecord record);
+        abstract List<Session2Token> getSession2TokensLocked(int userId);
+        abstract void onStopUser(int userId);
+        abstract void sessionDied(MediaSessionRecord session);
+        abstract void destroySession(MediaSessionRecord session);
+        abstract void pushSession2TokensChangedLocked(int userId);
+        abstract Context getContext();
+        abstract IBinder getServiceBinder();
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
new file mode 100644
index 0000000..f374c6d
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -0,0 +1,2142 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.server.media;
+
+import static android.os.UserHandle.USER_ALL;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.INotificationManager;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.media.AudioManagerInternal;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioSystem;
+import android.media.IAudioService;
+import android.media.IRemoteVolumeController;
+import android.media.MediaController2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.media.session.IActiveSessionsListener;
+import android.media.session.ICallback;
+import android.media.session.IOnMediaKeyListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
+import android.media.session.ISession;
+import android.media.session.ISession2TokensListener;
+import android.media.session.ISessionManager;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.SessionCallbackLink;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.speech.RecognizerIntent;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * System implementation of MediaSessionManager
+ */
+public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl {
+    private static final String TAG = "MediaSessionService";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    // Leave log for key event always.
+    private static final boolean DEBUG_KEY_EVENT = true;
+
+    private static final int WAKELOCK_TIMEOUT = 5000;
+    private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
+
+    private final Context mContext;
+    private final SessionManagerImpl mSessionManagerImpl;
+    private final MessageHandler mHandler = new MessageHandler();
+    private final PowerManager.WakeLock mMediaEventWakeLock;
+    private final int mLongPressTimeout;
+    private final INotificationManager mNotificationManager;
+    private final Object mLock = new Object();
+    // Keeps the full user id for each user.
+    @GuardedBy("mLock")
+    private final SparseIntArray mFullUserIds = new SparseIntArray();
+    @GuardedBy("mLock")
+    private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
+    @GuardedBy("mLock")
+    private final ArrayList<SessionsListenerRecord> mSessionsListeners =
+            new ArrayList<SessionsListenerRecord>();
+    // Map user id as index to list of Session2Tokens
+    // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
+    //       one place.
+    @GuardedBy("mLock")
+    private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
+            new ArrayList<>();
+
+    private KeyguardManager mKeyguardManager;
+    private IAudioService mAudioService;
+    private AudioManagerInternal mAudioManagerInternal;
+    private ActivityManager mActivityManager;
+    private ContentResolver mContentResolver;
+    private SettingsObserver mSettingsObserver;
+    private boolean mHasFeatureLeanback;
+
+    // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
+    // It's always not null after the MediaSessionService is started.
+    private FullUserRecord mCurrentFullUserRecord;
+    private MediaSessionRecord mGlobalPrioritySession;
+    private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
+
+    // Used to notify system UI when remote volume was changed. TODO find a
+    // better way to handle this.
+    private IRemoteVolumeController mRvc;
+
+    public MediaSessionServiceImpl(Context context) {
+        mContext = context;
+        mSessionManagerImpl = new SessionManagerImpl();
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
+        mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
+        mNotificationManager = INotificationManager.Stub.asInterface(
+                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+    }
+
+    Context getContext() {
+        return mContext;
+    }
+
+    IBinder getServiceBinder() {
+        return mSessionManagerImpl;
+    }
+
+    @Override
+    public void onStart() {
+        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        mAudioService = getAudioService();
+        mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+        mActivityManager =
+                (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+        mAudioPlayerStateMonitor.registerListener(
+                (config, isRemoved) -> {
+                    if (isRemoved || !config.isActive() || config.getPlayerType()
+                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+                        return;
+                    }
+                    synchronized (mLock) {
+                        FullUserRecord user = getFullUserRecordLocked(
+                                UserHandle.getUserId(config.getClientUid()));
+                        if (user != null) {
+                            user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                        }
+                    }
+                }, null /* handler */);
+        mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
+        mContentResolver = mContext.getContentResolver();
+        mSettingsObserver = new SettingsObserver();
+        mSettingsObserver.observe();
+        mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LEANBACK);
+
+        updateUser();
+    }
+
+    private IAudioService getAudioService() {
+        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+        return IAudioService.Stub.asInterface(b);
+    }
+
+    private boolean isGlobalPriorityActiveLocked() {
+        return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
+    }
+
+    @Override
+    public void updateSession(MediaSessionRecord record) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            if (user == null) {
+                Log.w(TAG, "Unknown session updated. Ignoring.");
+                return;
+            }
+            if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
+                if (DEBUG_KEY_EVENT) {
+                    Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
+                }
+                user.pushAddressedPlayerChangedLocked();
+            } else {
+                if (!user.mPriorityStack.contains(record)) {
+                    Log.w(TAG, "Unknown session updated. Ignoring.");
+                    return;
+                }
+                user.mPriorityStack.onSessionStateChange(record);
+            }
+            mHandler.postSessionsChanged(record.getUserId());
+        }
+    }
+
+    @Override
+    public void setGlobalPrioritySession(MediaSessionRecord record) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            if (mGlobalPrioritySession != record) {
+                Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
+                        + " to " + record);
+                mGlobalPrioritySession = record;
+                if (user != null && user.mPriorityStack.contains(record)) {
+                    // Handle the global priority session separately.
+                    // Otherwise, it can be the media button session regardless of the active state
+                    // because it or other system components might have been the lastly played media
+                    // app.
+                    user.mPriorityStack.removeSession(record);
+                }
+            }
+        }
+    }
+
+    private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
+        List<MediaSessionRecord> records = new ArrayList<>();
+        if (userId == USER_ALL) {
+            int size = mUserRecords.size();
+            for (int i = 0; i < size; i++) {
+                records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
+            }
+        } else {
+            FullUserRecord user = getFullUserRecordLocked(userId);
+            if (user == null) {
+                Log.w(TAG, "getSessions failed. Unknown user " + userId);
+                return records;
+            }
+            records.addAll(user.mPriorityStack.getActiveSessions(userId));
+        }
+
+        // Return global priority session at the first whenever it's asked.
+        if (isGlobalPriorityActiveLocked()
+                && (userId == USER_ALL || userId == mGlobalPrioritySession.getUserId())) {
+            records.add(0, mGlobalPrioritySession);
+        }
+        return records;
+    }
+
+    List<Session2Token> getSession2TokensLocked(int userId) {
+        List<Session2Token> list = new ArrayList<>();
+        if (userId == USER_ALL) {
+            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+                list.addAll(mSession2TokensPerUser.valueAt(i));
+            }
+        } else {
+            list.addAll(mSession2TokensPerUser.get(userId));
+        }
+        return list;
+    }
+
+    /**
+     * Tells the system UI that volume has changed on an active remote session.
+     */
+    public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
+        if (mRvc == null || !session.isActive()) {
+            return;
+        }
+        try {
+            mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Error sending volume change to system UI.", e);
+        }
+    }
+
+    @Override
+    public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            if (user == null || !user.mPriorityStack.contains(record)) {
+                Log.d(TAG, "Unknown session changed playback state. Ignoring.");
+                return;
+            }
+            user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
+        }
+    }
+
+    @Override
+    public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            if (user == null || !user.mPriorityStack.contains(record)) {
+                Log.d(TAG, "Unknown session changed playback type. Ignoring.");
+                return;
+            }
+            pushRemoteVolumeUpdateLocked(record.getUserId());
+        }
+    }
+
+    @Override
+    public void onStartUser(int userId) {
+        if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
+        updateUser();
+    }
+
+    @Override
+    public void onSwitchUser(int userId) {
+        if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
+        updateUser();
+    }
+
+    // Called when the user with the userId is removed.
+    @Override
+    public void onStopUser(int userId) {
+        if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
+        synchronized (mLock) {
+            // TODO: Also handle removing user in updateUser() because adding/switching user is
+            //       handled in updateUser().
+            FullUserRecord user = getFullUserRecordLocked(userId);
+            if (user != null) {
+                if (user.mFullUserId == userId) {
+                    user.destroySessionsForUserLocked(USER_ALL);
+                    mUserRecords.remove(userId);
+                } else {
+                    user.destroySessionsForUserLocked(userId);
+                }
+            }
+            mSession2TokensPerUser.remove(userId);
+            updateUser();
+        }
+    }
+
+    @Override
+    public void monitor() {
+        synchronized (mLock) {
+            // Check for deadlock
+        }
+    }
+
+    protected void enforcePhoneStatePermission(int pid, int uid) {
+        if (mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
+        }
+    }
+
+    void sessionDied(MediaSessionRecord session) {
+        synchronized (mLock) {
+            destroySessionLocked(session);
+        }
+    }
+
+    void destroySession(MediaSessionRecord session) {
+        synchronized (mLock) {
+            destroySessionLocked(session);
+        }
+    }
+
+    private void updateUser() {
+        synchronized (mLock) {
+            UserManager manager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+            mFullUserIds.clear();
+            List<UserInfo> allUsers = manager.getUsers();
+            if (allUsers != null) {
+                for (UserInfo userInfo : allUsers) {
+                    if (userInfo.isManagedProfile()) {
+                        mFullUserIds.put(userInfo.id, userInfo.profileGroupId);
+                    } else {
+                        mFullUserIds.put(userInfo.id, userInfo.id);
+                        if (mUserRecords.get(userInfo.id) == null) {
+                            mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
+                        }
+                    }
+                    if (mSession2TokensPerUser.get(userInfo.id) == null) {
+                        mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
+                    }
+                }
+            }
+            // Ensure that the current full user exists.
+            int currentFullUserId = ActivityManager.getCurrentUser();
+            mCurrentFullUserRecord = mUserRecords.get(currentFullUserId);
+            if (mCurrentFullUserRecord == null) {
+                Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
+                mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
+                mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
+                if (mSession2TokensPerUser.get(currentFullUserId) == null) {
+                    mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
+                }
+            }
+            mFullUserIds.put(currentFullUserId, currentFullUserId);
+        }
+    }
+
+    private void updateActiveSessionListeners() {
+        synchronized (mLock) {
+            for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
+                SessionsListenerRecord listener = mSessionsListeners.get(i);
+                try {
+                    enforceMediaPermissions(listener.componentName, listener.pid, listener.uid,
+                            listener.userId);
+                } catch (SecurityException e) {
+                    Log.i(TAG, "ActiveSessionsListener " + listener.componentName
+                            + " is no longer authorized. Disconnecting.");
+                    mSessionsListeners.remove(i);
+                    try {
+                        listener.listener
+                                .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
+                    } catch (Exception e1) {
+                        // ignore
+                    }
+                }
+            }
+        }
+    }
+
+    /*
+     * When a session is removed several things need to happen.
+     * 1. We need to remove it from the relevant user.
+     * 2. We need to remove it from the priority stack.
+     * 3. We need to remove it from all sessions.
+     * 4. If this is the system priority session we need to clear it.
+     * 5. We need to unlink to death from the cb binder
+     * 6. We need to tell the session to do any final cleanup (onDestroy)
+     */
+    private void destroySessionLocked(MediaSessionRecord session) {
+        if (DEBUG) {
+            Log.d(TAG, "Destroying " + session);
+        }
+        FullUserRecord user = getFullUserRecordLocked(session.getUserId());
+        if (mGlobalPrioritySession == session) {
+            mGlobalPrioritySession = null;
+            if (session.isActive() && user != null) {
+                user.pushAddressedPlayerChangedLocked();
+            }
+        } else {
+            if (user != null) {
+                user.mPriorityStack.removeSession(session);
+            }
+        }
+
+        try {
+            session.getCallback().getBinder().unlinkToDeath(session, 0);
+        } catch (Exception e) {
+            // ignore exceptions while destroying a session.
+        }
+        session.onDestroy();
+        mHandler.postSessionsChanged(session.getUserId());
+    }
+
+    private void enforcePackageName(String packageName, int uid) {
+        if (TextUtils.isEmpty(packageName)) {
+            throw new IllegalArgumentException("packageName may not be empty");
+        }
+        String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+        final int packageCount = packages.length;
+        for (int i = 0; i < packageCount; i++) {
+            if (packageName.equals(packages[i])) {
+                return;
+            }
+        }
+        throw new IllegalArgumentException("packageName is not owned by the calling process");
+    }
+
+    /**
+     * Checks a caller's authorization to register an IRemoteControlDisplay.
+     * Authorization is granted if one of the following is true:
+     * <ul>
+     * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
+     * permission</li>
+     * <li>the caller's listener is one of the enabled notification listeners
+     * for the caller's user</li>
+     * </ul>
+     */
+    private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
+            int resolvedUserId) {
+        if (isCurrentVolumeController(pid, uid)) return;
+        if (mContext
+                .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
+                != PackageManager.PERMISSION_GRANTED
+                && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
+                resolvedUserId)) {
+            throw new SecurityException("Missing permission to control media.");
+        }
+    }
+
+    private boolean isCurrentVolumeController(int pid, int uid) {
+        return mContext.checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+                pid, uid) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private void enforceSystemUiPermission(String action, int pid, int uid) {
+        if (!isCurrentVolumeController(pid, uid)) {
+            throw new SecurityException("Only system ui may " + action);
+        }
+    }
+
+    /**
+     * This checks if the component is an enabled notification listener for the
+     * specified user. Enabled components may only operate on behalf of the user
+     * they're running as.
+     *
+     * @param compName The component that is enabled.
+     * @param userId The user id of the caller.
+     * @param forUserId The user id they're making the request on behalf of.
+     * @return True if the component is enabled, false otherwise
+     */
+    private boolean isEnabledNotificationListener(ComponentName compName, int userId,
+            int forUserId) {
+        if (userId != forUserId) {
+            // You may not access another user's content as an enabled listener.
+            return false;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Checking if enabled notification listener " + compName);
+        }
+        if (compName != null) {
+            try {
+                return mNotificationManager.isNotificationListenerAccessGrantedForUser(
+                        compName, userId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
+            }
+        }
+        return false;
+    }
+
+    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
+            String callerPackageName, SessionCallbackLink cb, String tag) throws RemoteException {
+        synchronized (mLock) {
+            return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
+        }
+    }
+
+    /*
+     * When a session is created the following things need to happen.
+     * 1. Its callback binder needs a link to death
+     * 2. It needs to be added to all sessions.
+     * 3. It needs to be added to the priority stack.
+     * 4. It needs to be added to the relevant user record.
+     */
+    private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
+            String callerPackageName, SessionCallbackLink cb, String tag) {
+        FullUserRecord user = getFullUserRecordLocked(userId);
+        if (user == null) {
+            Log.wtf(TAG, "Request from invalid user: " +  userId);
+            throw new RuntimeException("Session request from invalid user.");
+        }
+
+        final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
+                callerPackageName, cb, tag, this, mHandler.getLooper());
+        try {
+            cb.getBinder().linkToDeath(session, 0);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Media Session owner died prematurely.", e);
+        }
+
+        user.mPriorityStack.addSession(session);
+        mHandler.postSessionsChanged(userId);
+
+        if (DEBUG) {
+            Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
+        }
+        return session;
+    }
+
+    private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
+        for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
+            if (mSessionsListeners.get(i).listener.asBinder() == listener.asBinder()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private int findIndexOfSession2TokensListenerLocked(ISession2TokensListener listener) {
+        for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+            if (mSession2TokensListenerRecords.get(i).listener.asBinder() == listener.asBinder()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+
+    private void pushSessionsChanged(int userId) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(userId);
+            if (user == null) {
+                Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
+                return;
+            }
+            List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
+            int size = records.size();
+            ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
+            for (int i = 0; i < size; i++) {
+                tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
+            }
+            pushRemoteVolumeUpdateLocked(userId);
+            for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
+                SessionsListenerRecord record = mSessionsListeners.get(i);
+                if (record.userId == USER_ALL || record.userId == userId) {
+                    try {
+                        record.listener.onActiveSessionsChanged(tokens);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
+                                e);
+                        mSessionsListeners.remove(i);
+                    }
+                }
+            }
+        }
+    }
+
+    private void pushRemoteVolumeUpdateLocked(int userId) {
+        if (mRvc != null) {
+            try {
+                FullUserRecord user = getFullUserRecordLocked(userId);
+                if (user == null) {
+                    Log.w(TAG, "pushRemoteVolumeUpdateLocked failed. No user with id=" + userId);
+                    return;
+                }
+                MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
+                mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
+            }
+        }
+    }
+
+    void pushSession2TokensChangedLocked(int userId) {
+        List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
+        List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
+
+        for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+            Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
+            try {
+                if (listenerRecord.userId == USER_ALL) {
+                    listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
+                } else if (listenerRecord.userId == userId) {
+                    listenerRecord.listener.onSession2TokensChanged(session2Tokens);
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
+                mSession2TokensListenerRecords.remove(i);
+            }
+        }
+    }
+
+    /**
+     * Called when the media button receiver for the {@code record} is changed.
+     *
+     * @param record the media session whose media button receiver is updated.
+     */
+    public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            MediaSessionRecord mediaButtonSession =
+                    user.mPriorityStack.getMediaButtonSession();
+            if (record == mediaButtonSession) {
+                user.rememberMediaButtonReceiverLocked(mediaButtonSession);
+            }
+        }
+    }
+
+    private String getCallingPackageName(int uid) {
+        String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages != null && packages.length > 0) {
+            return packages[0];
+        }
+        return "";
+    }
+
+    private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
+        if (mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
+            return;
+        }
+        try {
+            mCurrentFullUserRecord.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
+        }
+    }
+
+    private FullUserRecord getFullUserRecordLocked(int userId) {
+        int fullUserId = mFullUserIds.get(userId, -1);
+        if (fullUserId < 0) {
+            return null;
+        }
+        return mUserRecords.get(fullUserId);
+    }
+
+    /**
+     * Information about a full user and its corresponding managed profiles.
+     *
+     * <p>Since the full user runs together with its managed profiles, a user wouldn't differentiate
+     * them when he/she presses a media/volume button. So keeping media sessions for them in one
+     * place makes more sense and increases the readability.</p>
+     * <p>The contents of this object is guarded by {@link #mLock}.
+     */
+    final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
+        public static final int COMPONENT_TYPE_INVALID = 0;
+        public static final int COMPONENT_TYPE_BROADCAST = 1;
+        public static final int COMPONENT_TYPE_ACTIVITY = 2;
+        public static final int COMPONENT_TYPE_SERVICE = 3;
+        private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
+
+        private final int mFullUserId;
+        private final MediaSessionStack mPriorityStack;
+        private PendingIntent mLastMediaButtonReceiver;
+        private ComponentName mRestoredMediaButtonReceiver;
+        private int mRestoredMediaButtonReceiverComponentType;
+        private int mRestoredMediaButtonReceiverUserId;
+
+        private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
+        private int mOnVolumeKeyLongPressListenerUid;
+        private KeyEvent mInitialDownVolumeKeyEvent;
+        private int mInitialDownVolumeStream;
+        private boolean mInitialDownMusicOnly;
+
+        private IOnMediaKeyListener mOnMediaKeyListener;
+        private int mOnMediaKeyListenerUid;
+        private ICallback mCallback;
+
+        FullUserRecord(int fullUserId) {
+            mFullUserId = fullUserId;
+            mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
+            // Restore the remembered media button receiver before the boot.
+            String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver,
+                    Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
+            if (mediaButtonReceiverInfo == null) {
+                return;
+            }
+            String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM);
+            if (tokens == null || (tokens.length != 2 && tokens.length != 3)) {
+                return;
+            }
+            mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]);
+            mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]);
+            if (tokens.length == 3) {
+                mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]);
+            } else {
+                mRestoredMediaButtonReceiverComponentType =
+                        getComponentType(mRestoredMediaButtonReceiver);
+            }
+        }
+
+        public void destroySessionsForUserLocked(int userId) {
+            List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
+            for (MediaSessionRecord session : sessions) {
+                MediaSessionServiceImpl.this.destroySessionLocked(session);
+            }
+        }
+
+        public void dumpLocked(PrintWriter pw, String prefix) {
+            pw.print(prefix + "Record for full_user=" + mFullUserId);
+            // Dump managed profile user ids associated with this user.
+            int size = mFullUserIds.size();
+            for (int i = 0; i < size; i++) {
+                if (mFullUserIds.keyAt(i) != mFullUserIds.valueAt(i)
+                        && mFullUserIds.valueAt(i) == mFullUserId) {
+                    pw.print(", profile_user=" + mFullUserIds.keyAt(i));
+                }
+            }
+            pw.println();
+            String indent = prefix + "  ";
+            pw.println(indent + "Volume key long-press listener: " + mOnVolumeKeyLongPressListener);
+            pw.println(indent + "Volume key long-press listener package: "
+                    + getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
+            pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
+            pw.println(indent + "Media key listener package: "
+                    + getCallingPackageName(mOnMediaKeyListenerUid));
+            pw.println(indent + "Callback: " + mCallback);
+            pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
+            pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
+            pw.println(indent + "Restored MediaButtonReceiverComponentType: "
+                    + mRestoredMediaButtonReceiverComponentType);
+            mPriorityStack.dump(pw, indent);
+            pw.println(indent + "Session2Tokens:");
+            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+                List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
+                if (list == null || list.size() == 0) {
+                    continue;
+                }
+                for (Session2Token token : list) {
+                    pw.println(indent + "  " + token);
+                }
+            }
+        }
+
+        @Override
+        public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
+                MediaSessionRecord newMediaButtonSession) {
+            if (DEBUG_KEY_EVENT) {
+                Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
+            }
+            synchronized (mLock) {
+                if (oldMediaButtonSession != null) {
+                    mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
+                }
+                if (newMediaButtonSession != null) {
+                    rememberMediaButtonReceiverLocked(newMediaButtonSession);
+                    mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
+                }
+                pushAddressedPlayerChangedLocked();
+            }
+        }
+
+        // Remember media button receiver and keep it in the persistent storage.
+        public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
+            PendingIntent receiver = record.getMediaButtonReceiver();
+            mLastMediaButtonReceiver = receiver;
+            mRestoredMediaButtonReceiver = null;
+            mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
+
+            String mediaButtonReceiverInfo = "";
+            if (receiver != null) {
+                ComponentName component = receiver.getIntent().getComponent();
+                if (component != null
+                        && record.getPackageName().equals(component.getPackageName())) {
+                    String componentName = component.flattenToString();
+                    int componentType = getComponentType(component);
+                    mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM,
+                            componentName, String.valueOf(record.getUserId()),
+                            String.valueOf(componentType));
+                }
+            }
+            Settings.Secure.putStringForUser(mContentResolver,
+                    Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo,
+                    mFullUserId);
+        }
+
+        private void pushAddressedPlayerChangedLocked() {
+            if (mCallback == null) {
+                return;
+            }
+            try {
+                MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
+                if (mediaButtonSession != null) {
+                    mCallback.onAddressedPlayerChangedToMediaSession(
+                            new MediaSession.Token(mediaButtonSession.getControllerBinder()));
+                } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
+                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
+                            mCurrentFullUserRecord.mLastMediaButtonReceiver
+                                    .getIntent().getComponent());
+                } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
+                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
+                            mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
+            }
+        }
+
+        private MediaSessionRecord getMediaButtonSessionLocked() {
+            return isGlobalPriorityActiveLocked()
+                    ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
+        }
+
+        private int getComponentType(@Nullable ComponentName componentName) {
+            if (componentName == null) {
+                return COMPONENT_TYPE_INVALID;
+            }
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                ActivityInfo activityInfo = pm.getActivityInfo(componentName,
+                        PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                                | PackageManager.GET_ACTIVITIES);
+                if (activityInfo != null) {
+                    return COMPONENT_TYPE_ACTIVITY;
+                }
+            } catch (NameNotFoundException e) {
+            }
+            try {
+                ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
+                        PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                                | PackageManager.GET_SERVICES);
+                if (serviceInfo != null) {
+                    return COMPONENT_TYPE_SERVICE;
+                }
+            } catch (NameNotFoundException e) {
+            }
+            // Pick legacy behavior for BroadcastReceiver or unknown.
+            return COMPONENT_TYPE_BROADCAST;
+        }
+    }
+
+    final class SessionsListenerRecord implements IBinder.DeathRecipient {
+        public final IActiveSessionsListener listener;
+        public final ComponentName componentName;
+        public final int userId;
+        public final int pid;
+        public final int uid;
+
+        SessionsListenerRecord(IActiveSessionsListener listener,
+                ComponentName componentName,
+                int userId, int pid, int uid) {
+            this.listener = listener;
+            this.componentName = componentName;
+            this.userId = userId;
+            this.pid = pid;
+            this.uid = uid;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mSessionsListeners.remove(this);
+            }
+        }
+    }
+
+    final class Session2TokensListenerRecord implements IBinder.DeathRecipient {
+        public final ISession2TokensListener listener;
+        public final int userId;
+
+        Session2TokensListenerRecord(ISession2TokensListener listener,
+                int userId) {
+            this.listener = listener;
+            this.userId = userId;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mSession2TokensListenerRecords.remove(this);
+            }
+        }
+    }
+
+    final class SettingsObserver extends ContentObserver {
+        private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
+                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
+        private SettingsObserver() {
+            super(null);
+        }
+
+        private void observe() {
+            mContentResolver.registerContentObserver(mSecureSettingsUri,
+                    false, this, USER_ALL);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateActiveSessionListeners();
+        }
+    }
+
+    class SessionManagerImpl extends ISessionManager.Stub {
+        private static final String EXTRA_WAKELOCK_ACQUIRED =
+                "android.media.AudioService.WAKELOCK_ACQUIRED";
+        private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
+
+        private boolean mVoiceButtonDown = false;
+        private boolean mVoiceButtonHandled = false;
+
+        @Override
+        public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
+                int userId) throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                enforcePackageName(packageName, uid);
+                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+                        false /* allowAll */, true /* requireFull */, "createSession", packageName);
+                if (cb == null) {
+                    throw new IllegalArgumentException("Controller callback cannot be null");
+                }
+                return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
+                        .getSessionBinder();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void notifySession2Created(Session2Token sessionToken) throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG) {
+                    Log.d(TAG, "Session2 is created " + sessionToken);
+                }
+                if (uid != sessionToken.getUid()) {
+                    throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+                            + " but actually=" + sessionToken.getUid());
+                }
+                Controller2Callback callback = new Controller2Callback(sessionToken);
+                // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
+                //       it's closed.
+                // TODO: Keep controller as well for better readability
+                //       because the GC behavior isn't straightforward.
+                MediaController2 controller = new MediaController2(mContext, sessionToken,
+                        new HandlerExecutor(mHandler), callback);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public List<IBinder> getSessions(ComponentName componentName, int userId) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
+                ArrayList<IBinder> binders = new ArrayList<IBinder>();
+                synchronized (mLock) {
+                    List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
+                    for (MediaSessionRecord record : records) {
+                        binders.add(record.getControllerBinder().asBinder());
+                    }
+                }
+                return binders;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public List<Session2Token> getSession2Tokens(int userId) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                // Check that they can make calls on behalf of the user and
+                // get the final user id
+                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+                        true /* allowAll */, true /* requireFull */, "getSession2Tokens",
+                        null /* optional packageName */);
+                List<Session2Token> result;
+                synchronized (mLock) {
+                    result = getSession2TokensLocked(resolvedUserId);
+                }
+                return result;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void addSessionsListener(IActiveSessionsListener listener,
+                ComponentName componentName, int userId) throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
+                synchronized (mLock) {
+                    int index = findIndexOfSessionsListenerLocked(listener);
+                    if (index != -1) {
+                        Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
+                        return;
+                    }
+                    SessionsListenerRecord record = new SessionsListenerRecord(listener,
+                            componentName, resolvedUserId, pid, uid);
+                    try {
+                        listener.asBinder().linkToDeath(record, 0);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
+                        return;
+                    }
+                    mSessionsListeners.add(record);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void removeSessionsListener(IActiveSessionsListener listener)
+                throws RemoteException {
+            synchronized (mLock) {
+                int index = findIndexOfSessionsListenerLocked(listener);
+                if (index != -1) {
+                    SessionsListenerRecord record = mSessionsListeners.remove(index);
+                    try {
+                        record.listener.asBinder().unlinkToDeath(record, 0);
+                    } catch (Exception e) {
+                        // ignore exceptions, the record is being removed
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void addSession2TokensListener(ISession2TokensListener listener,
+                int userId) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                // Check that they can make calls on behalf of the user and get the final user id.
+                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+                        true /* allowAll */, true /* requireFull */, "addSession2TokensListener",
+                        null /* optional packageName */);
+                synchronized (mLock) {
+                    int index = findIndexOfSession2TokensListenerLocked(listener);
+                    if (index >= 0) {
+                        Log.w(TAG, "addSession2TokensListener is already added, ignoring");
+                        return;
+                    }
+                    mSession2TokensListenerRecords.add(
+                            new Session2TokensListenerRecord(listener, resolvedUserId));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void removeSession2TokensListener(ISession2TokensListener listener) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                synchronized (mLock) {
+                    int index = findIndexOfSession2TokensListenerLocked(listener);
+                    if (index >= 0) {
+                        Session2TokensListenerRecord listenerRecord =
+                                mSession2TokensListenerRecords.remove(index);
+                        try {
+                            listenerRecord.listener.asBinder().unlinkToDeath(listenerRecord, 0);
+                        } catch (Exception e) {
+                            // Ignore exception.
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        /**
+         * Handles the dispatching of the media button events to one of the
+         * registered listeners, or if there was none, broadcast an
+         * ACTION_MEDIA_BUTTON intent to the rest of the system.
+         *
+         * @param packageName The caller package
+         * @param asSystemService {@code true} if the event sent to the session as if it was come
+         *          from the system service instead of the app process. This helps sessions to
+         *          distinguish between the key injection by the app and key events from the
+         *          hardware devices. Should be used only when the volume key events aren't handled
+         *          by foreground activity. {@code false} otherwise to tell session about the real
+         *          caller.
+         * @param keyEvent a non-null KeyEvent whose key code is one of the
+         *            supported media buttons
+         * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
+         *            while this key event is dispatched.
+         */
+        @Override
+        public void dispatchMediaKeyEvent(String packageName, boolean asSystemService,
+                KeyEvent keyEvent, boolean needWakeLock) {
+            if (keyEvent == null || !KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
+                Log.w(TAG, "Attempted to dispatch null or non-media key event.");
+                return;
+            }
+
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG) {
+                    Log.d(TAG, "dispatchMediaKeyEvent, pkg=" + packageName + " pid=" + pid
+                            + ", uid=" + uid + ", asSystem=" + asSystemService + ", event="
+                            + keyEvent);
+                }
+                if (!isUserSetupComplete()) {
+                    // Global media key handling can have the side-effect of starting new
+                    // activities which is undesirable while setup is in progress.
+                    Slog.i(TAG, "Not dispatching media key event because user "
+                            + "setup is in progress.");
+                    return;
+                }
+
+                synchronized (mLock) {
+                    boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
+                    if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
+                        // Prevent dispatching key event through reflection while the global
+                        // priority session is active.
+                        Slog.i(TAG, "Only the system can dispatch media key event "
+                                + "to the global priority session.");
+                        return;
+                    }
+                    if (!isGlobalPriorityActive) {
+                        if (mCurrentFullUserRecord.mOnMediaKeyListener != null) {
+                            if (DEBUG_KEY_EVENT) {
+                                Log.d(TAG, "Send " + keyEvent + " to the media key listener");
+                            }
+                            try {
+                                mCurrentFullUserRecord.mOnMediaKeyListener.onMediaKey(keyEvent,
+                                        new MediaKeyListenerResultReceiver(packageName, pid, uid,
+                                                asSystemService, keyEvent, needWakeLock));
+                                return;
+                            } catch (RemoteException e) {
+                                Log.w(TAG, "Failed to send " + keyEvent
+                                        + " to the media key listener");
+                            }
+                        }
+                    }
+                    if (!isGlobalPriorityActive && isVoiceKey(keyEvent.getKeyCode())) {
+                        handleVoiceKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent,
+                                needWakeLock);
+                    } else {
+                        dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
+                                keyEvent, needWakeLock);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setCallback(ICallback callback) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
+                    throw new SecurityException("Only Bluetooth service processes can set"
+                            + " Callback");
+                }
+                synchronized (mLock) {
+                    int userId = UserHandle.getUserId(uid);
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    if (user == null || user.mFullUserId != userId) {
+                        Log.w(TAG, "Only the full user can set the callback"
+                                + ", userId=" + userId);
+                        return;
+                    }
+                    user.mCallback = callback;
+                    Log.d(TAG, "The callback " + user.mCallback
+                            + " is set by " + getCallingPackageName(uid));
+                    if (user.mCallback == null) {
+                        return;
+                    }
+                    try {
+                        user.mCallback.asBinder().linkToDeath(
+                                new IBinder.DeathRecipient() {
+                                    @Override
+                                    public void binderDied() {
+                                        synchronized (mLock) {
+                                            user.mCallback = null;
+                                        }
+                                    }
+                                }, 0);
+                        user.pushAddressedPlayerChangedLocked();
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to set callback", e);
+                        user.mCallback = null;
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
+                if (mContext.checkPermission(
+                        android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER"
+                            + " permission.");
+                }
+
+                synchronized (mLock) {
+                    int userId = UserHandle.getUserId(uid);
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    if (user == null || user.mFullUserId != userId) {
+                        Log.w(TAG, "Only the full user can set the volume key long-press listener"
+                                + ", userId=" + userId);
+                        return;
+                    }
+                    if (user.mOnVolumeKeyLongPressListener != null
+                            && user.mOnVolumeKeyLongPressListenerUid != uid) {
+                        Log.w(TAG, "The volume key long-press listener cannot be reset"
+                                + " by another app , mOnVolumeKeyLongPressListener="
+                                + user.mOnVolumeKeyLongPressListenerUid
+                                + ", uid=" + uid);
+                        return;
+                    }
+
+                    user.mOnVolumeKeyLongPressListener = listener;
+                    user.mOnVolumeKeyLongPressListenerUid = uid;
+
+                    Log.d(TAG, "The volume key long-press listener "
+                            + listener + " is set by " + getCallingPackageName(uid));
+
+                    if (user.mOnVolumeKeyLongPressListener != null) {
+                        try {
+                            user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
+                                    new IBinder.DeathRecipient() {
+                                        @Override
+                                        public void binderDied() {
+                                            synchronized (mLock) {
+                                                user.mOnVolumeKeyLongPressListener = null;
+                                            }
+                                        }
+                                    }, 0);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Failed to set death recipient "
+                                    + user.mOnVolumeKeyLongPressListener);
+                            user.mOnVolumeKeyLongPressListener = null;
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                // Enforce SET_MEDIA_KEY_LISTENER permission.
+                if (mContext.checkPermission(
+                        android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER permission.");
+                }
+
+                synchronized (mLock) {
+                    int userId = UserHandle.getUserId(uid);
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    if (user == null || user.mFullUserId != userId) {
+                        Log.w(TAG, "Only the full user can set the media key listener"
+                                + ", userId=" + userId);
+                        return;
+                    }
+                    if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
+                        Log.w(TAG, "The media key listener cannot be reset by another app. "
+                                + ", mOnMediaKeyListenerUid=" + user.mOnMediaKeyListenerUid
+                                + ", uid=" + uid);
+                        return;
+                    }
+
+                    user.mOnMediaKeyListener = listener;
+                    user.mOnMediaKeyListenerUid = uid;
+
+                    Log.d(TAG, "The media key listener " + user.mOnMediaKeyListener
+                            + " is set by " + getCallingPackageName(uid));
+
+                    if (user.mOnMediaKeyListener != null) {
+                        try {
+                            user.mOnMediaKeyListener.asBinder().linkToDeath(
+                                    new IBinder.DeathRecipient() {
+                                        @Override
+                                        public void binderDied() {
+                                            synchronized (mLock) {
+                                                user.mOnMediaKeyListener = null;
+                                            }
+                                        }
+                                    }, 0);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
+                            user.mOnMediaKeyListener = null;
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        /**
+         * Handles the dispatching of the volume button events to one of the
+         * registered listeners. If there's a volume key long-press listener and
+         * there's no active global priority session, long-pressess will be sent to the
+         * long-press listener instead of adjusting volume.
+         *
+         * @param packageName The caller's package name, obtained by Context#getPackageName()
+         * @param opPackageName The caller's op package name, obtained by Context#getOpPackageName()
+         * @param asSystemService {@code true} if the event sent to the session as if it was come
+         *          from the system service instead of the app process. This helps sessions to
+         *          distinguish between the key injection by the app and key events from the
+         *          hardware devices. Should be used only when the volume key events aren't handled
+         *          by foreground activity. {@code false} otherwise to tell session about the real
+         *          caller.
+         * @param keyEvent a non-null KeyEvent whose key code is one of the
+         *            {@link KeyEvent#KEYCODE_VOLUME_UP},
+         *            {@link KeyEvent#KEYCODE_VOLUME_DOWN},
+         *            or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
+         * @param stream stream type to adjust volume.
+         * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
+         */
+        @Override
+        public void dispatchVolumeKeyEvent(String packageName, String opPackageName,
+                boolean asSystemService, KeyEvent keyEvent, int stream, boolean musicOnly) {
+            if (keyEvent == null
+                    || (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
+                            && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
+                            && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
+                Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
+                return;
+            }
+
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            if (DEBUG_KEY_EVENT) {
+                Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + ", pid=" + pid + ", uid="
+                        + uid + ", asSystem=" + asSystemService + ", event=" + keyEvent);
+            }
+
+            try {
+                synchronized (mLock) {
+                    if (isGlobalPriorityActiveLocked()
+                            || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
+                        dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
+                                asSystemService, keyEvent, stream, musicOnly);
+                    } else {
+                        // TODO: Consider the case when both volume up and down keys are pressed
+                        //       at the same time.
+                        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                            if (keyEvent.getRepeatCount() == 0) {
+                                // Keeps the copy of the KeyEvent because it can be reused.
+                                mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
+                                        KeyEvent.obtain(keyEvent);
+                                mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
+                                mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
+                                mHandler.sendMessageDelayed(
+                                        mHandler.obtainMessage(
+                                                MessageHandler.MSG_VOLUME_INITIAL_DOWN,
+                                                mCurrentFullUserRecord.mFullUserId, 0),
+                                        mLongPressTimeout);
+                            }
+                            if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
+                                mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
+                                if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
+                                    dispatchVolumeKeyLongPressLocked(
+                                            mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
+                                    // Mark that the key is already handled.
+                                    mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
+                                }
+                                dispatchVolumeKeyLongPressLocked(keyEvent);
+                            }
+                        } else { // if up
+                            mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
+                            if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
+                                    && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
+                                    .getDownTime() == keyEvent.getDownTime()) {
+                                // Short-press. Should change volume.
+                                dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
+                                        asSystemService,
+                                        mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
+                                        mCurrentFullUserRecord.mInitialDownVolumeStream,
+                                        mCurrentFullUserRecord.mInitialDownMusicOnly);
+                                dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
+                                        asSystemService, keyEvent, stream, musicOnly);
+                            } else {
+                                dispatchVolumeKeyLongPressLocked(keyEvent);
+                            }
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        private void dispatchVolumeKeyEventLocked(String packageName, String opPackageName, int pid,
+                int uid, boolean asSystemService, KeyEvent keyEvent, int stream,
+                boolean musicOnly) {
+            boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
+            boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
+            int direction = 0;
+            boolean isMute = false;
+            switch (keyEvent.getKeyCode()) {
+                case KeyEvent.KEYCODE_VOLUME_UP:
+                    direction = AudioManager.ADJUST_RAISE;
+                    break;
+                case KeyEvent.KEYCODE_VOLUME_DOWN:
+                    direction = AudioManager.ADJUST_LOWER;
+                    break;
+                case KeyEvent.KEYCODE_VOLUME_MUTE:
+                    isMute = true;
+                    break;
+            }
+            if (down || up) {
+                int flags = AudioManager.FLAG_FROM_KEY;
+                if (musicOnly) {
+                    // This flag is used when the screen is off to only affect active media.
+                    flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
+                } else {
+                    // These flags are consistent with the home screen
+                    if (up) {
+                        flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
+                    } else {
+                        flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
+                    }
+                }
+                if (direction != 0) {
+                    // If this is action up we want to send a beep for non-music events
+                    if (up) {
+                        direction = 0;
+                    }
+                    dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
+                            asSystemService, stream, direction, flags);
+                } else if (isMute) {
+                    if (down && keyEvent.getRepeatCount() == 0) {
+                        dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
+                                asSystemService, stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void dispatchAdjustVolume(String packageName, String opPackageName,
+                int suggestedStream, int delta, int flags) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid, false,
+                            suggestedStream, delta, flags);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setRemoteVolumeController(IRemoteVolumeController rvc) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                enforceSystemUiPermission("listen for volume changes", pid, uid);
+                mRvc = rvc;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public boolean isGlobalPriorityActive() {
+            synchronized (mLock) {
+                return isGlobalPriorityActiveLocked();
+            }
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+            pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
+            pw.println();
+
+            synchronized (mLock) {
+                pw.println(mSessionsListeners.size() + " sessions listeners.");
+                pw.println("Global priority session is " + mGlobalPrioritySession);
+                if (mGlobalPrioritySession != null) {
+                    mGlobalPrioritySession.dump(pw, "  ");
+                }
+                pw.println("User Records:");
+                int count = mUserRecords.size();
+                for (int i = 0; i < count; i++) {
+                    mUserRecords.valueAt(i).dumpLocked(pw, "");
+                }
+                mAudioPlayerStateMonitor.dump(mContext, pw, "");
+            }
+        }
+
+        /**
+         * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
+         * permission or an enabled notification listener)
+         *
+         * @param controllerPackageName package name of the controller app
+         * @param controllerPid pid of the controller app
+         * @param controllerUid uid of the controller app
+         */
+        @Override
+        public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
+                throws RemoteException {
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                // Don't perform sanity check between controllerPackageName and controllerUid.
+                // When an (activity|service) runs on the another apps process by specifying
+                // android:process in the AndroidManifest.xml, then PID and UID would have the
+                // running process' information instead of the (activity|service) that has created
+                // MediaController.
+                // Note that we can use Context#getOpPackageName() instead of
+                // Context#getPackageName() for getting package name that matches with the PID/UID,
+                // but it doesn't tell which package has created the MediaController, so useless.
+                return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
+                        controllerPid, controllerUid);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        // For MediaSession
+        private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
+                final int uid) {
+            String packageName = null;
+            if (componentName != null) {
+                // If they gave us a component name verify they own the
+                // package
+                packageName = componentName.getPackageName();
+                enforcePackageName(packageName, uid);
+            }
+            // Check that they can make calls on behalf of the user and
+            // get the final user id
+            int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+                    true /* allowAll */, true /* requireFull */, "getSessions", packageName);
+            // Check if they have the permissions or their component is
+            // enabled for the user they're calling from.
+            enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
+            return resolvedUserId;
+        }
+
+        private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
+                int pid, int uid) throws RemoteException {
+            // Allow API calls from the System UI
+            if (isCurrentVolumeController(pid, uid)) {
+                return true;
+            }
+
+            // Check if it's system server or has MEDIA_CONTENT_CONTROL.
+            // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
+            // check here.
+            if (uid == Process.SYSTEM_UID || mContext.checkPermission(
+                    android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            } else if (DEBUG) {
+                Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
+            }
+
+            // You may not access another user's content as an enabled listener.
+            final int userId = UserHandle.getUserId(uid);
+            if (resolvedUserId != userId) {
+                return false;
+            }
+
+            // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
+            //               String pkgName) to notification team for optimization
+            final List<ComponentName> enabledNotificationListeners =
+                    mNotificationManager.getEnabledNotificationListeners(userId);
+            if (enabledNotificationListeners != null) {
+                for (int i = 0; i < enabledNotificationListeners.size(); i++) {
+                    if (TextUtils.equals(packageName,
+                            enabledNotificationListeners.get(i).getPackageName())) {
+                        return true;
+                    }
+                }
+            }
+            if (DEBUG) {
+                Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
+                        + "notification listener");
+            }
+            return false;
+        }
+
+        private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
+                int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
+            MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
+                    : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
+
+            boolean preferSuggestedStream = false;
+            if (isValidLocalStreamType(suggestedStream)
+                    && AudioSystem.isStreamActive(suggestedStream, 0)) {
+                preferSuggestedStream = true;
+            }
+            if (DEBUG_KEY_EVENT) {
+                Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
+                        + flags + ", suggestedStream=" + suggestedStream
+                        + ", preferSuggestedStream=" + preferSuggestedStream);
+            }
+            if (session == null || preferSuggestedStream) {
+                if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
+                        && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "No active session to adjust, skipping media only volume event");
+                    }
+                    return;
+                }
+
+                // Execute mAudioService.adjustSuggestedStreamVolume() on
+                // handler thread of MediaSessionService.
+                // This will release the MediaSessionService.mLock sooner and avoid
+                // a potential deadlock between MediaSessionService.mLock and
+                // ActivityManagerService lock.
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        final String callingOpPackageName;
+                        final int callingUid;
+                        if (asSystemService) {
+                            callingOpPackageName = mContext.getOpPackageName();
+                            callingUid = Process.myUid();
+                        } else {
+                            callingOpPackageName = opPackageName;
+                            callingUid = uid;
+                        }
+                        try {
+                            mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream,
+                                    direction, flags, callingOpPackageName, callingUid);
+                        } catch (SecurityException | IllegalArgumentException e) {
+                            Log.e(TAG, "Cannot adjust volume: direction=" + direction
+                                    + ", suggestedStream=" + suggestedStream + ", flags=" + flags
+                                    + ", packageName=" + packageName + ", uid=" + uid
+                                    + ", asSystemService=" + asSystemService, e);
+                        }
+                    }
+                });
+            } else {
+                session.adjustVolume(packageName, opPackageName, pid, uid, null, asSystemService,
+                        direction, flags, true);
+            }
+        }
+
+        private void handleVoiceKeyEventLocked(String packageName, int pid, int uid,
+                boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
+            int action = keyEvent.getAction();
+            boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
+            if (action == KeyEvent.ACTION_DOWN) {
+                if (keyEvent.getRepeatCount() == 0) {
+                    mVoiceButtonDown = true;
+                    mVoiceButtonHandled = false;
+                } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
+                    mVoiceButtonHandled = true;
+                    startVoiceInput(needWakeLock);
+                }
+            } else if (action == KeyEvent.ACTION_UP) {
+                if (mVoiceButtonDown) {
+                    mVoiceButtonDown = false;
+                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
+                        // Resend the down then send this event through
+                        KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
+                        dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
+                                downEvent, needWakeLock);
+                        dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
+                                keyEvent, needWakeLock);
+                    }
+                }
+            }
+        }
+
+        private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
+                boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
+            MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
+            if (session != null) {
+                if (DEBUG_KEY_EVENT) {
+                    Log.d(TAG, "Sending " + keyEvent + " to " + session);
+                }
+                if (needWakeLock) {
+                    mKeyEventReceiver.aquireWakeLockLocked();
+                }
+                // If we don't need a wakelock use -1 as the id so we won't release it later.
+                session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
+                        needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
+                        mKeyEventReceiver);
+                if (mCurrentFullUserRecord.mCallback != null) {
+                    try {
+                        mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
+                                keyEvent, new MediaSession.Token(session.getControllerBinder()));
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to send callback", e);
+                    }
+                }
+            } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
+                    || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
+                if (needWakeLock) {
+                    mKeyEventReceiver.aquireWakeLockLocked();
+                }
+                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+                mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+                // TODO: Find a way to also send PID/UID in secure way.
+                String callerPackageName =
+                        (asSystemService) ? mContext.getPackageName() : packageName;
+                mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName);
+                try {
+                    if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
+                        PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
+                        if (DEBUG_KEY_EVENT) {
+                            Log.d(TAG, "Sending " + keyEvent
+                                    + " to the last known PendingIntent " + receiver);
+                        }
+                        receiver.send(mContext,
+                                needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
+                                mediaButtonIntent, mKeyEventReceiver, mHandler);
+                        if (mCurrentFullUserRecord.mCallback != null) {
+                            ComponentName componentName = mCurrentFullUserRecord
+                                    .mLastMediaButtonReceiver.getIntent().getComponent();
+                            if (componentName != null) {
+                                mCurrentFullUserRecord.mCallback
+                                        .onMediaKeyEventDispatchedToMediaButtonReceiver(
+                                                keyEvent, componentName);
+                            }
+                        }
+                    } else {
+                        ComponentName receiver =
+                                mCurrentFullUserRecord.mRestoredMediaButtonReceiver;
+                        int componentType = mCurrentFullUserRecord
+                                .mRestoredMediaButtonReceiverComponentType;
+                        UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord
+                                .mRestoredMediaButtonReceiverUserId);
+                        if (DEBUG_KEY_EVENT) {
+                            Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
+                                    + receiver + ", type=" + componentType);
+                        }
+                        mediaButtonIntent.setComponent(receiver);
+                        try {
+                            switch (componentType) {
+                                case FullUserRecord.COMPONENT_TYPE_ACTIVITY:
+                                    mContext.startActivityAsUser(mediaButtonIntent, userHandle);
+                                    break;
+                                case FullUserRecord.COMPONENT_TYPE_SERVICE:
+                                    mContext.startForegroundServiceAsUser(mediaButtonIntent,
+                                            userHandle);
+                                    break;
+                                default:
+                                    // Legacy behavior for other cases.
+                                    mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle);
+                            }
+                        } catch (Exception e) {
+                            Log.w(TAG, "Error sending media button to the restored intent "
+                                    + receiver + ", type=" + componentType, e);
+                        }
+                        if (mCurrentFullUserRecord.mCallback != null) {
+                            mCurrentFullUserRecord.mCallback
+                                    .onMediaKeyEventDispatchedToMediaButtonReceiver(
+                                            keyEvent, receiver);
+                        }
+                    }
+                } catch (CanceledException e) {
+                    Log.i(TAG, "Error sending key event to media button receiver "
+                            + mCurrentFullUserRecord.mLastMediaButtonReceiver, e);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to send callback", e);
+                }
+            }
+        }
+
+        private void startVoiceInput(boolean needWakeLock) {
+            Intent voiceIntent = null;
+            // select which type of search to launch:
+            // - screen on and device unlocked: action is ACTION_WEB_SEARCH
+            // - device locked or screen off: action is
+            // ACTION_VOICE_SEARCH_HANDS_FREE
+            // with EXTRA_SECURE set to true if the device is securely locked
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+            if (!isLocked && pm.isScreenOn()) {
+                voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
+                Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
+            } else {
+                voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+                voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
+                        isLocked && mKeyguardManager.isKeyguardSecure());
+                Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
+            }
+            // start the search activity
+            if (needWakeLock) {
+                mMediaEventWakeLock.acquire();
+            }
+            try {
+                if (voiceIntent != null) {
+                    voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                    if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
+                    mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
+                }
+            } catch (ActivityNotFoundException e) {
+                Log.w(TAG, "No activity for search: " + e);
+            } finally {
+                if (needWakeLock) {
+                    mMediaEventWakeLock.release();
+                }
+            }
+        }
+
+        private boolean isVoiceKey(int keyCode) {
+            return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+                    || (!mHasFeatureLeanback && keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        }
+
+        private boolean isUserSetupComplete() {
+            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+        }
+
+        // we only handle public stream types, which are 0-5
+        private boolean isValidLocalStreamType(int streamType) {
+            return streamType >= AudioManager.STREAM_VOICE_CALL
+                    && streamType <= AudioManager.STREAM_NOTIFICATION;
+        }
+
+        private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
+            private final String mPackageName;
+            private final int mPid;
+            private final int mUid;
+            private final boolean mAsSystemService;
+            private final KeyEvent mKeyEvent;
+            private final boolean mNeedWakeLock;
+            private boolean mHandled;
+
+            private MediaKeyListenerResultReceiver(String packageName, int pid, int uid,
+                    boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
+                super(mHandler);
+                mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
+                mPackageName = packageName;
+                mPid = pid;
+                mUid = uid;
+                mAsSystemService = asSystemService;
+                mKeyEvent = keyEvent;
+                mNeedWakeLock = needWakeLock;
+            }
+
+            @Override
+            public void run() {
+                Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
+                dispatchMediaKeyEvent();
+            }
+
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
+                    mHandled = true;
+                    mHandler.removeCallbacks(this);
+                    return;
+                }
+                dispatchMediaKeyEvent();
+            }
+
+            private void dispatchMediaKeyEvent() {
+                if (mHandled) {
+                    return;
+                }
+                mHandled = true;
+                mHandler.removeCallbacks(this);
+                synchronized (mLock) {
+                    if (!isGlobalPriorityActiveLocked()
+                            && isVoiceKey(mKeyEvent.getKeyCode())) {
+                        handleVoiceKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
+                                mKeyEvent, mNeedWakeLock);
+                    } else {
+                        dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService,
+                                mKeyEvent, mNeedWakeLock);
+                    }
+                }
+            }
+        }
+
+        private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
+
+        class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
+                PendingIntent.OnFinished {
+            private final Handler mHandler;
+            private int mRefCount = 0;
+            private int mLastTimeoutId = 0;
+
+            KeyEventWakeLockReceiver(Handler handler) {
+                super(handler);
+                mHandler = handler;
+            }
+
+            public void onTimeout() {
+                synchronized (mLock) {
+                    if (mRefCount == 0) {
+                        // We've already released it, so just return
+                        return;
+                    }
+                    mLastTimeoutId++;
+                    mRefCount = 0;
+                    releaseWakeLockLocked();
+                }
+            }
+
+            public void aquireWakeLockLocked() {
+                if (mRefCount == 0) {
+                    mMediaEventWakeLock.acquire();
+                }
+                mRefCount++;
+                mHandler.removeCallbacks(this);
+                mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
+
+            }
+
+            @Override
+            public void run() {
+                onTimeout();
+            }
+
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode < mLastTimeoutId) {
+                    // Ignore results from calls that were before the last
+                    // timeout, just in case.
+                    return;
+                } else {
+                    synchronized (mLock) {
+                        if (mRefCount > 0) {
+                            mRefCount--;
+                            if (mRefCount == 0) {
+                                releaseWakeLockLocked();
+                            }
+                        }
+                    }
+                }
+            }
+
+            private void releaseWakeLockLocked() {
+                mMediaEventWakeLock.release();
+                mHandler.removeCallbacks(this);
+            }
+
+            @Override
+            public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+                    String resultData, Bundle resultExtras) {
+                onReceiveResult(resultCode, null);
+            }
+        };
+
+        BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent == null) {
+                    return;
+                }
+                Bundle extras = intent.getExtras();
+                if (extras == null) {
+                    return;
+                }
+                synchronized (mLock) {
+                    if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
+                            && mMediaEventWakeLock.isHeld()) {
+                        mMediaEventWakeLock.release();
+                    }
+                }
+            }
+        };
+    }
+
+    final class MessageHandler extends Handler {
+        private static final int MSG_SESSIONS_CHANGED = 1;
+        private static final int MSG_VOLUME_INITIAL_DOWN = 2;
+        private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SESSIONS_CHANGED:
+                    pushSessionsChanged((int) msg.obj);
+                    break;
+                case MSG_VOLUME_INITIAL_DOWN:
+                    synchronized (mLock) {
+                        FullUserRecord user = mUserRecords.get((int) msg.arg1);
+                        if (user != null && user.mInitialDownVolumeKeyEvent != null) {
+                            dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
+                            // Mark that the key is already handled.
+                            user.mInitialDownVolumeKeyEvent = null;
+                        }
+                    }
+                    break;
+            }
+        }
+
+        public void postSessionsChanged(int userId) {
+            // Use object instead of the arguments when posting message to remove pending requests.
+            Integer userIdInteger = mIntegerCache.get(userId);
+            if (userIdInteger == null) {
+                userIdInteger = Integer.valueOf(userId);
+                mIntegerCache.put(userId, userIdInteger);
+            }
+            removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
+            obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
+        }
+    }
+
+    private class Controller2Callback extends MediaController2.ControllerCallback {
+        private final Session2Token mToken;
+
+        Controller2Callback(Session2Token token) {
+            mToken = token;
+        }
+
+        @Override
+        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+            synchronized (mLock) {
+                int userId = UserHandle.getUserId(mToken.getUid());
+                mSession2TokensPerUser.get(userId).add(mToken);
+                pushSession2TokensChangedLocked(userId);
+            }
+        }
+
+        @Override
+        public void onDisconnected(MediaController2 controller) {
+            synchronized (mLock) {
+                int userId = UserHandle.getUserId(mToken.getUid());
+                mSession2TokensPerUser.get(userId).remove(mToken);
+                pushSession2TokensChangedLocked(userId);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
index 6a5f563..64c451d 100644
--- a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -159,12 +159,13 @@
                     + serviceInfo.packageName + "/" + serviceInfo.name);
             return false;
         }
-        if (!hasCaptureVideoPermission(serviceInfo.packageName)) {
-            // If the service does not have permission to capture video then it
-            // isn't going to be terribly useful as a remote display, is it?
-            // Kind of makes you wonder what it's doing there in the first place.
+        if (mPackageManager.checkPermission(Manifest.permission.REMOTE_DISPLAY_PROVIDER,
+                        serviceInfo.packageName) != PackageManager.PERMISSION_GRANTED) {
+            // If the service does not have this permission then the system will not bind to it.
+            // This is to prevent non privileged apps declaring themselves as remote display
+            // providers just to be bound to by the system and keep their process alive.
             Slog.w(TAG, "Ignoring remote display provider service because it does not "
-                    + "have the CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT "
+                    + "have the REMOTE_DISPLAY_PROVIDER "
                     + "permission: " + serviceInfo.packageName + "/" + serviceInfo.name);
             return false;
         }
@@ -172,18 +173,6 @@
         return true;
     }
 
-    private boolean hasCaptureVideoPermission(String packageName) {
-        if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT,
-                packageName) == PackageManager.PERMISSION_GRANTED) {
-            return true;
-        }
-        if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT,
-                packageName) == PackageManager.PERMISSION_GRANTED) {
-            return true;
-        }
-        return false;
-    }
-
     private int findProvider(String packageName, String className) {
         int count = mProviders.size();
         for (int i = 0; i < count; i++) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6932390..6baf12c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -23138,6 +23138,11 @@
         }
 
         @Override
+        public void setLocationExtraPackagesProvider(PackagesProvider provider) {
+            mDefaultPermissionPolicy.setLocationExtraPackagesProvider(provider);
+        }
+
+        @Override
         public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
             mDefaultPermissionPolicy.setVoiceInteractionPackagesProvider(provider);
         }
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 68a755b..78fa82c 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -28,7 +28,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
@@ -53,21 +52,18 @@
 
     private final IPackageManager mPackageManager;
     private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
-    private final Object mInstallLock;
-    @GuardedBy("mInstallLock")
     private final Installer mInstaller;
 
-    public DexLogger(IPackageManager pms, Installer installer, Object installLock) {
-        this(pms, installer, installLock, new PackageDynamicCodeLoading());
+    public DexLogger(IPackageManager pms, Installer installer) {
+        this(pms, installer, new PackageDynamicCodeLoading());
     }
 
     @VisibleForTesting
-    DexLogger(IPackageManager pms, Installer installer, Object installLock,
+    DexLogger(IPackageManager pms, Installer installer,
             PackageDynamicCodeLoading packageDynamicCodeLoading) {
         mPackageManager = pms;
         mPackageDynamicCodeLoading = packageDynamicCodeLoading;
         mInstaller = installer;
-        mInstallLock = installLock;
     }
 
     public Set<String> getAllPackagesWithDynamicCodeLoading() {
@@ -131,14 +127,16 @@
             }
 
             byte[] hash = null;
-            synchronized (mInstallLock) {
-                try {
-                    hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid,
-                            appInfo.volumeUuid, storageFlags);
-                } catch (InstallerException e) {
-                    Slog.e(TAG, "Got InstallerException when hashing file " + filePath
-                            + ": " + e.getMessage());
-                }
+            try {
+                // Note that we do not take the install lock here. Hashing should never interfere
+                // with app update/compilation/removal. We may get anomalous results if a file
+                // changes while we hash it, but that can happen anyway and is harmless for our
+                // purposes.
+                hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid,
+                        appInfo.volumeUuid, storageFlags);
+            } catch (InstallerException e) {
+                Slog.e(TAG, "Got InstallerException when hashing file " + filePath
+                        + ": " + e.getMessage());
             }
 
             String fileName = new File(filePath).getName();
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index e57d9d7..b546836 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -129,7 +129,7 @@
         mPackageDexOptimizer = pdo;
         mInstaller = installer;
         mInstallLock = installLock;
-        mDexLogger = new DexLogger(pms, installer, installLock);
+        mDexLogger = new DexLogger(pms, installer);
     }
 
     public DexLogger getDexLogger() {
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 789664d..ceaf69d 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -225,6 +225,7 @@
     private final Handler mHandler;
 
     private PackagesProvider mLocationPackagesProvider;
+    private PackagesProvider mLocationExtraPackagesProvider;
     private PackagesProvider mVoiceInteractionPackagesProvider;
     private PackagesProvider mSmsAppPackagesProvider;
     private PackagesProvider mDialerAppPackagesProvider;
@@ -270,6 +271,13 @@
         }
     }
 
+    /** Sets the provider for loction extra packages. */
+    public void setLocationExtraPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mLocationExtraPackagesProvider = provider;
+        }
+    }
+
     public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
         synchronized (mLock) {
             mVoiceInteractionPackagesProvider = provider;
@@ -403,6 +411,7 @@
         Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
 
         final PackagesProvider locationPackagesProvider;
+        final PackagesProvider locationExtraPackagesProvider;
         final PackagesProvider voiceInteractionPackagesProvider;
         final PackagesProvider smsAppPackagesProvider;
         final PackagesProvider dialerAppPackagesProvider;
@@ -412,6 +421,7 @@
 
         synchronized (mLock) {
             locationPackagesProvider = mLocationPackagesProvider;
+            locationExtraPackagesProvider = mLocationExtraPackagesProvider;
             voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
             smsAppPackagesProvider = mSmsAppPackagesProvider;
             dialerAppPackagesProvider = mDialerAppPackagesProvider;
@@ -424,6 +434,8 @@
                 ? voiceInteractionPackagesProvider.getPackages(userId) : null;
         String[] locationPackageNames = (locationPackagesProvider != null)
                 ? locationPackagesProvider.getPackages(userId) : null;
+        String[] locationExtraPackageNames = (locationExtraPackagesProvider != null)
+                ? locationExtraPackagesProvider.getPackages(userId) : null;
         String[] smsAppPackageNames = (smsAppPackagesProvider != null)
                 ? smsAppPackagesProvider.getPackages(userId) : null;
         String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
@@ -638,6 +650,12 @@
                         LOCATION_PERMISSIONS, ACTIVITY_RECOGNITION_PERMISSIONS);
             }
         }
+        if (locationExtraPackageNames != null) {
+            // Also grant location permission to location extra packages.
+            for (String packageName : locationExtraPackageNames) {
+                grantPermissionsToSystemPackage(packageName, userId, LOCATION_PERMISSIONS);
+            }
+        }
 
         // Music
         Intent musicIntent = new Intent(Intent.ACTION_VIEW)
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e82e748..2cd4921 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -210,6 +210,7 @@
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionManagerInternal;
+import android.sysprop.DisplayProperties;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.text.format.Time;
@@ -245,7 +246,6 @@
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.appop.AppOpsService;
 import com.android.server.AttributeCache;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -261,6 +261,7 @@
 import com.android.server.am.PendingIntentController;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.am.UserState;
+import com.android.server.appop.AppOpsService;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.UserManagerService;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -694,7 +695,7 @@
         final boolean isPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
 
         // Transfer any global setting for forcing RTL layout, into a System Property
-        SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+        DisplayProperties.debug_force_rtl(forceRtl);
 
         final Configuration configuration = new Configuration();
         Settings.System.getConfiguration(resolver, configuration);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 740d472..45bb94b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1160,12 +1160,14 @@
     @Override
     boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
             ConfigurationContainer requestingContainer) {
+        final int previousRotation = mRotation;
         final Configuration config = updateOrientationFromAppTokens(
                 getRequestedOverrideConfiguration(), freezeDisplayToken, false);
-        // If display rotation class tells us that it doesn't consider app requested orientation,
-        // this display won't rotate just because of an app changes its requested orientation. Thus
-        // it indicates that this display chooses not to handle this request.
-        final boolean handled = getDisplayRotation().respectAppRequestedOrientation();
+        // This event is considered handled iff a configuration propagation is triggered, because
+        // that's the only place lower level containers check if they need to do something to this
+        // request. The only guaranteed signal is that the display is rotated to a different
+        // orientation (i.e. rotating 180 degrees doesn't count).
+        final boolean handled = (mRotation - previousRotation) % 2 != 0;
         if (config == null) {
             return handled;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index bcc7be4..7aabc15 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -329,15 +329,6 @@
         return mFixedToUserRotation;
     }
 
-    /**
-     * Returns {@code true} if this display rotation takes app requested orientation into
-     * consideration; {@code false} otherwise. For the time being the only case where this is {@code
-     * false} is when {@link #isFixedToUserRotation()} is {@code true}.
-     */
-    boolean respectAppRequestedOrientation() {
-        return !mFixedToUserRotation;
-    }
-
     public int getLandscapeRotation() {
         return mLandscapeRotation;
     }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 5f56fe5..177f244 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -462,22 +462,19 @@
             mOccluded = false;
             mDismissingKeyguardActivity = null;
 
-            // Only the top activity of the focused stack on each display may control it's
-            // occluded state.
-            final ActivityStack focusedStack = display.getFocusedStack();
-            if (focusedStack != null) {
-                final ActivityRecord topDismissing =
-                        focusedStack.getTopDismissingKeyguardActivity();
-                mOccluded = focusedStack.topActivityOccludesKeyguard() || (topDismissing != null
-                                && focusedStack.topRunningActivityLocked() == topDismissing
-                                && controller.canShowWhileOccluded(
+            final ActivityStack stack = getStackForControllingOccluding(display);
+            if (stack != null) {
+                final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
+                mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null
+                        && stack.topRunningActivityLocked() == topDismissing
+                        && controller.canShowWhileOccluded(
                                 true /* dismissKeyguard */,
                                 false /* showWhenLocked */));
-                if (focusedStack.getTopDismissingKeyguardActivity() != null) {
-                    mDismissingKeyguardActivity = focusedStack.getTopDismissingKeyguardActivity();
+                if (stack.getTopDismissingKeyguardActivity() != null) {
+                    mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
                 }
-                mOccluded |= controller.mWindowManager.isShowingDream();
             }
+            mOccluded |= controller.mWindowManager.isShowingDream();
 
             // TODO(b/113840485): Handle app transition for individual display, and apply occluded
             // state change to secondary displays.
@@ -492,6 +489,23 @@
             }
         }
 
+        /**
+         * Gets the stack used to check the occluded state.
+         * <p>
+         * Only the top non-pinned activity of the focusable stack on each display can control its
+         * occlusion state.
+         */
+        private ActivityStack getStackForControllingOccluding(ActivityDisplay display) {
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                if (stack != null && stack.isFocusableAndVisible()
+                        && !stack.inPinnedWindowingMode()) {
+                    return stack;
+                }
+            }
+            return null;
+        }
+
         void dumpStatus(PrintWriter pw, String prefix) {
             final StringBuilder sb = new StringBuilder();
             sb.append(prefix);
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 4a553cf..e944858 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -1278,28 +1278,28 @@
     }
 
     /**
-     * Checks if the root activity requires a particular orientation (either by override or
+     * Checks if the top activity requires a particular orientation (either by override or
      * activityInfo) and returns that. Otherwise, this returns ORIENTATION_UNDEFINED.
      */
-    private int getRootActivityRequestedOrientation() {
-        ActivityRecord root = getRootActivity();
+    private int getTopActivityRequestedOrientation() {
+        ActivityRecord top = getTopActivity();
         if (getRequestedOverrideConfiguration().orientation != ORIENTATION_UNDEFINED
-                || root == null) {
+                || top == null) {
             return getRequestedOverrideConfiguration().orientation;
         }
-        int rootScreenOrientation = root.getOrientation();
-        if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+        int screenOrientation = top.getOrientation();
+        if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
             // NOSENSOR means the display's "natural" orientation, so return that.
             ActivityDisplay display = mStack != null ? mStack.getDisplay() : null;
             if (display != null && display.mDisplayContent != null) {
                 return mStack.getDisplay().mDisplayContent.getNaturalOrientation();
             }
-        } else if (rootScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
+        } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
             // LOCKED means the activity's orientation remains unchanged, so return existing value.
-            return root.getConfiguration().orientation;
-        } else if (ActivityInfo.isFixedOrientationLandscape(rootScreenOrientation)) {
+            return top.getConfiguration().orientation;
+        } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)) {
             return ORIENTATION_LANDSCAPE;
-        } else if (ActivityInfo.isFixedOrientationPortrait(rootScreenOrientation)) {
+        } else if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)) {
             return ORIENTATION_PORTRAIT;
         }
         return ORIENTATION_UNDEFINED;
@@ -2196,9 +2196,9 @@
             // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent"
             outOverrideBounds.setEmpty();
 
-            // If the task or its root activity require a different orientation, make it fit the
+            // If the task or its top activity requires a different orientation, make it fit the
             // available bounds by scaling down its bounds.
-            int forcedOrientation = getRootActivityRequestedOrientation();
+            int forcedOrientation = getTopActivityRequestedOrientation();
             if (forcedOrientation != ORIENTATION_UNDEFINED
                     && forcedOrientation != newParentConfig.orientation) {
                 final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index c8c5e8f..fb00aeb 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -107,6 +107,7 @@
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
+        "android.hardware.gnss.visibility_control@1.0",
         "android.hardware.input.classifier@1.0",
         "android.hardware.ir@1.0",
         "android.hardware.light@2.0",
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index bce944d..dcb2ff5 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -26,6 +26,7 @@
 #include <android/hardware/gnss/1.1/IGnssMeasurement.h>
 #include <android/hardware/gnss/2.0/IGnssMeasurement.h>
 #include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
+#include <android/hardware/gnss/visibility_control/1.0/IGnssVisibilityControl.h>
 #include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include "hardware_legacy/power.h"
@@ -88,6 +89,8 @@
 static jmethodID method_correctionPlaneLngDeg;
 static jmethodID method_correctionPlaneAltDeg;
 static jmethodID method_correctionPlaneAzimDeg;
+static jmethodID method_reportNfwNotification;
+static jmethodID method_isInEmergencySession;
 
 /*
  * Save a pointer to JavaVm to attach/detach threads executing
@@ -152,6 +155,9 @@
 using IMeasurementCorrections =
     android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections;
 
+using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControl;
+using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControlCallback;
+
 struct GnssDeathRecipient : virtual public hidl_death_recipient
 {
     // hidl_death_recipient interface
@@ -190,7 +196,7 @@
 // This boolean is needed to ensure that Gnsss Measurement Corrections related method are only
 // initalized when needed which will be few devices initially
 bool firstGnssMeasurementCorrectionInjected = false;
-
+sp<IGnssVisibilityControl> gnssVisibilityControlIface = nullptr;
 
 #define WAKE_LOCK_NAME  "GPS"
 
@@ -1090,6 +1096,54 @@
 }
 
 /*
+ * GnssVisibilityControlCallback implements callback methods of IGnssVisibilityControlCallback.hal.
+ */
+struct GnssVisibilityControlCallback : public IGnssVisibilityControlCallback {
+    Return<void> nfwNotifyCb(const IGnssVisibilityControlCallback::NfwNotification& notification)
+            override;
+    Return<bool> isInEmergencySession() override;
+};
+
+Return<void> GnssVisibilityControlCallback::nfwNotifyCb(
+        const IGnssVisibilityControlCallback::NfwNotification& notification) {
+    JNIEnv* env = getJniEnv();
+    jstring proxyAppPackageName = env->NewStringUTF(notification.proxyAppPackageName.c_str());
+    jstring otherProtocolStackName = env->NewStringUTF(notification.otherProtocolStackName.c_str());
+    jstring requestorId = env->NewStringUTF(notification.requestorId.c_str());
+
+    if (proxyAppPackageName && otherProtocolStackName && requestorId) {
+        env->CallVoidMethod(mCallbacksObj, method_reportNfwNotification, proxyAppPackageName,
+                            notification.protocolStack, otherProtocolStackName,
+                            notification.requestor, requestorId,
+                            notification.inEmergencyMode, notification.isCachedLocation);
+    } else {
+        ALOGE("%s: OOM Error\n", __func__);
+    }
+
+    if (requestorId) {
+        env->DeleteLocalRef(requestorId);
+    }
+
+    if (otherProtocolStackName) {
+        env->DeleteLocalRef(otherProtocolStackName);
+    }
+
+    if (proxyAppPackageName) {
+        env->DeleteLocalRef(proxyAppPackageName);
+    }
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+Return<bool> GnssVisibilityControlCallback::isInEmergencySession() {
+    JNIEnv* env = getJniEnv();
+    auto result = env->CallBooleanMethod(mCallbacksObj, method_isInEmergencySession);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
+/*
  * AGnssCallback_V1_0 implements callback methods required by the IAGnssCallback 1.0 interface.
  */
 struct AGnssCallback_V1_0 : public IAGnssCallback_V1_0 {
@@ -1323,6 +1377,9 @@
             "reportLocationBatch",
             "([Landroid/location/Location;)V");
     method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
+    method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification",
+            "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V");
+    method_isInEmergencySession = env->GetMethodID(clazz, "isInEmergencySession", "()Z");
 
     /*
      * Save a pointer to JVM.
@@ -1398,12 +1455,6 @@
     if (gnssHal_V2_0 != nullptr) {
         // TODO(b/119638366): getExtensionGnssMeasurement_1_1 from gnssHal_V2_0
         auto gnssMeasurement = gnssHal_V2_0->getExtensionGnssMeasurement_2_0();
-        auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections();
-        if (!gnssCorrections.isOk()) {
-            ALOGD("Unable to get a handle to GnssMeasurementCorrections interface");
-        } else {
-            gnssCorrectionsIface = gnssCorrections;
-        }
         if (!gnssMeasurement.isOk()) {
             ALOGD("Unable to get a handle to GnssMeasurement_V2_0");
         } else {
@@ -1411,6 +1462,12 @@
             gnssMeasurementIface_V1_1 = gnssMeasurementIface_V2_0;
             gnssMeasurementIface = gnssMeasurementIface_V2_0;
         }
+        auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections();
+        if (!gnssCorrections.isOk()) {
+            ALOGD("Unable to get a handle to GnssMeasurementCorrections interface");
+        } else {
+            gnssCorrectionsIface = gnssCorrections;
+        }
     } else if (gnssHal_V1_1 != nullptr) {
          auto gnssMeasurement = gnssHal_V1_1->getExtensionGnssMeasurement_1_1();
          if (!gnssMeasurement.isOk()) {
@@ -1481,6 +1538,15 @@
     } else {
         gnssBatchingIface = gnssBatching;
     }
+
+    if (gnssHal_V2_0 != nullptr) {
+        auto gnssVisibilityControl = gnssHal_V2_0->getExtensionVisibilityControl();
+        if (!gnssVisibilityControl.isOk()) {
+            ALOGD("Unable to get a handle to GnssVisibilityControl interface");
+        } else {
+            gnssVisibilityControlIface = gnssVisibilityControl;
+        }
+    }
 }
 
 static jboolean android_location_GnssLocationProvider_is_supported(
@@ -1582,7 +1648,13 @@
     if (agnssRilIface != nullptr) {
         agnssRilIface->setCallback(aGnssRilCbIface);
     } else {
-        ALOGI("Unable to Initialize AGnss Ril interface\n");
+        ALOGI("Unable to initialize AGnss Ril interface\n");
+    }
+
+    if (gnssVisibilityControlIface != nullptr) {
+        sp<IGnssVisibilityControlCallback> gnssVisibilityControlCbIface =
+                new GnssVisibilityControlCallback();
+        gnssVisibilityControlIface->setCallback(gnssVisibilityControlCbIface);
     }
 
     return JNI_TRUE;
@@ -1984,6 +2056,11 @@
     return result;
 }
 
+static jboolean android_location_GnssLocationProvider_is_gnss_visibility_control_supported(
+        JNIEnv* /* env */, jclass /* clazz */) {
+    return (gnssVisibilityControlIface != nullptr) ?  JNI_TRUE : JNI_FALSE;
+}
+
 static void android_location_GnssNetworkConnectivityHandler_update_network_state(JNIEnv* env,
                                                                        jobject /* obj */,
                                                                        jboolean connected,
@@ -2567,6 +2644,29 @@
     return gnssBatchingIface->stop();
 }
 
+static jboolean android_location_GnssVisibilityControl_enable_nfw_location_access(
+        JNIEnv* env, jobject, jobjectArray proxyApps) {
+    if (gnssVisibilityControlIface == nullptr) {
+        ALOGI("No GNSS Visibility Control interface available");
+        return JNI_FALSE;
+    }
+
+    const jsize length = env->GetArrayLength(proxyApps);
+    hidl_vec<hidl_string> hidlProxyApps(length);
+    for (int i = 0; i < length; ++i) {
+        jstring proxyApp = (jstring) (env->GetObjectArrayElement(proxyApps, i));
+        ScopedJniString jniProxyApp(env, proxyApp);
+        hidlProxyApps[i] = jniProxyApp;
+    }
+
+    auto result = gnssVisibilityControlIface->enableNfwLocationAccess(hidlProxyApps);
+    if (result.isOk()) {
+        return result;
+    } else {
+        return JNI_FALSE;
+    }
+}
+
 static const JNINativeMethod sMethods[] = {
      /* name, signature, funcPtr */
     {"class_init_native", "()V", reinterpret_cast<void *>(
@@ -2617,6 +2717,8 @@
     {"native_get_internal_state",
             "()Ljava/lang/String;",
             reinterpret_cast<void *>(android_location_GnssLocationProvider_get_internal_state)},
+    {"native_is_gnss_visibility_control_supported", "()Z", reinterpret_cast<void *>(
+            android_location_GnssLocationProvider_is_gnss_visibility_control_supported)},
 };
 
 static const JNINativeMethod sMethodsBatching[] = {
@@ -2746,6 +2848,14 @@
             reinterpret_cast<void *>(android_location_GnssConfiguration_set_es_extension_sec)},
 };
 
+static const JNINativeMethod sVisibilityControlMethods[] = {
+     /* name, signature, funcPtr */
+    {"native_enable_nfw_location_access",
+            "([Ljava/lang/String;)Z",
+            reinterpret_cast<void *>(
+                    android_location_GnssVisibilityControl_enable_nfw_location_access)},
+};
+
 int register_android_server_location_GnssLocationProvider(JNIEnv* env) {
     jniRegisterNativeMethods(
             env,
@@ -2777,6 +2887,11 @@
             "com/android/server/location/GnssConfiguration",
             sConfigurationMethods,
             NELEM(sConfigurationMethods));
+    jniRegisterNativeMethods(
+            env,
+            "com/android/server/location/GnssVisibilityControl",
+            sVisibilityControlMethods,
+            NELEM(sVisibilityControlMethods));
     return jniRegisterNativeMethods(
             env,
             "com/android/server/location/GnssLocationProvider",
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
new file mode 100644
index 0000000..eaab650
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Encapsulating class for using the SQLite database backing the memory store.
+ *
+ * This class groups together the contracts and the SQLite helper used to
+ * use the database.
+ *
+ * @hide
+ */
+public class IpMemoryStoreDatabase {
+    /**
+     * Contract class for the Network Attributes table.
+     */
+    public static class NetworkAttributesContract {
+        public static final String TABLENAME = "NetworkAttributes";
+
+        public static final String COLNAME_L2KEY = "l2Key";
+        public static final String COLTYPE_L2KEY = "TEXT NOT NULL";
+
+        public static final String COLNAME_EXPIRYDATE = "expiryDate";
+        // Milliseconds since the Epoch, in true Java style
+        public static final String COLTYPE_EXPIRYDATE = "BIGINT";
+
+        public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address";
+        public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER";
+
+        // Please note that the group hint is only a *hint*, hence its name. The client can offer
+        // this information to nudge the grouping in the decision it thinks is right, but it can't
+        // decide for the memory store what is the same L3 network.
+        public static final String COLNAME_GROUPHINT = "groupHint";
+        public static final String COLTYPE_GROUPHINT = "TEXT";
+
+        public static final String COLNAME_DNSADDRESSES = "dnsAddresses";
+        // Stored in marshalled form as is
+        public static final String COLTYPE_DNSADDRESSES = "BLOB";
+
+        public static final String COLNAME_MTU = "mtu";
+        public static final String COLTYPE_MTU = "INTEGER";
+
+        public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
+                + TABLENAME                 + " ("
+                + COLNAME_L2KEY             + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
+                + COLNAME_EXPIRYDATE        + " " + COLTYPE_EXPIRYDATE        + ", "
+                + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", "
+                + COLNAME_GROUPHINT         + " " + COLTYPE_GROUPHINT         + ", "
+                + COLNAME_DNSADDRESSES      + " " + COLTYPE_DNSADDRESSES      + ", "
+                + COLNAME_MTU               + " " + COLTYPE_MTU               + ")";
+        public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
+    }
+
+    /**
+     * Contract class for the Private Data table.
+     */
+    public static class PrivateDataContract {
+        public static final String TABLENAME = "PrivateData";
+
+        public static final String COLNAME_L2KEY = "l2Key";
+        public static final String COLTYPE_L2KEY = "TEXT NOT NULL";
+
+        public static final String COLNAME_CLIENT = "client";
+        public static final String COLTYPE_CLIENT = "TEXT NOT NULL";
+
+        public static final String COLNAME_DATANAME = "dataName";
+        public static final String COLTYPE_DATANAME = "TEXT NOT NULL";
+
+        public static final String COLNAME_DATA = "data";
+        public static final String COLTYPE_DATA = "BLOB NOT NULL";
+
+        public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
+                + TABLENAME        + " ("
+                + COLNAME_L2KEY    + " " + COLTYPE_L2KEY    + ", "
+                + COLNAME_CLIENT   + " " + COLTYPE_CLIENT   + ", "
+                + COLNAME_DATANAME + " " + COLTYPE_DATANAME + ", "
+                + COLNAME_DATA     + " " + COLTYPE_DATA     + ", "
+                + "PRIMARY KEY ("
+                + COLNAME_L2KEY    + ", "
+                + COLNAME_CLIENT   + ", "
+                + COLNAME_DATANAME + "))";
+        public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
+    }
+
+    // To save memory when the DB is not used, close it after 30s of inactivity. This is
+    // determined manually based on what feels right.
+    private static final long IDLE_CONNECTION_TIMEOUT_MS = 30_000;
+
+    /** The SQLite DB helper */
+    public static class DbHelper extends SQLiteOpenHelper {
+        // Update this whenever changing the schema.
+        private static final int SCHEMA_VERSION = 1;
+        private static final String DATABASE_FILENAME = "IpMemoryStore.db";
+
+        public DbHelper(@NonNull final Context context) {
+            super(context, DATABASE_FILENAME, null, SCHEMA_VERSION);
+            setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+        }
+
+        /** Called when the database is created */
+        public void onCreate(@NonNull final SQLiteDatabase db) {
+            db.execSQL(NetworkAttributesContract.CREATE_TABLE);
+            db.execSQL(PrivateDataContract.CREATE_TABLE);
+        }
+
+        /** Called when the database is upgraded */
+        public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
+                final int newVersion) {
+            // No upgrade supported yet.
+            db.execSQL(NetworkAttributesContract.DROP_TABLE);
+            db.execSQL(PrivateDataContract.DROP_TABLE);
+            onCreate(db);
+        }
+
+        /** Called when the database is downgraded */
+        public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
+                final int newVersion) {
+            // Downgrades always nuke all data and recreate an empty table.
+            db.execSQL(NetworkAttributesContract.DROP_TABLE);
+            db.execSQL(PrivateDataContract.DROP_TABLE);
+            onCreate(db);
+        }
+    }
+}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index c9759bf..55a72190 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
 import android.net.IIpMemoryStore;
 import android.net.ipmemorystore.Blob;
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
@@ -27,6 +29,10 @@
 import android.net.ipmemorystore.IOnSameNetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.util.Log;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * Implementation for the IP memory store.
@@ -37,10 +43,75 @@
  * @hide
  */
 public class IpMemoryStoreService extends IIpMemoryStore.Stub {
-    final Context mContext;
+    private static final String TAG = IpMemoryStoreService.class.getSimpleName();
+    private static final int MAX_CONCURRENT_THREADS = 4;
 
+    @NonNull
+    final Context mContext;
+    @Nullable
+    final SQLiteDatabase mDb;
+    @NonNull
+    final ExecutorService mExecutor;
+
+    /**
+     * Construct an IpMemoryStoreService object.
+     * This constructor will block on disk access to open the database.
+     * @param context the context to access storage with.
+     */
     public IpMemoryStoreService(@NonNull final Context context) {
+        // Note that constructing the service will access the disk and block
+        // for some time, but it should make no difference to the clients. Because
+        // the interface is one-way, clients fire and forget requests, and the callback
+        // will get called eventually in any case, and the framework will wait for the
+        // service to be created to deliver subsequent requests.
+        // Avoiding this would mean the mDb member can't be final, which means the service would
+        // have to test for nullity, care for failure, and allow for a wait at every single access,
+        // which would make the code a lot more complex and require all methods to possibly block.
         mContext = context;
+        SQLiteDatabase db;
+        final IpMemoryStoreDatabase.DbHelper helper = new IpMemoryStoreDatabase.DbHelper(context);
+        try {
+            db = helper.getWritableDatabase();
+            if (null == db) Log.e(TAG, "Unexpected null return of getWriteableDatabase");
+        } catch (final SQLException e) {
+            Log.e(TAG, "Can't open the Ip Memory Store database", e);
+            db = null;
+        } catch (final Exception e) {
+            Log.wtf(TAG, "Impossible exception Ip Memory Store database", e);
+            db = null;
+        }
+        mDb = db;
+        // The work-stealing thread pool executor will spawn threads as needed up to
+        // the max only when there is no free thread available. This generally behaves
+        // exactly like one would expect it intuitively :
+        // - When work arrives, it will spawn a new thread iff there are no available threads
+        // - When there is no work to do it will shutdown threads after a while (the while
+        //   being equal to 2 seconds (not configurable) when max threads are spun up and
+        //   twice as much for every one less thread)
+        // - When all threads are busy the work is enqueued and waits for any worker
+        //   to become available.
+        // Because the stealing pool is made for very heavily parallel execution of
+        // small tasks that spawn others, it creates a queue per thread that in this
+        // case is overhead. However, the three behaviors above make it a superior
+        // choice to cached or fixedThreadPoolExecutor, neither of which can actually
+        // enqueue a task waiting for a thread to be free. This can probably be solved
+        // with judicious subclassing of ThreadPoolExecutor, but that's a lot of dangerous
+        // complexity for little benefit in this case.
+        mExecutor = Executors.newWorkStealingPool(MAX_CONCURRENT_THREADS);
+    }
+
+    /**
+     * Shutdown the memory store service, cancelling running tasks and dropping queued tasks.
+     *
+     * This is provided to give a way to clean up, and is meant to be available in case of an
+     * emergency shutdown.
+     */
+    public void shutdown() {
+        // By contrast with ExecutorService#shutdown, ExecutorService#shutdownNow tries
+        // to cancel the existing tasks, and does not wait for completion. It does not
+        // guarantee the threads can be terminated in any given amount of time.
+        mExecutor.shutdownNow();
+        if (mDb != null) mDb.close();
     }
 
     /**
@@ -61,7 +132,7 @@
     public void storeNetworkAttributes(@NonNull final String l2Key,
             @NonNull final NetworkAttributesParcelable attributes,
             @Nullable final IOnStatusListener listener) {
-        // TODO : implement this
+        // TODO : implement this.
     }
 
     /**
@@ -79,7 +150,7 @@
     public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
             @NonNull final String name, @NonNull final Blob data,
             @Nullable final IOnStatusListener listener) {
-        // TODO : implement this
+        // TODO : implement this.
     }
 
     /**
@@ -99,7 +170,7 @@
     @Override
     public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
             @NonNull final IOnL2KeyResponseListener listener) {
-        // TODO : implement this
+        // TODO : implement this.
     }
 
     /**
@@ -114,7 +185,7 @@
     @Override
     public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
             @NonNull final IOnSameNetworkResponseListener listener) {
-        // TODO : implement this
+        // TODO : implement this.
     }
 
     /**
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java
new file mode 100644
index 0000000..aa45400
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/RelevanceUtils.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A class containing the logic around the relevance value for
+ * IP Memory Store.
+ *
+ * @hide
+ */
+public class RelevanceUtils {
+    /**
+     * The relevance is a decaying value that gets lower and lower until it
+     * reaches 0 after some time passes. It follows an exponential decay law,
+     * dropping slowly at first then faster and faster, because a network is
+     * likely to be visited again if it was visited not long ago, and the longer
+     * it hasn't been visited the more likely it is that it won't be visited
+     * again. For example, a network visited on holiday should stay fresh for
+     * the duration of the holiday and persist for a while, but after the venue
+     * hasn't been visited for a while it should quickly be discarded. What
+     * should accelerate forgetting the network is extended periods without
+     * visits, so that occasional venues get discarded but regular visits keep
+     * the network relevant, even if the visits are infrequent.
+     *
+     * This function must be stable by iteration, meaning that adjusting the same value
+     * for different dates iteratively multiple times should give the same result.
+     * Formally, if f is the decay function that associates a relevance x at a date d1
+     * to the value at ulterior date d3, then for any date d2 between d1 and d3 :
+     * f(x, d3 - d1) = f(f(x, d3 - d2), d2 - d1). Intuitively, this property simply
+     * means it should be the same to compute and store back the value after two months,
+     * or to do it once after one month, store it back, and do it again after another
+     * months has passed.
+     * The pair of the relevance and date define the entire curve, so any pair
+     * of values on the curve will define the same curve. Setting one of them to a
+     * constant, so as not to have to store it, means the other one will always suffice
+     * to describe the curve. For example, only storing the date for a known, constant
+     * value of the relevance is an efficient way of remembering this information (and
+     * to compare relevances together, as f is monotonically decreasing).
+     *
+     *** Choosing the function :
+     * Functions of the kind described above are standard exponential decay functions
+     * like the ones that govern atomic decay where the value at any given date can be
+     * computed uniformly from the value at a previous date and the time elapsed since
+     * that date. It is simple to picture this kind of function as one where after a
+     * given period of time called the half-life, the relevance value will have been
+     * halved. Decay of this kind is expressed in function of the previous value by
+     * functions like
+     * f(x, t) = x * F ^ (t / L)
+     * ...where x is the value, t is the elapsed time, L is the half-life (or more
+     * generally the F-th-life) and F the decay factor (typically 0.5, hence why L is
+     * usually called the half-life). The ^ symbol here is used for exponentiation.
+     * Or, starting at a given M for t = 0 :
+     * f(t) = M * F ^ (t / L)
+     *
+     * Because a line in the store needs to become irrelevant at some point but
+     * this class of functions never go to 0, a minimum cutoff has to be chosen to
+     * represent irrelevance. The simpler way of doing this is to simply add this
+     * minimum cutoff to the computation before and removing it after.
+     * Thus the function becomes :
+     * f(x, t) = ((x + K) * F ^ (t / L)) - K
+     * ...where K is the minimum cutoff, L the half-life, and F the factor between
+     * the original x and x after its half-life. Strictly speaking using the word
+     * "half-life" implies that F = 0.5, but the relation works for any value of F.
+     *
+     * It is easy enough to check that this function satisfies the stability
+     * relation that was given above for any value of F, L and K, which become
+     * parameters that can be defined at will.
+     *
+     * relevance
+     *  1.0 |
+     *      |\
+     *      | \
+     *      |  \            (this graph rendered with L = 75 days and K = 1/40)
+     *  0.75|   ',
+     *      |     \
+     *      |      '.
+     *      |        \.
+     *      |          \
+     *  0.5 |           '\
+     *      |             ''.
+     *      |                ''.
+     *      |                   ''.
+     *  0.25|                      '''..
+     *      |                           '''..
+     *      |                                ''''....
+     *      |                                        '''''..........
+     *    0 +-------------------------------------------------------''''''''''----
+     *      0       50       100      150     200      250     300      350     400 days
+     *
+     *** Choosing the parameters
+     * The maximum M is an arbitrary parameter that simply scales the curve.
+     * The tradeoff for M is pretty simple : if the relevance is going to be an
+     * integer, the bigger M is the more precision there is in the relevance.
+     * However, values of M that are easy for humans to read are preferable to
+     * help debugging, and a suitably low value may be enough to ensure there
+     * won't be integer overflows in intermediate computations.
+     * A value of 1_000_000 probably is plenty for precision, while still in the
+     * low range of what ints can represent.
+     *
+     * F and L are parameters to be chosen arbitrarily and have an impact on how
+     * fast the relevance will be decaying at first, keeping in mind that
+     * the 400 days value and the cap stay the same. In simpler words, F and L
+     * define the steepness of the curve.
+     * To keep things simple (and familiar) F is arbitrarily chosen to be 0.5, and
+     * L is set to 200 days visually to achieve the desired effect. Refer to the
+     * illustration above to get a feel of how that feels.
+     *
+     * Moreover, the memory store works on an assumption that the relevance should
+     * be capped, and that an entry with capped relevance should decay in 400 days.
+     * This is on premises that the networks a device will need to remember the
+     * longest should be networks visited about once a year.
+     * For this reason, the relevance is at the maximum M 400 days before expiry :
+     * f(M, 400 days) = 0
+     * From replacing this with the value of the function, K can then be derived
+     * from the values of M, F and L :
+     * (M + K) * F ^ (t / L) - K = 0
+     * K = M * F ^ (400 days / L) / (1 - F ^ (400 days / L))
+     * Replacing with actual values this gives :
+     * K = 1_000_000 * 0.5 ^ (400 / 200) / (1 - 0.5 ^ (400 / 200))
+     *   = 1_000_000 / 3 ≈ 333_333.3
+     * This ensures the function has the desired profile, the desired value at
+     * cap, and the desired value at expiry.
+     *
+     *** Useful relations
+     * Let's define the expiry time for any given relevance x as the interval of
+     * time such as :
+     * f(x, expiry) = 0
+     * which can be rewritten
+     * ((x + K) * F ^ (expiry / L)) = K
+     * ...giving an expression of the expiry in function of the relevance x as
+     * expiry = L * logF(K / (x + K))
+     * Conversely the relevance x can be expressed in function of the expiry as
+     * x = K / F ^ (expiry / L) - K
+     * These relations are useful in utility functions.
+     *
+     *** Bumping things up
+     * The last issue therefore is to decide how to bump up the relevance. The
+     * simple approach is to simply lift up the curve a little bit by a constant
+     * normalized amount, delaying the time of expiry. For example increasing
+     * the relevance by an amount I gives :
+     * x2 = x1 + I
+     * x2 and x1 correspond to two different expiry times expiry2 and expiry1,
+     * and replacing x1 and x2 in the relation above with their expression in
+     * function of the expiry comes :
+     * K / F ^ (expiry2 / L) - K = K / F ^ (expiry1 / L) - K + I
+     * which resolves to :
+     * expiry2 = L * logF(K / (I + K / F ^ (expiry1 / L)))
+     *
+     * In this implementation, the bump is defined as 1/25th of the cap for
+     * the relevance. This means a network will be remembered for the maximum
+     * period of 400 days if connected 25 times in succession not accounting
+     * for decay. Of course decay actually happens so it will take more than 25
+     * connections for any given network to actually reach the cap, but because
+     * decay is slow at first, it is a good estimate of how fast cap happens.
+     *
+     * Specifically, it gives the following four results :
+     * - A network that a device connects to once hits irrelevance about 32.7 days after
+     *   it was first registered if never connected again.
+     * - A network that a device connects to once a day at a fixed hour will hit the cap
+     *   on the 27th connection.
+     * - A network that a device connects to once a week at a fixed hour will hit the cap
+     *   on the 57th connection.
+     * - A network that a device connects to every day for 7 straight days then never again
+     *   expires 144 days after the last connection.
+     * These metrics tend to match pretty well the requirements.
+     */
+
+    // TODO : make these constants configurable at runtime. Don't forget to build it so that
+    // changes will wipe the database, migrate the values, or otherwise make sure the relevance
+    // values are still meaningful.
+
+    // How long, in milliseconds, is a capped relevance valid for, or in other
+    // words how many milliseconds after its relevance was set to RELEVANCE_CAP does
+    // any given line expire. 400 days.
+    @VisibleForTesting
+    public static final long CAPPED_RELEVANCE_LIFETIME_MS = 400L * 24 * 60 * 60 * 1000;
+
+    // The constant that represents a normalized 1.0 value for the relevance. In other words,
+    // the cap for the relevance. This is referred to as M in the explanation above.
+    @VisibleForTesting
+    public static final int CAPPED_RELEVANCE = 1_000_000;
+
+    // The decay factor. After a half-life, the relevance will have decayed by this value.
+    // This is referred to as F in the explanation above.
+    private static final double DECAY_FACTOR = 0.5;
+
+    // The half-life. After this time, the relevance will have decayed by a factor DECAY_FACTOR.
+    // This is referred to as L in the explanation above.
+    private static final long HALF_LIFE_MS = 200L * 24 * 60 * 60 * 1000;
+
+    // The value of the frame change. This is referred to as K in the explanation above.
+    private static final double IRRELEVANCE_FLOOR =
+            CAPPED_RELEVANCE * powF((double) CAPPED_RELEVANCE_LIFETIME_MS / HALF_LIFE_MS)
+            / (1 - powF((double) CAPPED_RELEVANCE_LIFETIME_MS / HALF_LIFE_MS));
+
+    // How much to bump the relevance by every time a line is written to.
+    @VisibleForTesting
+    public static final int RELEVANCE_BUMP = CAPPED_RELEVANCE / 25;
+
+    // Java doesn't include a function for the logarithm in an arbitrary base, so implement it
+    private static final double LOG_DECAY_FACTOR = Math.log(DECAY_FACTOR);
+    private static double logF(final double value) {
+        return Math.log(value) / LOG_DECAY_FACTOR;
+    }
+
+    // Utility function to get a power of the decay factor, to simplify the code.
+    private static double powF(final double value) {
+        return Math.pow(DECAY_FACTOR, value);
+    }
+
+    /**
+     * Compute the value of the relevance now given an expiry date.
+     *
+     * @param expiry the date at which the column in the database expires.
+     * @return the adjusted value of the relevance for this moment in time.
+     */
+    public static int computeRelevanceForNow(final long expiry) {
+        return computeRelevanceForTargetDate(expiry, System.currentTimeMillis());
+    }
+
+    /**
+     * Compute the value of the relevance at a given date from an expiry date.
+     *
+     * Because relevance decays with time, a relevance in the past corresponds to
+     * a different relevance later.
+     *
+     * Relevance is always a positive value. 0 means not relevant at all.
+     *
+     * See the explanation at the top of this file to get the justification for this
+     * computation.
+     *
+     * @param expiry the date at which the column in the database expires.
+     * @param target the target date to adjust the relevance to.
+     * @return the adjusted value of the relevance for the target moment.
+     */
+    public static int computeRelevanceForTargetDate(final long expiry, final long target) {
+        final long delay = expiry - target;
+        if (delay >= CAPPED_RELEVANCE_LIFETIME_MS) return CAPPED_RELEVANCE;
+        if (delay <= 0) return 0;
+        return (int) (IRRELEVANCE_FLOOR / powF((float) delay / HALF_LIFE_MS) - IRRELEVANCE_FLOOR);
+    }
+
+    /**
+     * Compute the expiry duration adjusted up for a new fresh write.
+     *
+     * Every time data is written to the memory store for a given line, the
+     * relevance is bumped up by a certain amount, which will boost the priority
+     * of this line for computation of group attributes, and delay (possibly
+     * indefinitely, if the line is accessed regularly) forgetting the data stored
+     * in that line.
+     * As opposed to bumpExpiryDate, this function uses a duration from now to expiry.
+     *
+     * See the explanation at the top of this file for a justification of this computation.
+     *
+     * @param oldExpiryDuration the old expiry duration in milliseconds from now.
+     * @return the expiry duration representing a bumped up relevance value.
+     */
+    public static long bumpExpiryDuration(final long oldExpiryDuration) {
+        // L * logF(K / (I + K / F ^ (expiry1 / L))), as documented above
+        final double divisionFactor = powF(((double) oldExpiryDuration) / HALF_LIFE_MS);
+        final double oldRelevance = IRRELEVANCE_FLOOR / divisionFactor;
+        final long newDuration =
+                (long) (HALF_LIFE_MS * logF(IRRELEVANCE_FLOOR / (RELEVANCE_BUMP + oldRelevance)));
+        return Math.min(newDuration, CAPPED_RELEVANCE_LIFETIME_MS);
+    }
+
+    /**
+     * Compute the new expiry date adjusted up for a new fresh write.
+     *
+     * Every time data is written to the memory store for a given line, the
+     * relevance is bumped up by a certain amount, which will boost the priority
+     * of this line for computation of group attributes, and delay (possibly
+     * indefinitely, if the line is accessed regularly) forgetting the data stored
+     * in that line.
+     * As opposed to bumpExpiryDuration, this function takes the old timestamp and returns the
+     * new timestamp.
+     *
+     * {@see bumpExpiryDuration}, and keep in mind that the bump depends on when this is called,
+     * because the relevance decays exponentially, therefore bumping up a high relevance (for a
+     * date far in the future) is less potent than bumping up a low relevance (for a date in
+     * a close future).
+     *
+     * @param oldExpiryDate the old date of expiration.
+     * @return the new expiration date after the relevance bump.
+     */
+    public static long bumpExpiryDate(final long oldExpiryDate) {
+        final long now = System.currentTimeMillis();
+        final long newDuration = bumpExpiryDuration(oldExpiryDate - now);
+        return now + newDuration;
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
index 7559560..9a6e003 100644
--- a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
@@ -58,7 +58,9 @@
 import com.android.server.backup.transport.TransportClient;
 import com.android.server.backup.transport.TransportClientManager;
 import com.android.server.backup.transport.TransportNotRegisteredException;
+import com.android.server.testing.shadows.ShadowApplicationPackageManager;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,6 +68,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowPackageManager;
 
 import java.util.ArrayList;
@@ -75,6 +78,7 @@
 import java.util.stream.Stream;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationPackageManager.class})
 @Presubmit
 public class TransportManagerTest {
     private static final String PACKAGE_A = "some.package.a";
@@ -102,6 +106,12 @@
         mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz");
     }
 
+    /** Reset shadow state. */
+    @After
+    public void tearDown() throws Exception {
+        ShadowApplicationPackageManager.reset();
+    }
+
     @Test
     public void testRegisterTransports() throws Exception {
         setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
@@ -666,7 +676,8 @@
         packageInfo.packageName = packageName;
         packageInfo.applicationInfo = new ApplicationInfo();
         packageInfo.applicationInfo.privateFlags = flags;
-        mShadowPackageManager.addPackage(packageInfo);
+        mShadowPackageManager.installPackage(packageInfo);
+        ShadowApplicationPackageManager.addInstalledPackage(packageName, packageInfo);
     }
 
     private TransportManager createTransportManagerWithRegisteredTransports(
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 3b7fa3d..427aed7 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -514,10 +514,14 @@
     private void setUpForUpdateTransportAttributes() throws Exception {
         mTransportComponent = mTransport.getTransportComponent();
         String transportPackage = mTransportComponent.getPackageName();
+        PackageInfo packageInfo = getPackageInfo(transportPackage);
 
         ShadowPackageManager shadowPackageManager = shadowOf(mContext.getPackageManager());
-        shadowPackageManager.addPackage(transportPackage);
+        shadowPackageManager.installPackage(packageInfo);
         shadowPackageManager.setPackagesForUid(PACKAGE_UID, transportPackage);
+        // Set up for user invocations on ApplicationPackageManager.
+        ShadowApplicationPackageManager.addInstalledPackage(transportPackage, packageInfo);
+        ShadowApplicationPackageManager.setPackageUid(transportPackage, PACKAGE_UID);
 
         mTransportUid = mContext.getPackageManager().getPackageUid(transportPackage, 0);
     }
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
index dc32209..ab121ed 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
@@ -38,6 +38,7 @@
         extends org.robolectric.shadows.ShadowApplicationPackageManager {
     private static final Map<String, PackageInfo> sPackageInfos = new ArrayMap<>();
     private static final List<PackageInfo> sInstalledPackages = new ArrayList<>();
+    private static final Map<String, Integer> sPackageUids = new ArrayMap<>();
 
     /**
      * Registers the package {@code packageName} to be returned when invoking {@link
@@ -49,6 +50,14 @@
         sInstalledPackages.add(packageInfo);
     }
 
+    /**
+     * Sets the package uid {@code packageUid} for the package {@code packageName} to be returned
+     * when invoking {@link ApplicationPackageManager#getPackageUidAsUser(String, int, int)}.
+     */
+    public static void setPackageUid(String packageName, int packageUid) {
+        sPackageUids.put(packageName, packageUid);
+    }
+
     @Override
     protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
@@ -63,6 +72,15 @@
         return sInstalledPackages;
     }
 
+    @Override
+    protected int getPackageUidAsUser(String packageName, int flags, int userId)
+            throws NameNotFoundException {
+        if (!sPackageUids.containsKey(packageName)) {
+            throw new NameNotFoundException(packageName);
+        }
+        return sPackageUids.get(packageName);
+    }
+
     /** Clear package state. */
     @Resetter
     public static void reset() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index f1cd0cd..57ee6dc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -33,6 +33,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -43,7 +44,12 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
+import android.app.AppGlobals;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
 import android.app.job.JobInfo;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
@@ -56,13 +62,16 @@
 import android.os.BatteryManagerInternal;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
+import android.util.SparseBooleanArray;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.job.JobStore;
 import com.android.server.job.controllers.QuotaController.ExecutionStats;
 import com.android.server.job.controllers.QuotaController.TimingSession;
 
@@ -96,9 +105,13 @@
     private BroadcastReceiver mChargingReceiver;
     private Constants mConstants;
     private QuotaController mQuotaController;
+    private int mSourceUid;
+    private IUidObserver mUidObserver;
 
     private MockitoSession mMockingSession;
     @Mock
+    private ActivityManagerInternal mActivityMangerInternal;
+    @Mock
     private AlarmManager mAlarmManager;
     @Mock
     private Context mContext;
@@ -107,6 +120,8 @@
     @Mock
     private UsageStatsManagerInternal mUsageStatsManager;
 
+    private JobStore mJobStore;
+
     @Before
     public void setUp() {
         mMockingSession = mockitoSession()
@@ -123,8 +138,17 @@
         when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
         when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
         // Called in QuotaController constructor.
+        IActivityManager activityManager = ActivityManager.getService();
+        spyOn(activityManager);
+        try {
+            doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
+        } catch (RemoteException e) {
+            fail("registerUidObserver threw exception: " + e.getMessage());
+        }
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
         when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+        doReturn(mActivityMangerInternal)
+                .when(() -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mock(BatteryManagerInternal.class))
                 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
         doReturn(mUsageStatsManager)
@@ -132,6 +156,9 @@
         // Used in JobStatus.
         doReturn(mock(PackageManagerInternal.class))
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
+        // Used in QuotaController.Handler.
+        mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
+        when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
 
         // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
         // in the past, and QuotaController sometimes floors values at 0, so if the test time
@@ -150,10 +177,23 @@
         // Capture the listeners.
         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
+        ArgumentCaptor<IUidObserver> uidObserverCaptor =
+                ArgumentCaptor.forClass(IUidObserver.class);
         mQuotaController = new QuotaController(mJobSchedulerService);
 
         verify(mContext).registerReceiver(receiverCaptor.capture(), any());
         mChargingReceiver = receiverCaptor.getValue();
+        try {
+            verify(activityManager).registerUidObserver(
+                    uidObserverCaptor.capture(),
+                    eq(ActivityManager.UID_OBSERVER_PROCSTATE),
+                    eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE),
+                    any());
+            mUidObserver = uidObserverCaptor.getValue();
+            mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
+        } catch (RemoteException e) {
+            fail(e.getMessage());
+        }
     }
 
     @After
@@ -182,6 +222,25 @@
         mChargingReceiver.onReceive(mContext, intent);
     }
 
+    private void setProcessState(int procState) {
+        try {
+            doReturn(procState).when(mActivityMangerInternal).getUidProcessState(mSourceUid);
+            SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
+            spyOn(foregroundUids);
+            mUidObserver.onUidStateChanged(mSourceUid, procState, 0);
+            if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+                verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1))
+                        .put(eq(mSourceUid), eq(true));
+                assertTrue(foregroundUids.get(mSourceUid));
+            } else {
+                verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1)).delete(eq(mSourceUid));
+                assertFalse(foregroundUids.get(mSourceUid));
+            }
+        } catch (RemoteException e) {
+            fail("registerUidObserver threw exception: " + e.getMessage());
+        }
+    }
+
     private void setStandbyBucket(int bucketIndex) {
         int bucket;
         switch (bucketIndex) {
@@ -204,9 +263,18 @@
                 anyLong())).thenReturn(bucket);
     }
 
-    private void setStandbyBucket(int bucketIndex, JobStatus job) {
+    private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
         setStandbyBucket(bucketIndex);
-        job.setStandbyBucket(bucketIndex);
+        for (JobStatus job : jobs) {
+            job.setStandbyBucket(bucketIndex);
+        }
+    }
+
+    private void trackJobs(JobStatus... jobs) {
+        for (JobStatus job : jobs) {
+            mJobStore.add(job);
+            mQuotaController.maybeStartTrackingJobLocked(job, null);
+        }
     }
 
     private JobStatus createJobStatus(String testTag, int jobId) {
@@ -214,8 +282,11 @@
                 new ComponentName(mContext, "TestQuotaJobService"))
                 .setMinimumLatency(Math.abs(jobId) + 1)
                 .build();
-        return JobStatus.createFromJobInfo(
+        JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+        // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
+        js.setStandbyBucket(FREQUENT_INDEX);
+        return js;
     }
 
     private TimingSession createTimingSession(long start, long duration, int count) {
@@ -709,6 +780,7 @@
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
+        setStandbyBucket(standbyBucket, jobStatus);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
         mQuotaController.prepareForExecutionLocked(jobStatus);
         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
@@ -1339,19 +1411,23 @@
         setDischarging();
 
         JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
-        jobStatus.uidActive = true;
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
 
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
         mQuotaController.prepareForExecutionLocked(jobStatus);
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        // Change to a state that should still be considered foreground.
+        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
 
     /**
-     * Tests that Timers properly track overlapping foreground and background jobs.
+     * Tests that Timers properly track sessions when switching between foreground and background
+     * states.
      */
     @Test
     public void testTimerTracking_ForegroundAndBackground() {
@@ -1360,7 +1436,6 @@
         JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
         JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
         JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
-        jobFg3.uidActive = true;
         mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
         mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
@@ -1368,6 +1443,7 @@
         List<TimingSession> expected = new ArrayList<>();
 
         // UID starts out inactive.
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
         mQuotaController.prepareForExecutionLocked(jobBg1);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
@@ -1379,48 +1455,223 @@
 
         // Bg job starts while inactive, spans an entire active session, and ends after the
         // active session.
-        // Fg job starts after the bg job and ends before the bg job.
-        // Entire bg job duration should be counted since it started before active session. However,
-        // count should only be 1 since Timer shouldn't count fg jobs.
+        // App switching to foreground state then fg job starts.
+        // App remains in foreground state after coming to foreground, so there should only be one
+        // session.
         start = JobSchedulerService.sElapsedRealtimeClock.millis();
         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
         mQuotaController.prepareForExecutionLocked(jobBg2);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         mQuotaController.prepareForExecutionLocked(jobFg3);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
-        expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
         advanceElapsedClock(SECOND_IN_MILLIS);
 
         // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
         // "inactive" and then bg job 2 starts. Then fg job ends.
-        // This should result in two TimingSessions with a count of one each.
+        // This should result in two TimingSessions:
+        //  * The first should have a count of 1
+        //  * The second should have a count of 2 since it will include both jobs
         start = JobSchedulerService.sElapsedRealtimeClock.millis();
         mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
         mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
+        setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
         mQuotaController.prepareForExecutionLocked(jobBg1);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         mQuotaController.prepareForExecutionLocked(jobFg3);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
-        expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
         start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
         mQuotaController.prepareForExecutionLocked(jobBg2);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
-        expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
+        expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
 
     /**
+     * Tests that Timers properly track overlapping top and background jobs.
+     */
+    @Test
+    public void testTimerTracking_TopAndNonTop() {
+        setDischarging();
+
+        JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1);
+        JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2);
+        JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3);
+        JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+        List<TimingSession> expected = new ArrayList<>();
+
+        // UID starts out inactive.
+        setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+        long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.prepareForExecutionLocked(jobBg1);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+
+        // Bg job starts while inactive, spans an entire active session, and ends after the
+        // active session.
+        // App switching to top state then fg job starts.
+        // App remains in top state after coming to top, so there should only be one
+        // session.
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+        mQuotaController.prepareForExecutionLocked(jobBg2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        mQuotaController.prepareForExecutionLocked(jobTop);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+
+        // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
+        // foreground_service and a new job starts. Shortly after, uid goes
+        // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
+        // This should result in two TimingSessions:
+        //  * The first should have a count of 1
+        //  * The second should have a count of 2, which accounts for the bg2 and fg, but not top
+        //    jobs.
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
+        setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
+        mQuotaController.prepareForExecutionLocked(jobBg1);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        mQuotaController.prepareForExecutionLocked(jobTop);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mQuotaController.prepareForExecutionLocked(jobFg1);
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        mQuotaController.prepareForExecutionLocked(jobBg2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+        mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+        expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+    }
+
+    /**
+     * Tests that TOP jobs aren't stopped when an app runs out of quota.
+     */
+    @Test
+    public void testTracking_OutOfQuota_ForegroundAndBackground() {
+        setDischarging();
+
+        JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
+        JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
+        trackJobs(jobBg, jobTop);
+        setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
+        // Now the package only has 20 seconds to run.
+        final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(
+                        JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+                        10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
+
+        InOrder inOrder = inOrder(mJobSchedulerService);
+
+        // UID starts out inactive.
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+        // Start the job.
+        mQuotaController.prepareForExecutionLocked(jobBg);
+        advanceElapsedClock(remainingTimeMs / 2);
+        // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+        // should continue to have remainingTimeMs / 2 time remaining.
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        mQuotaController.prepareForExecutionLocked(jobTop);
+        advanceElapsedClock(remainingTimeMs);
+
+        // Wait for some extra time to allow for job processing.
+        inOrder.verify(mJobSchedulerService,
+                timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+                .onControllerStateChanged();
+        assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg));
+        assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop));
+        // Go to a background state.
+        setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        advanceElapsedClock(remainingTimeMs / 2 + 1);
+        inOrder.verify(mJobSchedulerService,
+                timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged();
+        // Top job should still be allowed to run.
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+        // New jobs to run.
+        JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
+        JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
+        JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
+        setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_TOP);
+        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged();
+        trackJobs(jobFg, jobTop);
+        mQuotaController.prepareForExecutionLocked(jobTop);
+        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+        // App still in foreground so everything should be in quota.
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+        inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged();
+        // App is now in background and out of quota. Fg should now change to out of quota since it
+        // wasn't started. Top should remain in quota since it started when the app was in TOP.
+        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+        trackJobs(jobBg2);
+        assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+    }
+
+    /**
      * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
      * its quota.
      */
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 823b7a5..3ebc6ad 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -544,6 +544,18 @@
         switchUser(-1, UserHandle.of(startUser), false);
     }
 
+    public void testSwitchUserByHandle_ThrowsException() {
+        synchronized (mUserSwitchLock) {
+            try {
+                ActivityManager am = getContext().getSystemService(ActivityManager.class);
+                am.switchUser(null);
+                fail("Expected IllegalArgumentException on passing in a null UserHandle.");
+            } catch (IllegalArgumentException expected) {
+                // Do nothing - exception is expected.
+            }
+        }
+    }
+
     @MediumTest
     public void testConcurrentUserCreate() throws Exception {
         int userCount = mUserManager.getUserCount();
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
index 3b6b48b..f817e8e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
@@ -77,7 +77,6 @@
 
     @Mock IPackageManager mPM;
     @Mock Installer mInstaller;
-    private final Object mInstallLock = new Object();
 
     private PackageDynamicCodeLoading mPackageDynamicCodeLoading;
     private DexLogger mDexLogger;
@@ -103,7 +102,7 @@
         };
 
         // For test purposes capture log messages as well as sending to the event log.
-        mDexLogger = new DexLogger(mPM, mInstaller, mInstallLock, mPackageDynamicCodeLoading) {
+        mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) {
             @Override
                 void writeDclEvent(int uid, String message) {
                     super.writeDclEvent(uid, message);
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 8f9d2ba..d1609c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -41,9 +41,11 @@
 import android.app.ActivityOptions;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.PauseActivityItem;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
 import android.util.MutableBoolean;
 
 import androidx.test.filters.MediumTest;
@@ -251,4 +253,72 @@
         assertEquals(prevSeq + 1, appWindowTokenRequestedOrientation.seq);
         verify(mActivity.mAppWindowToken).onMergedOverrideConfigurationChanged();
     }
+
+    @Test
+    public void testSetsRelaunchReason_NotDragResizing() {
+        mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
+
+        mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
+        mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+                mActivity.getConfiguration()));
+
+        mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION;
+        final Configuration newConfig = new Configuration(mTask.getConfiguration());
+        newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+                ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+        mTask.onRequestedOverrideConfigurationChanged(newConfig);
+
+        mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+
+        mActivity.ensureActivityConfiguration(0, false, false);
+
+        assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE,
+                mActivity.mRelaunchReason);
+    }
+
+    @Test
+    public void testSetsRelaunchReason_DragResizing() {
+        mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
+
+        mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
+        mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+                mActivity.getConfiguration()));
+
+        mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION;
+        final Configuration newConfig = new Configuration(mTask.getConfiguration());
+        newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+                ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
+        mTask.onRequestedOverrideConfigurationChanged(newConfig);
+
+        doReturn(true).when(mTask.getTask()).isDragResizing();
+
+        mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+
+        mActivity.ensureActivityConfiguration(0, false, false);
+
+        assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE,
+                mActivity.mRelaunchReason);
+    }
+
+    @Test
+    public void testSetsRelaunchReason_NonResizeConfigChanges() {
+        mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
+
+        mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
+        mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
+                mActivity.getConfiguration()));
+
+        mActivity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
+        final Configuration newConfig = new Configuration(mTask.getConfiguration());
+        newConfig.fontScale = 5;
+        mTask.onRequestedOverrideConfigurationChanged(newConfig);
+
+        mActivity.mRelaunchReason =
+                ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+
+        mActivity.ensureActivityConfiguration(0, false, false);
+
+        assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_NONE,
+                mActivity.mRelaunchReason);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index b2a2869..9d5f687 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -36,6 +36,8 @@
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
+import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
+import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -687,6 +689,62 @@
     }
 
     @Test
+    public void testHandleAppDied_RelaunchesAfterCrashDuringWindowingModeResize() {
+        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+        activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+        activity.launchCount = 1;
+        activity.haveState = false;
+
+        mStack.handleAppDiedLocked(activity.app);
+
+        assertEquals(1, mTask.mActivities.size());
+        assertEquals(1, mStack.getAllTasks().size());
+    }
+
+    @Test
+    public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringWindowingModeResize() {
+        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+        activity.mRelaunchReason = RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+        activity.launchCount = 3;
+        activity.haveState = false;
+
+        mStack.handleAppDiedLocked(activity.app);
+
+        assertThat(mTask.mActivities).isEmpty();
+        assertThat(mStack.getAllTasks()).isEmpty();
+    }
+
+    @Test
+    public void testHandleAppDied_RelaunchesAfterCrashDuringFreeResize() {
+        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+        activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
+        activity.launchCount = 1;
+        activity.haveState = false;
+
+        mStack.handleAppDiedLocked(activity.app);
+
+        assertEquals(1, mTask.mActivities.size());
+        assertEquals(1, mStack.getAllTasks().size());
+    }
+
+    @Test
+    public void testHandleAppDied_NotRelaunchAfterThreeCrashesDuringFreeResize() {
+        final ActivityRecord activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+        activity.mRelaunchReason = RELAUNCH_REASON_FREE_RESIZE;
+        activity.launchCount = 3;
+        activity.haveState = false;
+
+        mStack.handleAppDiedLocked(activity.app);
+
+        assertThat(mTask.mActivities).isEmpty();
+        assertThat(mStack.getAllTasks()).isEmpty();
+    }
+
+    @Test
     public void testFinishCurrentActivity() {
         // Create 2 activities on a new display.
         final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index fa42289..51bebbb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,7 +39,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
@@ -52,15 +54,34 @@
 @Presubmit
 public class AppTransitionTests extends WindowTestsBase {
 
+    private static RootWindowContainer sOriginalRootWindowContainer;
+
     private DisplayContent mDc;
 
+    @BeforeClass
+    public static void setUpRootWindowContainerMock() {
+        final WindowManagerService wm = WmServiceUtils.getWindowManagerService();
+        // For unit test, we don't need to test performSurfacePlacement to prevent some abnormal
+        // interaction with surfaceflinger native side.
+        sOriginalRootWindowContainer = wm.mRoot;
+        // Creating spied mock of RootWindowContainer shouldn't be done in @Before, since it will
+        // create unnecessary nested spied objects chain, because WindowManagerService object under
+        // test is a single instance shared among all tests that extend WindowTestsBase class.
+        // Instead it should be done once before running all tests in this test class.
+        wm.mRoot = spy(wm.mRoot);
+        doNothing().when(wm.mRoot).performSurfacePlacement(anyBoolean());
+    }
+
+    @AfterClass
+    public static void tearDownRootWindowContainerMock() {
+        final WindowManagerService wm = WmServiceUtils.getWindowManagerService();
+        wm.mRoot = sOriginalRootWindowContainer;
+        sOriginalRootWindowContainer = null;
+    }
+
     @Before
     public void setUp() throws Exception {
         mDc = mWm.getDefaultDisplayContentLocked();
-        // For unit test,  we don't need to test performSurfacePlacement to prevent some
-        // abnormal interaction with surfaceflinger native side.
-        mWm.mRoot = spy(mWm.mRoot);
-        doNothing().when(mWm.mRoot).performSurfacePlacement(anyBoolean());
     }
 
     @Test
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 3826fac..f399463 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -59,6 +59,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.view.DisplayCutout;
+import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -584,15 +585,17 @@
 
     @Test
     public void testOnDescendantOrientationRequestChanged() {
-        final DisplayContent dc = createNewDisplay();
+        final DisplayInfo info = new DisplayInfo();
+        info.logicalWidth = 1080;
+        info.logicalHeight = 1920;
+        info.logicalDensityDpi = 240;
+        final DisplayContent dc = createNewDisplay(info);
+        dc.configureDisplayPolicy();
         mWm.mAtmService.mRootActivityContainer = mock(RootActivityContainer.class);
-        final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
-                ? SCREEN_ORIENTATION_PORTRAIT
-                : SCREEN_ORIENTATION_LANDSCAPE;
 
         final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
         window.getTask().mTaskRecord = mock(TaskRecord.class, ExtendedMockito.RETURNS_DEEP_STUBS);
-        window.mAppToken.setOrientation(newOrientation);
+        window.mAppToken.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
 
         ActivityRecord activityRecord = mock(ActivityRecord.class);
 
@@ -603,21 +606,22 @@
         verify(mWm.mAtmService).updateDisplayOverrideConfigurationLocked(captor.capture(),
                 same(activityRecord), anyBoolean(), eq(dc.getDisplayId()));
         final Configuration newDisplayConfig = captor.getValue();
-        assertEquals(Configuration.ORIENTATION_PORTRAIT, newDisplayConfig.orientation);
+        assertEquals(Configuration.ORIENTATION_LANDSCAPE, newDisplayConfig.orientation);
     }
 
     @Test
     public void testOnDescendantOrientationRequestChanged_FrozenToUserRotation() {
-        final DisplayContent dc = createNewDisplay();
+        final DisplayInfo info = new DisplayInfo();
+        info.logicalWidth = 1080;
+        info.logicalHeight = 1920;
+        info.logicalDensityDpi = 240;
+        final DisplayContent dc = createNewDisplay(info);
         dc.getDisplayRotation().setFixedToUserRotation(true);
         mWm.mAtmService.mRootActivityContainer = mock(RootActivityContainer.class);
-        final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
-                ? SCREEN_ORIENTATION_PORTRAIT
-                : SCREEN_ORIENTATION_LANDSCAPE;
 
         final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
         window.getTask().mTaskRecord = mock(TaskRecord.class, ExtendedMockito.RETURNS_DEEP_STUBS);
-        window.mAppToken.setOrientation(newOrientation);
+        window.mAppToken.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
 
         ActivityRecord activityRecord = mock(ActivityRecord.class);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 198e7ce..d05711e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -33,7 +33,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.ContentResolver;
@@ -612,23 +611,6 @@
     // ========================
     // Non-rotation API Tests
     // ========================
-    @Test
-    public void testRespectsAppRequestedOrientationByDefault() throws Exception {
-        mBuilder.build();
-
-        assertTrue("Display rotation should respect app requested orientation by"
-                + " default.", mTarget.respectAppRequestedOrientation());
-    }
-
-    @Test
-    public void testNotRespectAppRequestedOrientation_FixedToUserRotation() throws Exception {
-        mBuilder.build();
-        mTarget.setFixedToUserRotation(true);
-
-        assertFalse("Display rotation shouldn't respect app requested orientation if"
-                + " fixed to user rotation.", mTarget.respectAppRequestedOrientation());
-    }
-
     /**
      * Call {@link DisplayRotation#configure(int, int, int, int)} to configure {@link #mTarget}
      * according to given parameters.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 8a98cbe..1c33dfb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -25,6 +25,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.hamcrest.Matchers.not;
@@ -46,6 +53,7 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.Xml;
 import android.view.DisplayInfo;
+import android.view.Surface;
 
 import androidx.test.filters.MediumTest;
 
@@ -217,12 +225,17 @@
         info.logicalHeight = fullScreenBounds.height();
         ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
         assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null);
+        // Override display orientation. Normally this is available via DisplayContent, but DC
+        // is mocked-out.
+        display.getRequestedOverrideConfiguration().orientation =
+                Configuration.ORIENTATION_LANDSCAPE;
+        display.onRequestedOverrideConfigurationChanged(
+                display.getRequestedOverrideConfiguration());
         ActivityStack stack = new StackBuilder(mRootActivityContainer)
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
         TaskRecord task = stack.getChildAt(0);
-        ActivityRecord root = task.getRootActivity();
-        ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
-        assertEquals(root, task.getRootActivity());
+        ActivityRecord root = task.getTopActivity();
+        assertEquals(root, task.getTopActivity());
 
         assertEquals(fullScreenBounds, task.getBounds());
 
@@ -233,16 +246,22 @@
         assertTrue(task.getBounds().width() < task.getBounds().height());
         assertEquals(fullScreenBounds.height(), task.getBounds().height());
 
-        // Setting non-root app has no effect
-        setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
-        assertTrue(task.getBounds().width() < task.getBounds().height());
+        // Top activity gets used
+        ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build();
+        assertEquals(top, task.getTopActivity());
+        setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE);
+        assertTrue(task.getBounds().width() > task.getBounds().height());
+        assertEquals(task.getBounds().width(), fullScreenBounds.width());
 
         // Setting app to unspecified restores
-        setActivityRequestedOrientation(root, SCREEN_ORIENTATION_UNSPECIFIED);
+        setActivityRequestedOrientation(top, SCREEN_ORIENTATION_UNSPECIFIED);
         assertEquals(fullScreenBounds, task.getBounds());
 
         // Setting app to fixed landscape and changing display
-        setActivityRequestedOrientation(root, SCREEN_ORIENTATION_LANDSCAPE);
+        setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE);
+        // simulate display orientation changing (normally done via DisplayContent)
+        display.getRequestedOverrideConfiguration().orientation =
+                Configuration.ORIENTATION_PORTRAIT;
         display.setBounds(fullScreenBoundsPort);
         assertTrue(task.getBounds().width() > task.getBounds().height());
         assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
@@ -265,6 +284,53 @@
         assertEquals(freeformBounds, task.getBounds());
     }
 
+    @Test
+    public void testUpdatesForcedOrientationInBackground() {
+        final DisplayInfo info = new DisplayInfo();
+        info.logicalWidth = 1920;
+        info.logicalHeight = 1080;
+        final ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP);
+        doCallRealMethod().when(display.mDisplayContent).setDisplayRotation(any());
+        display.mDisplayContent.setDisplayRotation(mock(DisplayRotation.class));
+        doCallRealMethod().when(display.mDisplayContent).onDescendantOrientationChanged(any(),
+                any());
+        doCallRealMethod().when(display.mDisplayContent).setRotation(anyInt());
+        doAnswer(invocation -> {
+            display.mDisplayContent.setRotation(Surface.ROTATION_0);
+            return null;
+        }).when(display.mDisplayContent).updateOrientationFromAppTokens(any(), any(), anyBoolean());
+
+        final ActivityStack stack = new StackBuilder(mRootActivityContainer)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+        final TaskRecord task = stack.getChildAt(0);
+        final ActivityRecord activity = task.getRootActivity();
+
+        // Wire up app window token and task.
+        doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt(), any(), any());
+        doCallRealMethod().when(activity.mAppWindowToken).onDescendantOrientationChanged(any(),
+                any());
+        doReturn(task.mTask).when(activity.mAppWindowToken).getParent();
+
+        // Wire up task and stack.
+        task.mTask.mTaskRecord = task;
+        doCallRealMethod().when(task.mTask).onDescendantOrientationChanged(any(), any());
+        doReturn(stack.getWindowContainerController().mContainer).when(task.mTask).getParent();
+
+        // Wire up stack and display content.
+        doCallRealMethod().when(stack.mWindowContainerController.mContainer)
+                .onDescendantOrientationChanged(any(), any());
+        doReturn(display.mDisplayContent).when(stack.mWindowContainerController.mContainer)
+                .getParent();
+
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        assertTrue("Bounds of the task should be pillarboxed.",
+                task.getBounds().width() < task.getBounds().height());
+
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+        assertTrue("Bounds of the task should be fullscreen.",
+                task.getBounds().equals(new Rect(0, 0, 1920, 1080)));
+    }
+
     /** Ensures that the alias intent won't have target component resolved. */
     @Test
     public void testTaskIntentActivityAlias() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index bbf3d45..613c4ff 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -371,14 +371,15 @@
         }
 
         private boolean shouldEnableService(Context context) {
-            // VoiceInteractionService should not be enabled on any low RAM devices
-            // or devices that have not declared the recognition feature, unless the
-            // device's configuration has explicitly set the config flag for a fixed
+            // VoiceInteractionService should not be enabled on devices that have not declared the
+            // recognition feature (including low-ram devices where notLowRam="true" takes effect),
+            // unless the device's configuration has explicitly set the config flag for a fixed
             // voice interaction service.
-            return (!ActivityManager.isLowRamDeviceStatic()
-                            && context.getPackageManager().hasSystemFeature(
-                                    PackageManager.FEATURE_VOICE_RECOGNIZERS)) ||
-                    getForceVoiceInteractionServicePackage(context.getResources()) != null;
+            if (getForceVoiceInteractionServicePackage(context.getResources()) != null) {
+                return true;
+            }
+            return context.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS);
         }
 
         private String getForceVoiceInteractionServicePackage(Resources res) {
diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java
index fe07370..4da79b3 100644
--- a/telephony/java/android/telephony/AvailableNetworkInfo.java
+++ b/telephony/java/android/telephony/AvailableNetworkInfo.java
@@ -110,6 +110,7 @@
     private AvailableNetworkInfo(Parcel in) {
         mSubId = in.readInt();
         mPriority = in.readInt();
+        mMccMncs = new ArrayList<>();
         in.readStringList(mMccMncs);
     }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index babeb7b..3311218 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9963,7 +9963,7 @@
         boolean ret = false;
         try {
             IOns iOpportunisticNetworkService = getIOns();
-            if (iOpportunisticNetworkService != null) {
+            if (iOpportunisticNetworkService != null && availableNetworks != null) {
                 ret = iOpportunisticNetworkService.updateAvailableNetworks(availableNetworks,
                         pkgForDebug);
             }
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 96f7a1b..3408291 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -92,6 +92,7 @@
     public static final int EVENT_DATA_RECONNECT = BASE + 47;
     public static final int EVENT_ROAMING_SETTING_CHANGE = BASE + 48;
     public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
+    public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 50;
 
     /***** Constants *****/
 
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 2a648bd..8523554 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -501,4 +501,18 @@
      */
     public static final String ACTION_LINE1_NUMBER_ERROR_DETECTED =
             "com.android.internal.telephony.ACTION_LINE1_NUMBER_ERROR_DETECTED";
+
+    /**
+     * Broadcast action to notify radio bug.
+     *
+     * Requires the READ_PRIVILEGED_PHONE_STATE permission.
+     *
+     * @hide
+     */
+    public static final String ACTION_REPORT_RADIO_BUG =
+            "com.android.internal.telephony.ACTION_REPORT_RADIO_BUG";
+
+    // ACTION_REPORT_RADIO_BUG extra keys
+    public static final String EXTRA_SLOT_ID = "slotId";
+    public static final String EXTRA_RADIO_BUG_TYPE = "radioBugType";
 }
diff --git a/test-base/api/current.txt b/test-base/api/current.txt
index 7ebd6aa..91fcca5 100644
--- a/test-base/api/current.txt
+++ b/test-base/api/current.txt
@@ -48,6 +48,9 @@
     method public abstract void startTiming(boolean);
   }
 
+  public abstract deprecated class RepetitiveTest implements java.lang.annotation.Annotation {
+  }
+
   public abstract deprecated class UiThreadTest implements java.lang.annotation.Annotation {
   }
 
diff --git a/test-base/src/android/test/RepetitiveTest.java b/test-base/src/android/test/RepetitiveTest.java
index 6a7130e..13e89d2 100644
--- a/test-base/src/android/test/RepetitiveTest.java
+++ b/test-base/src/android/test/RepetitiveTest.java
@@ -26,8 +26,10 @@
  * When the annotation is present, the test method is executed the number of times specified by
  * numIterations and defaults to 1.
  *
- * {@hide} Not needed for public API.
+ * @deprecated New tests should be written using the
+ * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
  */
+@Deprecated
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface RepetitiveTest {
@@ -37,4 +39,4 @@
      * @return The total number of iterations, the default is 1.
      */
     int numIterations() default 1;
-}
\ No newline at end of file
+}
diff --git a/test-legacy/Android.bp b/test-legacy/Android.bp
index 833c714..a69f422 100644
--- a/test-legacy/Android.bp
+++ b/test-legacy/Android.bp
@@ -25,7 +25,7 @@
     static_libs: [
         "android.test.base-minus-junit",
         "android.test.runner-minus-junit",
-        "android.test.mock.impl",
+        "android.test.mock_static",
     ],
 
     no_framework_libs: true,
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index e1d6e01..43b765d 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -30,3 +30,19 @@
     srcs_lib_whitelist_pkgs: ["android"],
     compile_dex: true,
 }
+
+// Build the android.test.mock_static library
+// ==========================================
+// This is only intended for inclusion in the legacy-android-test.
+// Must not be used elewhere.
+java_library_static {
+    name: "android.test.mock_static",
+
+    java_version: "1.8",
+    srcs: ["src/**/*.java"],
+
+    no_framework_libs: true,
+    libs: [
+        "framework",
+    ],
+}
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index 932fee0..299fbef 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -849,6 +849,18 @@
         assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
     }
 
+    private void assertParcelingIsLossless(LinkProperties source) {
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        final byte[] marshalled = p.marshall();
+        p = Parcel.obtain();
+        p.unmarshall(marshalled, 0, marshalled.length);
+        p.setDataPosition(0);
+        LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
+        assertEquals(source, dest);
+    }
+
     @Test
     public void testLinkPropertiesParcelable() throws Exception {
         LinkProperties source = new LinkProperties();
@@ -870,15 +882,12 @@
 
         source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
 
-        Parcel p = Parcel.obtain();
-        source.writeToParcel(p, /* flags */ 0);
-        p.setDataPosition(0);
-        final byte[] marshalled = p.marshall();
-        p = Parcel.obtain();
-        p.unmarshall(marshalled, 0, marshalled.length);
-        p.setDataPosition(0);
-        LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
+        assertParcelingIsLossless(source);
+    }
 
-        assertEquals(source, dest);
+    @Test
+    public void testParcelUninitialized() throws Exception {
+        LinkProperties empty = new LinkProperties();
+        assertParcelingIsLossless(empty);
     }
 }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bf39644..2a92a7d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4683,7 +4683,7 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         mCellNetworkAgent.connect(true);
         networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        verify(mNetworkManagementService, times(1)).startClatd(MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME);
         Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent);
 
         // Clat iface up, expect stack link updated.
@@ -4710,7 +4710,7 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         waitForIdle();
         networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
-        verify(mNetworkManagementService, times(1)).stopClatd(MOBILE_IFNAME);
+        verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
 
         // Clat iface removed, expect linkproperties revert to original one
         clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME);
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 4c52d81..9578ded 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -32,11 +32,13 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.INetd;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
 import android.net.NetworkStack;
+import android.os.INetworkManagementService;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.format.DateUtils;
@@ -66,6 +68,8 @@
     LingerMonitor mMonitor;
 
     @Mock ConnectivityService mConnService;
+    @Mock INetd mNetd;
+    @Mock INetworkManagementService mNMS;
     @Mock Context mCtx;
     @Mock NetworkMisc mMisc;
     @Mock NetworkNotificationManager mNotifier;
@@ -352,7 +356,7 @@
         caps.addCapability(0);
         caps.addTransportType(transport);
         NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
-                caps, 50, mCtx, null, mMisc, mConnService);
+                caps, 50, mCtx, null, mMisc, mConnService, mNetd, mNMS);
         nai.everValidated = true;
         return nai;
     }
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index bf42412..07b1d05 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -17,9 +17,7 @@
 package com.android.server.connectivity;
 
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -27,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.net.ConnectivityManager;
+import android.net.INetd;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -57,6 +56,7 @@
 
     @Mock ConnectivityService mConnectivity;
     @Mock NetworkMisc mMisc;
+    @Mock INetd mNetd;
     @Mock INetworkManagementService mNms;
     @Mock InterfaceConfiguration mConfig;
     @Mock NetworkAgentInfo mNai;
@@ -65,7 +65,7 @@
     Handler mHandler;
 
     Nat464Xlat makeNat464Xlat() {
-        return new Nat464Xlat(mNms, mNai);
+        return new Nat464Xlat(mNai, mNetd, mNms);
     }
 
     @Before
@@ -129,7 +129,7 @@
         nat.start();
 
         verify(mNms).registerObserver(eq(nat));
-        verify(mNms).startClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStart(eq(BASE_IFACE));
 
         // Stacked interface up notification arrives.
         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -144,7 +144,7 @@
         // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
         nat.stop();
 
-        verify(mNms).stopClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStop(eq(BASE_IFACE));
 
         // Stacked interface removed notification arrives.
         nat.interfaceRemoved(STACKED_IFACE);
@@ -156,7 +156,7 @@
         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
         assertIdle(nat);
 
-        verifyNoMoreInteractions(mNms, mConnectivity);
+        verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
     @Test
@@ -168,7 +168,7 @@
         nat.start();
 
         verify(mNms).registerObserver(eq(nat));
-        verify(mNms).startClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStart(eq(BASE_IFACE));
 
         // Stacked interface up notification arrives.
         nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -185,7 +185,7 @@
         mLooper.dispatchNext();
 
         verify(mNms).unregisterObserver(eq(nat));
-        verify(mNms).stopClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStop(eq(BASE_IFACE));
         verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
         assertTrue(c.getValue().getStackedLinks().isEmpty());
         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
@@ -194,7 +194,7 @@
         // ConnectivityService stops clat: no-op.
         nat.stop();
 
-        verifyNoMoreInteractions(mNms, mConnectivity);
+        verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
     @Test
@@ -205,13 +205,13 @@
         nat.start();
 
         verify(mNms).registerObserver(eq(nat));
-        verify(mNms).startClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStart(eq(BASE_IFACE));
 
         // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
         nat.stop();
 
         verify(mNms).unregisterObserver(eq(nat));
-        verify(mNms).stopClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStop(eq(BASE_IFACE));
         assertIdle(nat);
 
         // In-flight interface up notification arrives: no-op
@@ -225,7 +225,7 @@
 
         assertIdle(nat);
 
-        verifyNoMoreInteractions(mNms, mConnectivity);
+        verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
     @Test
@@ -236,16 +236,16 @@
         nat.start();
 
         verify(mNms).registerObserver(eq(nat));
-        verify(mNms).startClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStart(eq(BASE_IFACE));
 
         // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
         nat.stop();
 
         verify(mNms).unregisterObserver(eq(nat));
-        verify(mNms).stopClatd(eq(BASE_IFACE));
+        verify(mNetd).clatdStop(eq(BASE_IFACE));
         assertIdle(nat);
 
-        verifyNoMoreInteractions(mNms, mConnectivity);
+        verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
     }
 
     static void assertIdle(Nat464Xlat nat) {
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index 859a54d..e63c3b0 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.net.ipmemorystore;
 
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
 import android.content.Context;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -26,6 +29,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+
 /** Unit tests for {@link IpMemoryStoreServiceTest}. */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -36,6 +41,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString());
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java b/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java
new file mode 100644
index 0000000..8d367e2
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/RelevanceUtilsTests.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import static com.android.server.net.ipmemorystore.RelevanceUtils.CAPPED_RELEVANCE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RelevanceUtils}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RelevanceUtilsTests {
+    @Test
+    public void testComputeRelevanceForTargetDate() {
+        final long dayInMillis = 24L * 60 * 60 * 1000;
+        final long base = 1_000_000L; // any given point in time
+        // Relevance when the network expires in 1000 years must be capped
+        assertEquals(CAPPED_RELEVANCE, RelevanceUtils.computeRelevanceForTargetDate(
+                base + 1000L * dayInMillis, base));
+        // Relevance when expiry is before the date must be 0
+        assertEquals(0, RelevanceUtils.computeRelevanceForTargetDate(base - 1, base));
+        // Make sure the relevance for a given target date is higher if the expiry is further
+        // in the future
+        assertTrue(RelevanceUtils.computeRelevanceForTargetDate(base + 100 * dayInMillis, base)
+                < RelevanceUtils.computeRelevanceForTargetDate(base + 150 * dayInMillis, base));
+
+        // Make sure the relevance falls slower as the expiry is closing in. This is to ensure
+        // the decay is indeed logarithmic.
+        final int relevanceAtExpiry = RelevanceUtils.computeRelevanceForTargetDate(base, base);
+        final int relevance50DaysBeforeExpiry =
+                RelevanceUtils.computeRelevanceForTargetDate(base + 50 * dayInMillis, base);
+        final int relevance100DaysBeforeExpiry =
+                RelevanceUtils.computeRelevanceForTargetDate(base + 100 * dayInMillis, base);
+        final int relevance150DaysBeforeExpiry =
+                RelevanceUtils.computeRelevanceForTargetDate(base + 150 * dayInMillis, base);
+        assertEquals(0, relevanceAtExpiry);
+        assertTrue(relevance50DaysBeforeExpiry - relevanceAtExpiry
+                < relevance100DaysBeforeExpiry - relevance50DaysBeforeExpiry);
+        assertTrue(relevance100DaysBeforeExpiry - relevance50DaysBeforeExpiry
+                < relevance150DaysBeforeExpiry - relevance100DaysBeforeExpiry);
+    }
+
+    @Test
+    public void testIncreaseRelevance() {
+        long expiry = System.currentTimeMillis();
+
+        final long firstBump = RelevanceUtils.bumpExpiryDate(expiry);
+        // Though a few milliseconds might have elapsed, the first bump should push the duration
+        // to days in the future, so unless this test takes literal days between these two lines,
+        // this should always pass.
+        assertTrue(firstBump > expiry);
+
+        expiry = 0;
+        long lastDifference = Long.MAX_VALUE;
+        // The relevance should be capped in at most this many steps. Otherwise, fail.
+        final int steps = 1000;
+        for (int i = 0; i < steps; ++i) {
+            final long newExpiry = RelevanceUtils.bumpExpiryDuration(expiry);
+            if (newExpiry == expiry) {
+                // The relevance should be capped. Make sure it is, then exit without failure.
+                assertEquals(newExpiry, RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS);
+                return;
+            }
+            // Make sure the new expiry is further in the future than last time.
+            assertTrue(newExpiry > expiry);
+            // Also check that it was not bumped as much as the last bump, because the
+            // decay must be exponential.
+            assertTrue(newExpiry - expiry < lastDifference);
+            lastDifference = newExpiry - expiry;
+            expiry = newExpiry;
+        }
+        fail("Relevance failed to go to the maximum value after " + steps + " bumps");
+    }
+
+    @Test
+    public void testContinuity() {
+        final long expiry = System.currentTimeMillis();
+
+        // Relevance at expiry and after expiry should be the cap.
+        final int relevanceBeforeMaxLifetime = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+                expiry - (RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS + 1_000_000));
+        assertEquals(relevanceBeforeMaxLifetime, CAPPED_RELEVANCE);
+        final int relevanceForMaxLifetime = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+                expiry - RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS);
+        assertEquals(relevanceForMaxLifetime, CAPPED_RELEVANCE);
+
+        // If the max relevance is reached at the cap lifetime, one millisecond less than this
+        // should be very close. Strictly speaking this is a bit brittle, but it should be
+        // good enough for the purposes of the memory store.
+        final int relevanceForOneMillisecLessThanCap = RelevanceUtils.computeRelevanceForTargetDate(
+                expiry, expiry - RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS + 1);
+        assertTrue(relevanceForOneMillisecLessThanCap <= CAPPED_RELEVANCE);
+        assertTrue(relevanceForOneMillisecLessThanCap >= CAPPED_RELEVANCE - 10);
+
+        // Likewise the relevance one millisecond before expiry should be very close to 0. It's
+        // fine if it rounds down to 0.
+        final int relevanceOneMillisecBeforeExpiry = RelevanceUtils.computeRelevanceForTargetDate(
+                expiry, expiry - 1);
+        assertTrue(relevanceOneMillisecBeforeExpiry <= 10);
+        assertTrue(relevanceOneMillisecBeforeExpiry >= 0);
+
+        final int relevanceAtExpiry = RelevanceUtils.computeRelevanceForTargetDate(expiry, expiry);
+        assertEquals(relevanceAtExpiry, 0);
+        final int relevanceAfterExpiry = RelevanceUtils.computeRelevanceForTargetDate(expiry,
+                expiry + 1_000_000);
+        assertEquals(relevanceAfterExpiry, 0);
+    }
+
+    // testIncreaseRelevance makes sure bumping the expiry continuously always yields a
+    // monotonically increasing date as a side effect, but this tests that the relevance (as
+    // opposed to the expiry date) increases monotonically with increasing periods.
+    @Test
+    public void testMonotonicity() {
+        // Hopefully the relevance is granular enough to give a different value for every one
+        // of this number of steps.
+        final int steps = 40;
+        final long expiry = System.currentTimeMillis();
+
+        int lastRelevance = -1;
+        for (int i = 0; i < steps; ++i) {
+            final long date = expiry - i * (RelevanceUtils.CAPPED_RELEVANCE_LIFETIME_MS / steps);
+            final int relevance = RelevanceUtils.computeRelevanceForTargetDate(expiry, date);
+            assertTrue(relevance > lastRelevance);
+            lastRelevance = relevance;
+        }
+    }
+}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 85bf6f2..5812ec4 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -213,6 +213,27 @@
   return true;
 }
 
+static bool AddDeprecatedUsesFeatures(xml::Element* el, SourcePathDiagnostics* diag) {
+  if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) {
+    if (attr->value.empty()) {
+      return true;
+    }
+
+    // Add "android.hardware.fingerprint" when "android.hardware.biometric.fingerprint" is found,
+    // since the former is deprecated in Q and the latter is not present pre-Q. (see b/115639644)
+    if (attr->value == "android.hardware.biometrics.fingerprint") {
+      auto element = el->CloneElement([&](const xml::Element& el, xml::Element* out_el) {
+        xml::Attribute* cloned_attr = out_el->FindOrCreateAttribute(xml::kSchemaAndroid, "name");
+        cloned_attr->value = "android.hardware.fingerprint";
+      });
+
+      el->parent->AppendChild(std::move(element));
+    }
+  }
+
+  return true;
+}
+
 bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
                                IDiagnostics* diag) {
   // First verify some options.
@@ -247,6 +268,7 @@
   // Common <uses-feature> actions.
   xml::XmlNodeAction uses_feature_action;
   uses_feature_action.Action(VerifyUsesFeature);
+  uses_feature_action.Action(AddDeprecatedUsesFeatures);
 
   // Common component actions.
   xml::XmlNodeAction component_action;
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index adea627..fcc9f55 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -832,4 +832,36 @@
   EXPECT_THAT(Verify(input), NotNull());
 }
 
+TEST_F(ManifestFixerTest, UsesFeatureAddDeprecated) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <uses-feature android:name="android.hardware.biometrics.fingerprint" />
+        <feature-group>
+          <uses-feature android:name="android.hardware.biometrics.fingerprint" />
+        </feature-group>
+      </manifest>)";
+
+  std::unique_ptr<xml::XmlResource> manifest = Verify(input);
+  ASSERT_THAT(manifest, NotNull());
+  EXPECT_THAT(manifest->root->FindChildWithAttribute("", "uses-feature",
+                                                     xml::kSchemaAndroid, "name",
+                                                     "android.hardware.biometrics.fingerprint"),
+              Ne(nullptr));
+  EXPECT_THAT(manifest->root->FindChildWithAttribute("", "uses-feature",
+                                                     xml::kSchemaAndroid, "name",
+                                                     "android.hardware.fingerprint"),
+              Ne(nullptr));
+
+  xml::Element* feature_group = manifest->root->FindChild("", "feature-group");
+  ASSERT_THAT(feature_group, Ne(nullptr));
+
+  EXPECT_THAT(feature_group->FindChildWithAttribute("", "uses-feature", xml::kSchemaAndroid, "name",
+                                                    "android.hardware.biometrics.fingerprint"),
+              Ne(nullptr));
+  EXPECT_THAT(feature_group->FindChildWithAttribute("", "uses-feature", xml::kSchemaAndroid, "name",
+                                                    "android.hardware.fingerprint"),
+              Ne(nullptr));
+}
+
 }  // namespace aapt
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 85871fe..e019f28 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -145,11 +145,23 @@
          */
         public static final int SUITE_B_192 = 10;
 
+        /**
+         * WPA pre-shared key with stronger SHA256-based algorithms.
+         * @hide
+         */
+        public static final int WPA_PSK_SHA256 = 11;
+
+        /**
+         * WPA using EAP authentication with stronger SHA256-based algorithms.
+         * @hide
+         */
+        public static final int WPA_EAP_SHA256 = 12;
+
         public static final String varName = "key_mgmt";
 
         public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP",
                 "IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP",
-                "SAE", "OWE", "SUITE_B_192"};
+                "SAE", "OWE", "SUITE_B_192", "WPA_PSK_SHA256", "WPA_EAP_SHA256" };
     }
 
     /**