Merge "Bouncer shouldn't be translated when occluded" into pi-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index f1c6120..f0017b9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -621,6 +621,19 @@
     method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException;
   }
 
+  public final class RemoteCallback implements android.os.Parcelable {
+    ctor public RemoteCallback(android.os.RemoteCallback.OnResultListener);
+    ctor public RemoteCallback(android.os.RemoteCallback.OnResultListener, android.os.Handler);
+    method public int describeContents();
+    method public void sendResult(android.os.Bundle);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.RemoteCallback> CREATOR;
+  }
+
+  public static abstract interface RemoteCallback.OnResultListener {
+    method public abstract void onResult(android.os.Bundle);
+  }
+
   public final class StrictMode {
     method public static void setViolationLogger(android.os.StrictMode.ViolationLogger);
     field public static final int DETECT_CUSTOM = 8; // 0x8
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 2fc5808..c6d2bc8 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -171,7 +171,6 @@
             showError("Error: unknown dispatch code '" + cmd + "'");
             return;
         }
-
         final long now = SystemClock.uptimeMillis();
         sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0, 0,
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
@@ -189,7 +188,6 @@
         @Override
         public void onSessionDestroyed() {
             System.out.println("onSessionDestroyed. Enter q to quit.");
-
         }
 
         @Override
@@ -246,7 +244,7 @@
                 @Override
                 protected void onLooperPrepared() {
                     try {
-                        mController.registerCallbackListener(ControllerMonitor.this);
+                        mController.registerCallbackListener(PACKAGE_NAME, ControllerMonitor.this);
                     } catch (RemoteException e) {
                         System.out.println("Error registering monitor callback");
                     }
@@ -266,13 +264,13 @@
                     } else if ("q".equals(line) || "quit".equals(line)) {
                         break;
                     } else if ("play".equals(line)) {
-                        mController.play(PACKAGE_NAME);
+                        dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY);
                     } else if ("pause".equals(line)) {
-                        mController.pause(PACKAGE_NAME);
+                        dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
                     } else if ("next".equals(line)) {
-                        mController.next(PACKAGE_NAME);
+                        dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT);
                     } else if ("previous".equals(line)) {
-                        mController.previous(PACKAGE_NAME);
+                        dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
                     } else {
                         System.out.println("Invalid command: " + line);
                     }
@@ -295,6 +293,20 @@
                 }
             }
         }
+
+        private void dispatchKeyCode(int keyCode) {
+            final long now = SystemClock.uptimeMillis();
+            KeyEvent down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
+                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
+            KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
+                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
+            try {
+                mController.sendMediaButton(PACKAGE_NAME, null, false, down);
+                mController.sendMediaButton(PACKAGE_NAME, null, false, up);
+            } catch (RemoteException e) {
+                System.out.println("Failed to dispatch " + keyCode);
+            }
+        }
     }
 
     private void runListSessions() {
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index c03340e..5662aea 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -28,6 +28,8 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManagerGlobal;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
@@ -47,6 +49,7 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 
+import com.android.internal.util.function.pooled.PooledLambda;
 import libcore.io.IoUtils;
 
 import java.io.IOException;
@@ -118,10 +121,14 @@
 
     private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
 
-    private final IAccessibilityServiceClient mClient;
+    private final Handler mLocalCallbackHandler;
 
     private final IUiAutomationConnection mUiAutomationConnection;
 
+    private HandlerThread mRemoteCallbackThread;
+
+    private IAccessibilityServiceClient mClient;
+
     private int mConnectionId = CONNECTION_ID_UNDEFINED;
 
     private OnAccessibilityEventListener mOnAccessibilityEventListener;
@@ -190,8 +197,8 @@
         if (connection == null) {
             throw new IllegalArgumentException("Connection cannot be null!");
         }
+        mLocalCallbackHandler = new Handler(looper);
         mUiAutomationConnection = connection;
-        mClient = new IAccessibilityServiceClientImpl(looper);
     }
 
     /**
@@ -217,6 +224,9 @@
                 return;
             }
             mIsConnecting = true;
+            mRemoteCallbackThread = new HandlerThread("UiAutomation");
+            mRemoteCallbackThread.start();
+            mClient = new IAccessibilityServiceClientImpl(mRemoteCallbackThread.getLooper());
         }
 
         try {
@@ -281,6 +291,9 @@
             mUiAutomationConnection.disconnect();
         } catch (RemoteException re) {
             throw new RuntimeException("Error while disconnecting UiAutomation", re);
+        } finally {
+            mRemoteCallbackThread.quit();
+            mRemoteCallbackThread = null;
         }
     }
 
@@ -311,6 +324,7 @@
 
     /**
      * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
+     * The callbacks are delivered on the main application thread.
      *
      * @param listener The callback.
      */
@@ -1139,17 +1153,21 @@
 
                 @Override
                 public void onAccessibilityEvent(AccessibilityEvent event) {
+                    final OnAccessibilityEventListener listener;
                     synchronized (mLock) {
                         mLastEventTimeMillis = event.getEventTime();
                         if (mWaitingForEventDelivery) {
                             mEventQueue.add(AccessibilityEvent.obtain(event));
                         }
                         mLock.notifyAll();
+                        listener = mOnAccessibilityEventListener;
                     }
-                    // Calling out only without a lock held.
-                    final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
                     if (listener != null) {
-                        listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
+                        // Calling out only without a lock held.
+                        mLocalCallbackHandler.post(PooledLambda.obtainRunnable(
+                                OnAccessibilityEventListener::onAccessibilityEvent,
+                                listener, AccessibilityEvent.obtain(event))
+                                .recycleOnUse());
                     }
                 }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 34ac9ae..c3548e5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5574,8 +5574,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {Manifest.permission.SUSPEND_APPS,
-            Manifest.permission.MANAGE_USERS})
+    @RequiresPermission(Manifest.permission.SUSPEND_APPS)
     public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
             @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
             String dialogMessage) {
diff --git a/core/java/android/os/RemoteCallback.java b/core/java/android/os/RemoteCallback.java
index 7dbcb95..5914739 100644
--- a/core/java/android/os/RemoteCallback.java
+++ b/core/java/android/os/RemoteCallback.java
@@ -19,11 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 
 /**
  * @hide
  */
 @SystemApi
+@TestApi
 public final class RemoteCallback implements Parcelable {
 
     public interface OnResultListener {
diff --git a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java
index 1811800c..26fb6b64 100644
--- a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java
+++ b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java
@@ -59,8 +59,11 @@
     }
 
     public boolean pulseOnPickupAvailable() {
-        return mContext.getResources().getBoolean(R.bool.config_dozePulsePickup)
-                && ambientDisplayAvailable();
+        return dozePulsePickupSensorAvailable() && ambientDisplayAvailable();
+    }
+
+    public boolean dozePulsePickupSensorAvailable() {
+        return mContext.getResources().getBoolean(R.bool.config_dozePulsePickup);
     }
 
     public boolean pulseOnPickupCanBeModified(int user) {
@@ -73,7 +76,11 @@
     }
 
     public boolean pulseOnDoubleTapAvailable() {
-        return !TextUtils.isEmpty(doubleTapSensorType()) && ambientDisplayAvailable();
+        return doubleTapSensorAvailable() && ambientDisplayAvailable();
+    }
+
+    public boolean doubleTapSensorAvailable() {
+        return !TextUtils.isEmpty(doubleTapSensorType());
     }
 
     public String doubleTapSensorType() {
@@ -115,7 +122,7 @@
         return boolSettingDefaultOff(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, user);
     }
 
-    private boolean ambientDisplayAvailable() {
+    public boolean ambientDisplayAvailable() {
         return !TextUtils.isEmpty(ambientDisplayComponent());
     }
 
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 9634c7f..626338d 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -17,6 +17,7 @@
 
 import android.content.Intent;
 import android.media.Rating;
+import android.media.session.ISessionControllerCallback;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -25,33 +26,46 @@
  * @hide
  */
 oneway interface ISessionCallback {
-    void onCommand(String packageName, int pid, int uid, String command, in Bundle args,
-            in ResultReceiver cb);
+    void onCommand(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            String command, in Bundle args, in ResultReceiver cb);
     void onMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent,
             int sequenceNumber, in ResultReceiver cb);
+    void onMediaButtonFromController(String packageName, int pid, int uid,
+            ISessionControllerCallback caller, in Intent mediaButtonIntent);
 
     // These callbacks are for the TransportPerformer
-    void onPrepare(String packageName, int pid, int uid);
-    void onPrepareFromMediaId(String packageName, int pid, int uid, String mediaId,
-            in Bundle extras);
-    void onPrepareFromSearch(String packageName, int pid, int uid, String query, in Bundle extras);
-    void onPrepareFromUri(String packageName, int pid, int uid, in Uri uri, in Bundle extras);
-    void onPlay(String packageName, int pid, int uid);
-    void onPlayFromMediaId(String packageName, int pid, int uid, String mediaId, in Bundle extras);
-    void onPlayFromSearch(String packageName, int pid, int uid, String query, in Bundle extras);
-    void onPlayFromUri(String packageName, int pid, int uid, in Uri uri, in Bundle extras);
-    void onSkipToTrack(String packageName, int pid, int uid, long id);
-    void onPause(String packageName, int pid, int uid);
-    void onStop(String packageName, int pid, int uid);
-    void onNext(String packageName, int pid, int uid);
-    void onPrevious(String packageName, int pid, int uid);
-    void onFastForward(String packageName, int pid, int uid);
-    void onRewind(String packageName, int pid, int uid);
-    void onSeekTo(String packageName, int pid, int uid, long pos);
-    void onRate(String packageName, int pid, int uid, in Rating rating);
-    void onCustomAction(String packageName, int pid, int uid, String action, in Bundle args);
+    void onPrepare(String packageName, int pid, int uid, ISessionControllerCallback caller);
+    void onPrepareFromMediaId(String packageName, int pid, int uid,
+            ISessionControllerCallback caller, String mediaId, in Bundle extras);
+    void onPrepareFromSearch(String packageName, int pid, int uid,
+            ISessionControllerCallback caller, String query, in Bundle extras);
+    void onPrepareFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            in Uri uri, in Bundle extras);
+    void onPlay(String packageName, int pid, int uid, ISessionControllerCallback caller);
+    void onPlayFromMediaId(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            String mediaId, in Bundle extras);
+    void onPlayFromSearch(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            String query, in Bundle extras);
+    void onPlayFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            in Uri uri, in Bundle extras);
+    void onSkipToTrack(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            long id);
+    void onPause(String packageName, int pid, int uid, ISessionControllerCallback caller);
+    void onStop(String packageName, int pid, int uid, ISessionControllerCallback caller);
+    void onNext(String packageName, int pid, int uid, ISessionControllerCallback caller);
+    void onPrevious(String packageName, int pid, int uid, ISessionControllerCallback caller);
+    void onFastForward(String packageName, int pid, int uid, ISessionControllerCallback caller);
+    void onRewind(String packageName, int pid, int uid, ISessionControllerCallback caller);
+    void onSeekTo(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            long pos);
+    void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            in Rating rating);
+    void onCustomAction(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            String action, in Bundle args);
 
     // These callbacks are for volume handling
-    void onAdjustVolume(String packageName, int pid, int uid, int direction);
-    void onSetVolumeTo(String packageName, int pid, int uid, int value);
+    void onAdjustVolume(String packageName, int pid, int uid, ISessionControllerCallback caller,
+            int direction);
+    void onSetVolumeTo(String packageName, int pid, int uid,
+            ISessionControllerCallback caller, int value);
 }
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index b4f52f9..861a8ce 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -32,42 +32,53 @@
 import java.util.List;
 
 /**
- * Interface to a MediaSession in the system.
+ * Interface to MediaSessionRecord in the system.
  * @hide
  */
 interface ISessionController {
-    void sendCommand(String packageName, String command, in Bundle args, in ResultReceiver cb);
-    boolean sendMediaButton(String packageName, boolean asSystemService, in KeyEvent mediaButton);
-    void registerCallbackListener(in ISessionControllerCallback cb);
-    void unregisterCallbackListener(in ISessionControllerCallback cb);
+    void sendCommand(String packageName, ISessionControllerCallback caller,
+            String command, in Bundle args, in ResultReceiver cb);
+    boolean sendMediaButton(String packageName, ISessionControllerCallback caller,
+            boolean asSystemService, in KeyEvent mediaButton);
+    void registerCallbackListener(String packageName, ISessionControllerCallback cb);
+    void unregisterCallbackListener(ISessionControllerCallback cb);
     boolean isTransportControlEnabled();
     String getPackageName();
     String getTag();
     PendingIntent getLaunchPendingIntent();
     long getFlags();
     ParcelableVolumeInfo getVolumeAttributes();
-    void adjustVolume(String packageName, boolean asSystemService, int direction, int flags);
-    void setVolumeTo(String packageName, int value, int flags);
+    void adjustVolume(String packageName, ISessionControllerCallback caller,
+            boolean asSystemService, int direction, int flags);
+    void setVolumeTo(String packageName, ISessionControllerCallback caller,
+            int value, int flags);
 
     // These commands are for the TransportControls
-    void prepare(String packageName);
-    void prepareFromMediaId(String packageName, String mediaId, in Bundle extras);
-    void prepareFromSearch(String packageName, String string, in Bundle extras);
-    void prepareFromUri(String packageName, in Uri uri, in Bundle extras);
-    void play(String packageName);
-    void playFromMediaId(String packageName, String mediaId, in Bundle extras);
-    void playFromSearch(String packageName, String string, in Bundle extras);
-    void playFromUri(String packageName, in Uri uri, in Bundle extras);
-    void skipToQueueItem(String packageName, long id);
-    void pause(String packageName);
-    void stop(String packageName);
-    void next(String packageName);
-    void previous(String packageName);
-    void fastForward(String packageName);
-    void rewind(String packageName);
-    void seekTo(String packageName, long pos);
-    void rate(String packageName, in Rating rating);
-    void sendCustomAction(String packageName, String action, in Bundle args);
+    void prepare(String packageName, ISessionControllerCallback caller);
+    void prepareFromMediaId(String packageName, ISessionControllerCallback caller,
+            String mediaId, in Bundle extras);
+    void prepareFromSearch(String packageName, ISessionControllerCallback caller,
+            String string, in Bundle extras);
+    void prepareFromUri(String packageName, ISessionControllerCallback caller,
+            in Uri uri, in Bundle extras);
+    void play(String packageName, ISessionControllerCallback caller);
+    void playFromMediaId(String packageName, ISessionControllerCallback caller,
+            String mediaId, in Bundle extras);
+    void playFromSearch(String packageName, ISessionControllerCallback caller,
+            String string, in Bundle extras);
+    void playFromUri(String packageName, ISessionControllerCallback caller,
+            in Uri uri, in Bundle extras);
+    void skipToQueueItem(String packageName, ISessionControllerCallback caller, long id);
+    void pause(String packageName, ISessionControllerCallback caller);
+    void stop(String packageName, ISessionControllerCallback caller);
+    void next(String packageName, ISessionControllerCallback caller);
+    void previous(String packageName, ISessionControllerCallback caller);
+    void fastForward(String packageName, ISessionControllerCallback caller);
+    void rewind(String packageName, ISessionControllerCallback caller);
+    void seekTo(String packageName, ISessionControllerCallback caller, long pos);
+    void rate(String packageName, ISessionControllerCallback caller, in Rating rating);
+    void sendCustomAction(String packageName, ISessionControllerCallback caller,
+            String action, in Bundle args);
     MediaMetadata getMetadata();
     PlaybackState getPlaybackState();
     ParceledListSlice getQueue();
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 8c34a31..de22fa3 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -126,7 +126,7 @@
      * @return true if the event was sent to the session, false otherwise.
      */
     public boolean dispatchMediaButtonEvent(@NonNull KeyEvent keyEvent) {
-        return dispatchMediButtonEventInternal(false, keyEvent);
+        return dispatchMediaButtonEventInternal(false, keyEvent);
     }
 
     /**
@@ -142,10 +142,10 @@
      * @hide
      */
     public boolean dispatchMediaButtonEventAsSystemService(@NonNull KeyEvent keyEvent) {
-        return dispatchMediButtonEventInternal(true, keyEvent);
+        return dispatchMediaButtonEventInternal(true, keyEvent);
     }
 
-    private boolean dispatchMediButtonEventInternal(boolean asSystemService,
+    private boolean dispatchMediaButtonEventInternal(boolean asSystemService,
             @NonNull KeyEvent keyEvent) {
         if (keyEvent == null) {
             throw new IllegalArgumentException("KeyEvent may not be null");
@@ -154,8 +154,8 @@
             return false;
         }
         try {
-            return mSessionBinder.sendMediaButton(mContext.getPackageName(), asSystemService,
-                    keyEvent);
+            return mSessionBinder.sendMediaButton(mContext.getPackageName(), mCbStub,
+                    asSystemService, keyEvent);
         } catch (RemoteException e) {
             // System is dead. =(
         }
@@ -189,7 +189,7 @@
                         break;
                 }
                 try {
-                    mSessionBinder.adjustVolume(mContext.getPackageName(), true, direction,
+                    mSessionBinder.adjustVolume(mContext.getPackageName(), mCbStub, true, direction,
                             AudioManager.FLAG_SHOW_UI);
                 } catch (RemoteException e) {
                     Log.wtf(TAG, "Error calling adjustVolumeBy", e);
@@ -200,7 +200,7 @@
                 final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
                         | AudioManager.FLAG_FROM_KEY;
                 try {
-                    mSessionBinder.adjustVolume(mContext.getPackageName(), true, 0, flags);
+                    mSessionBinder.adjustVolume(mContext.getPackageName(), mCbStub, true, 0, flags);
                 } catch (RemoteException e) {
                     Log.wtf(TAG, "Error calling adjustVolumeBy", e);
                 }
@@ -369,7 +369,7 @@
      */
     public void setVolumeTo(int value, int flags) {
         try {
-            mSessionBinder.setVolumeTo(mContext.getPackageName(), value, flags);
+            mSessionBinder.setVolumeTo(mContext.getPackageName(), mCbStub, value, flags);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error calling setVolumeTo.", e);
         }
@@ -390,7 +390,8 @@
      */
     public void adjustVolume(int direction, int flags) {
         try {
-            mSessionBinder.adjustVolume(mContext.getPackageName(), false, direction, flags);
+            mSessionBinder.adjustVolume(mContext.getPackageName(), mCbStub, false, direction,
+                    flags);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
         }
@@ -456,7 +457,7 @@
             throw new IllegalArgumentException("command cannot be null or empty");
         }
         try {
-            mSessionBinder.sendCommand(mContext.getPackageName(), command, args, cb);
+            mSessionBinder.sendCommand(mContext.getPackageName(), mCbStub, command, args, cb);
         } catch (RemoteException e) {
             Log.d(TAG, "Dead object in sendCommand.", e);
         }
@@ -521,7 +522,7 @@
 
         if (!mCbRegistered) {
             try {
-                mSessionBinder.registerCallbackListener(mCbStub);
+                mSessionBinder.registerCallbackListener(mContext.getPackageName(), mCbStub);
                 mCbRegistered = true;
             } catch (RemoteException e) {
                 Log.e(TAG, "Dead object in registerCallback", e);
@@ -668,7 +669,7 @@
          */
         public void prepare() {
             try {
-                mSessionBinder.prepare(mContext.getPackageName());
+                mSessionBinder.prepare(mContext.getPackageName(), mCbStub);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling prepare.", e);
             }
@@ -692,7 +693,8 @@
                         "You must specify a non-empty String for prepareFromMediaId.");
             }
             try {
-                mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mediaId, extras);
+                mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mCbStub, mediaId,
+                        extras);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling prepare(" + mediaId + ").", e);
             }
@@ -718,7 +720,7 @@
                 query = "";
             }
             try {
-                mSessionBinder.prepareFromSearch(mContext.getPackageName(), query, extras);
+                mSessionBinder.prepareFromSearch(mContext.getPackageName(), mCbStub, query, extras);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling prepare(" + query + ").", e);
             }
@@ -742,7 +744,7 @@
                         "You must specify a non-empty Uri for prepareFromUri.");
             }
             try {
-                mSessionBinder.prepareFromUri(mContext.getPackageName(), uri, extras);
+                mSessionBinder.prepareFromUri(mContext.getPackageName(), mCbStub, uri, extras);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling prepare(" + uri + ").", e);
             }
@@ -753,7 +755,7 @@
          */
         public void play() {
             try {
-                mSessionBinder.play(mContext.getPackageName());
+                mSessionBinder.play(mContext.getPackageName(), mCbStub);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling play.", e);
             }
@@ -772,7 +774,7 @@
                         "You must specify a non-empty String for playFromMediaId.");
             }
             try {
-                mSessionBinder.playFromMediaId(mContext.getPackageName(), mediaId, extras);
+                mSessionBinder.playFromMediaId(mContext.getPackageName(), mCbStub, mediaId, extras);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling play(" + mediaId + ").", e);
             }
@@ -794,7 +796,7 @@
                 query = "";
             }
             try {
-                mSessionBinder.playFromSearch(mContext.getPackageName(), query, extras);
+                mSessionBinder.playFromSearch(mContext.getPackageName(), mCbStub, query, extras);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling play(" + query + ").", e);
             }
@@ -813,7 +815,7 @@
                         "You must specify a non-empty Uri for playFromUri.");
             }
             try {
-                mSessionBinder.playFromUri(mContext.getPackageName(), uri, extras);
+                mSessionBinder.playFromUri(mContext.getPackageName(), mCbStub, uri, extras);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling play(" + uri + ").", e);
             }
@@ -825,7 +827,7 @@
          */
         public void skipToQueueItem(long id) {
             try {
-                mSessionBinder.skipToQueueItem(mContext.getPackageName(), id);
+                mSessionBinder.skipToQueueItem(mContext.getPackageName(), mCbStub, id);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e);
             }
@@ -837,7 +839,7 @@
          */
         public void pause() {
             try {
-                mSessionBinder.pause(mContext.getPackageName());
+                mSessionBinder.pause(mContext.getPackageName(), mCbStub);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling pause.", e);
             }
@@ -849,7 +851,7 @@
          */
         public void stop() {
             try {
-                mSessionBinder.stop(mContext.getPackageName());
+                mSessionBinder.stop(mContext.getPackageName(), mCbStub);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling stop.", e);
             }
@@ -862,7 +864,7 @@
          */
         public void seekTo(long pos) {
             try {
-                mSessionBinder.seekTo(mContext.getPackageName(), pos);
+                mSessionBinder.seekTo(mContext.getPackageName(), mCbStub, pos);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling seekTo.", e);
             }
@@ -874,7 +876,7 @@
          */
         public void fastForward() {
             try {
-                mSessionBinder.fastForward(mContext.getPackageName());
+                mSessionBinder.fastForward(mContext.getPackageName(), mCbStub);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling fastForward.", e);
             }
@@ -885,7 +887,7 @@
          */
         public void skipToNext() {
             try {
-                mSessionBinder.next(mContext.getPackageName());
+                mSessionBinder.next(mContext.getPackageName(), mCbStub);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling next.", e);
             }
@@ -897,7 +899,7 @@
          */
         public void rewind() {
             try {
-                mSessionBinder.rewind(mContext.getPackageName());
+                mSessionBinder.rewind(mContext.getPackageName(), mCbStub);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling rewind.", e);
             }
@@ -908,7 +910,7 @@
          */
         public void skipToPrevious() {
             try {
-                mSessionBinder.previous(mContext.getPackageName());
+                mSessionBinder.previous(mContext.getPackageName(), mCbStub);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling previous.", e);
             }
@@ -923,7 +925,7 @@
          */
         public void setRating(Rating rating) {
             try {
-                mSessionBinder.rate(mContext.getPackageName(), rating);
+                mSessionBinder.rate(mContext.getPackageName(), mCbStub, rating);
             } catch (RemoteException e) {
                 Log.wtf(TAG, "Error calling rate.", e);
             }
@@ -937,7 +939,7 @@
          *             custom action.
          */
         public void sendCustomAction(@NonNull PlaybackState.CustomAction customAction,
-                    @Nullable Bundle args) {
+                @Nullable Bundle args) {
             if (customAction == null) {
                 throw new IllegalArgumentException("CustomAction cannot be null.");
             }
@@ -958,7 +960,7 @@
                 throw new IllegalArgumentException("CustomAction cannot be null.");
             }
             try {
-                mSessionBinder.sendCustomAction(mContext.getPackageName(), action, args);
+                mSessionBinder.sendCustomAction(mContext.getPackageName(), mCbStub, action, args);
             } catch (RemoteException e) {
                 Log.d(TAG, "Dead object in sendCustomAction.", e);
             }
@@ -1124,8 +1126,8 @@
         public void onVolumeInfoChanged(ParcelableVolumeInfo pvi) {
             MediaController controller = mController.get();
             if (controller != null) {
-                PlaybackInfo info = new PlaybackInfo(pvi.volumeType, pvi.audioAttrs, pvi.controlType,
-                        pvi.maxVolume, pvi.currentVolume);
+                PlaybackInfo info = new PlaybackInfo(pvi.volumeType, pvi.audioAttrs,
+                        pvi.controlType, pvi.maxVolume, pvi.currentVolume);
                 controller.postMessage(MSG_UPDATE_VOLUME, info, null);
             }
         }
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 6f4f20e..fad7e3f 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -43,6 +43,7 @@
 import android.service.media.MediaBrowserService;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.view.KeyEvent;
 import android.view.ViewConfiguration;
 
@@ -122,15 +123,6 @@
             FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
     public @interface SessionFlags { }
 
-    private static final String EXTRA_KEY_CALLING_PACKAGE =
-            "android.media.session.extra.CALLING_PACKAGE";
-    private static final String EXTRA_KEY_CALLING_PID =
-            "android.media.session.extra.CALLING_PID";
-    private static final String EXTRA_KEY_CALLING_UID =
-            "android.media.session.extra.CALLING_UID";
-    private static final String EXTRA_KEY_ORIGINAL_BUNDLE =
-            "android.media.session.extra.ORIGINAL_BUNDLE";
-
     private final Object mLock = new Object();
     private final int mMaxBitmapSize;
 
@@ -529,15 +521,11 @@
      * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
      */
     public final @NonNull RemoteUserInfo getCurrentControllerInfo() {
-        return createRemoteUserInfo(getCurrentData());
-    }
-
-    private @NonNull Bundle getCurrentData() {
-        if (mCallback == null || mCallback.mCurrentData == null) {
+        if (mCallback == null || mCallback.mCurrentControllerInfo == null) {
             throw new IllegalStateException(
                     "This should be called inside of MediaSession.Callback methods");
         }
-        return mCallback.mCurrentData;
+        return mCallback.mCurrentControllerInfo;
     }
 
     /**
@@ -568,161 +556,122 @@
      * @hide
      */
     public String getCallingPackage() {
-        if (mCallback != null) {
-            return createRemoteUserInfo(mCallback.mCurrentData).getPackageName();
+        if (mCallback != null && mCallback.mCurrentControllerInfo != null) {
+            return mCallback.mCurrentControllerInfo.getPackageName();
         }
         return null;
     }
 
-    private void dispatchPrepare(Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PREPARE, null, extras);
+    private void dispatchPrepare(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
     }
 
-    private void dispatchPrepareFromMediaId(String mediaId, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
+    private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
     }
 
-    private void dispatchPrepareFromSearch(String query, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
+    private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
     }
 
-    private void dispatchPrepareFromUri(Uri uri, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
+    private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
     }
 
-    private void dispatchPlay(Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PLAY, null, extras);
+    private void dispatchPlay(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
     }
 
-    private void dispatchPlayFromMediaId(String mediaId, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
+    private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
     }
 
-    private void dispatchPlayFromSearch(String query, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
+    private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
     }
 
-    private void dispatchPlayFromUri(Uri uri, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
+    private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
     }
 
-    private void dispatchSkipToItem(long id, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, extras);
+    private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
+        postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
     }
 
-    private void dispatchPause(Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PAUSE, null, extras);
+    private void dispatchPause(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
     }
 
-    private void dispatchStop(Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_STOP, null, extras);
+    private void dispatchStop(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
     }
 
-    private void dispatchNext(Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_NEXT, null, extras);
+    private void dispatchNext(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
     }
 
-    private void dispatchPrevious(Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_PREVIOUS, null, extras);
+    private void dispatchPrevious(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
     }
 
-    private void dispatchFastForward(Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD, null, extras);
+    private void dispatchFastForward(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
     }
 
-    private void dispatchRewind(Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_REWIND, null, extras);
+    private void dispatchRewind(RemoteUserInfo caller) {
+        postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
     }
 
-    private void dispatchSeekTo(long pos, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos, extras);
+    private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
+        postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
     }
 
-    private void dispatchRate(Rating rating, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_RATE, rating, extras);
+    private void dispatchRate(RemoteUserInfo caller, Rating rating) {
+        postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
     }
 
-    private void dispatchCustomAction(String action, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, extras);
+    private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
+        postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
     }
 
-    private void dispatchMediaButton(Intent mediaButtonIntent, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, extras);
+    private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
+        postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
     }
 
-    private void dispatchAdjustVolume(int direction, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, extras);
+    private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
+            long delay) {
+        postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
+                mediaButtonIntent, null, delay);
     }
 
-    private void dispatchSetVolumeTo(int volume, Bundle extras) {
-        postToCallback(CallbackMessageHandler.MSG_SET_VOLUME, volume, extras);
+    private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
+        postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
     }
 
-    private void postCommand(String command, Bundle args, ResultReceiver resultCb, Bundle extras) {
+    private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
+        postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
+    }
+
+    private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
+            ResultReceiver resultCb) {
         Command cmd = new Command(command, args, resultCb);
-        postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd, extras);
+        postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
     }
 
-    private void postToCallback(int what, Object obj, Bundle extras) {
+    private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
+        postToCallbackDelayed(caller, what, obj, data, 0);
+    }
+
+    private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
+            long delay) {
         synchronized (mLock) {
             if (mCallback != null) {
-                mCallback.post(what, obj, extras);
+                mCallback.post(caller, what, obj, data, delay);
             }
         }
     }
 
     /**
-     * Creates the extra bundle that includes the caller information.
-     *
-     * @return An extraBundle that contains caller information
-     */
-    private static Bundle createExtraBundle(String packageName, int pid, int uid) {
-        return createExtraBundle(packageName, pid, uid, null);
-    }
-
-    /**
-     * Creates the extra bundle that includes the caller information.
-     *
-     * @param originalBundle bundle
-     * @return An extraBundle that contains caller information
-     */
-    private static Bundle createExtraBundle(String packageName, int pid, int uid,
-            Bundle originalBundle) {
-        Bundle bundle = new Bundle();
-        bundle.putString(EXTRA_KEY_CALLING_PACKAGE, packageName);
-        bundle.putInt(EXTRA_KEY_CALLING_PID, pid);
-        bundle.putInt(EXTRA_KEY_CALLING_UID, uid);
-        if (originalBundle != null) {
-            bundle.putBundle(EXTRA_KEY_ORIGINAL_BUNDLE, originalBundle);
-        }
-        return bundle;
-    }
-
-    /**
-     * Creates the {@link RemoteUserInfo} from the extra bundle created by
-     * {@link #createExtraBundle}.
-     *
-     * @param extraBundle that previously created by createExtraBundle()
-     * @return a RemoteUserInfo
-     */
-    private static RemoteUserInfo createRemoteUserInfo(Bundle extraBundle) {
-        return new RemoteUserInfo(
-                extraBundle.getString(EXTRA_KEY_CALLING_PACKAGE),
-                extraBundle.getInt(EXTRA_KEY_CALLING_PID, INVALID_PID),
-                extraBundle.getInt(EXTRA_KEY_CALLING_UID, INVALID_UID));
-    }
-
-    /**
-     * Gets the original bundle from the extra bundle created by {@link #createExtraBundle}.
-     *
-     * @param extraBundle that previously created by createExtraBundle()
-     * @return a Bundle
-     */
-    private static Bundle getOriginalBundle(Bundle extraBundle) {
-        return extraBundle.getBundle(EXTRA_KEY_ORIGINAL_BUNDLE);
-    }
-
-    /**
      * Return true if this is considered an active playback state.
      *
      * @hide
@@ -872,10 +821,9 @@
                                 }
                             } else {
                                 mMediaPlayPauseKeyPending = true;
-                                mHandler.postDelayed(CallbackMessageHandler
-                                                .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
-                                        mSession.getCurrentData(),
-                                        ViewConfiguration.getDoubleTapTimeout());
+                                mSession.dispatchMediaButtonDelayed(
+                                        mSession.getCurrentControllerInfo(),
+                                        mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
                             }
                             return true;
                         default:
@@ -1109,12 +1057,19 @@
             mMediaSession = new WeakReference<>(session);
         }
 
+        private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
+            return new RemoteUserInfo(packageName, pid, uid,
+                    caller != null ? caller.asBinder() : null);
+        }
+
         @Override
-        public void onCommand(String packageName, int pid, int uid, String command, Bundle args,
-                ResultReceiver cb) {
+        public void onCommand(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.postCommand(command, args, cb, createExtraBundle(packageName, pid, uid));
+                session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
+                        command, args, cb);
             }
         }
 
@@ -1124,8 +1079,8 @@
             MediaSession session = mMediaSession.get();
             try {
                 if (session != null) {
-                    session.dispatchMediaButton(
-                            mediaButtonIntent, createExtraBundle(packageName, pid, uid));
+                    session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, null),
+                            mediaButtonIntent);
                 }
             } finally {
                 if (cb != null) {
@@ -1135,173 +1090,205 @@
         }
 
         @Override
-        public void onPrepare(String packageName, int pid, int uid) {
+        public void onMediaButtonFromController(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, Intent mediaButtonIntent) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchPrepare(createExtraBundle(packageName, pid, uid));
+                session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
+                        mediaButtonIntent);
             }
         }
 
         @Override
-        public void onPrepareFromMediaId(String packageName, int pid, int uid, String mediaId,
+        public void onPrepare(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
+            MediaSession session = mMediaSession.get();
+            if (session != null) {
+                session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
+            }
+        }
+
+        @Override
+        public void onPrepareFromMediaId(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String mediaId,
                 Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPrepareFromMediaId(
-                        mediaId, createExtraBundle(packageName, pid, uid, extras));
+                        createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
             }
         }
 
         @Override
-        public void onPrepareFromSearch(String packageName, int pid, int uid, String query,
+        public void onPrepareFromSearch(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String query,
                 Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 session.dispatchPrepareFromSearch(
-                        query, createExtraBundle(packageName, pid, uid, extras));
+                        createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
             }
         }
 
         @Override
-        public void onPrepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
+        public void onPrepareFromUri(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, Uri uri, Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchPrepareFromUri(uri,
-                        createExtraBundle(packageName, pid, uid, extras));
+                session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
+                        uri, extras);
             }
         }
 
         @Override
-        public void onPlay(String packageName, int pid, int uid) {
+        public void onPlay(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchPlay(createExtraBundle(packageName, pid, uid));
+                session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
             }
         }
 
         @Override
-        public void onPlayFromMediaId(String packageName, int pid, int uid, String mediaId,
+        public void onPlayFromMediaId(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String mediaId,
                 Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchPlayFromMediaId(
-                        mediaId, createExtraBundle(packageName, pid, uid, extras));
+                session.dispatchPlayFromMediaId(createRemoteUserInfo(packageName, pid, uid, caller),
+                        mediaId, extras);
             }
         }
 
         @Override
-        public void onPlayFromSearch(String packageName, int pid, int uid, String query,
+        public void onPlayFromSearch(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String query,
                 Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchPlayFromSearch(query, createExtraBundle(packageName, pid, uid,
-                        extras));
+                session.dispatchPlayFromSearch(createRemoteUserInfo(packageName, pid, uid, caller),
+                        query, extras);
             }
         }
 
         @Override
-        public void onPlayFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
+        public void onPlayFromUri(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, Uri uri, Bundle extras) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchPlayFromUri(uri, createExtraBundle(packageName, pid, uid, extras));
+                session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
+                        uri, extras);
             }
         }
 
         @Override
-        public void onSkipToTrack(String packageName, int pid, int uid, long id) {
+        public void onSkipToTrack(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, long id) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchSkipToItem(id, createExtraBundle(packageName, pid, uid));
+                session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id);
             }
         }
 
         @Override
-        public void onPause(String packageName, int pid, int uid) {
+        public void onPause(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchPause(createExtraBundle(packageName, pid, uid));
+                session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
             }
         }
 
         @Override
-        public void onStop(String packageName, int pid, int uid) {
+        public void onStop(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchStop(createExtraBundle(packageName, pid, uid));
+                session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
             }
         }
 
         @Override
-        public void onNext(String packageName, int pid, int uid) {
+        public void onNext(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchNext(createExtraBundle(packageName, pid, uid));
+                session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
             }
         }
 
         @Override
-        public void onPrevious(String packageName, int pid, int uid) {
+        public void onPrevious(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchPrevious(createExtraBundle(packageName, pid, uid));
+                session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
             }
         }
 
         @Override
-        public void onFastForward(String packageName, int pid, int uid) {
+        public void onFastForward(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchFastForward(createExtraBundle(packageName, pid, uid));
+                session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller));
             }
         }
 
         @Override
-        public void onRewind(String packageName, int pid, int uid) {
+        public void onRewind(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchRewind(createExtraBundle(packageName, pid, uid));
+                session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
             }
         }
 
         @Override
-        public void onSeekTo(String packageName, int pid, int uid, long pos) {
+        public void onSeekTo(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, long pos) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchSeekTo(pos, createExtraBundle(packageName, pid, uid));
+                session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos);
             }
         }
 
         @Override
-        public void onRate(String packageName, int pid, int uid, Rating rating) {
+        public void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+                Rating rating) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchRate(rating, createExtraBundle(packageName, pid, uid));
+                session.dispatchRate(createRemoteUserInfo(packageName, pid, uid, caller), rating);
             }
         }
 
         @Override
-        public void onCustomAction(String packageName, int pid, int uid, String action,
-                Bundle args) {
+        public void onCustomAction(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String action, Bundle args) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchCustomAction(
-                        action, createExtraBundle(packageName, pid, uid, args));
+                session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller),
+                        action, args);
             }
         }
 
         @Override
-        public void onAdjustVolume(String packageName, int pid, int uid, int direction) {
+        public void onAdjustVolume(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, int direction) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchAdjustVolume(direction, createExtraBundle(packageName, pid, uid));
+                session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller),
+                        direction);
             }
         }
 
         @Override
-        public void onSetVolumeTo(String packageName, int pid, int uid, int value) {
+        public void onSetVolumeTo(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, int value) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
-                session.dispatchSetVolumeTo(value, createExtraBundle(packageName, pid, uid));
+                session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller),
+                        value);
             }
         }
     }
@@ -1424,7 +1411,6 @@
     }
 
     private class CallbackMessageHandler extends Handler {
-
         private static final int MSG_COMMAND = 1;
         private static final int MSG_MEDIA_BUTTON = 2;
         private static final int MSG_PREPARE = 3;
@@ -1450,7 +1436,7 @@
         private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
 
         private MediaSession.Callback mCallback;
-        private Bundle mCurrentData;
+        private RemoteUserInfo mCurrentControllerInfo;
 
         public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
             super(looper, null, true);
@@ -1458,60 +1444,58 @@
             mCallback.mHandler = this;
         }
 
-        public void post(int what, Object obj, Bundle data) {
-            Message msg = obtainMessage(what, obj);
+        public void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
+            Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
+            Message msg = obtainMessage(what, objWithCaller);
             msg.setData(data);
-            msg.sendToTarget();
-        }
-
-        public void postDelayed(int what, Bundle data, long delayMs) {
-            Message msg = obtainMessage(what);
-            msg.setData(data);
-            sendMessageDelayed(msg, delayMs);
+            if (delayMs > 0) {
+                sendMessageDelayed(msg, delayMs);
+            } else {
+                sendMessage(msg);
+            }
         }
 
         @Override
         public void handleMessage(Message msg) {
-            VolumeProvider vp;
-            Bundle data = msg.getData();
-            Bundle originalBundle = getOriginalBundle(data);
+            mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
 
-            mCurrentData = data;
+            VolumeProvider vp;
+            Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
 
             switch (msg.what) {
                 case MSG_COMMAND:
-                    Command cmd = (Command) msg.obj;
+                    Command cmd = (Command) obj;
                     mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
                     break;
                 case MSG_MEDIA_BUTTON:
-                    mCallback.onMediaButtonEvent((Intent) msg.obj);
+                    mCallback.onMediaButtonEvent((Intent) obj);
                     break;
                 case MSG_PREPARE:
                     mCallback.onPrepare();
                     break;
                 case MSG_PREPARE_MEDIA_ID:
-                    mCallback.onPrepareFromMediaId((String) msg.obj, originalBundle);
+                    mCallback.onPrepareFromMediaId((String) obj, msg.getData());
                     break;
                 case MSG_PREPARE_SEARCH:
-                    mCallback.onPrepareFromSearch((String) msg.obj, originalBundle);
+                    mCallback.onPrepareFromSearch((String) obj, msg.getData());
                     break;
                 case MSG_PREPARE_URI:
-                    mCallback.onPrepareFromUri((Uri) msg.obj, originalBundle);
+                    mCallback.onPrepareFromUri((Uri) obj, msg.getData());
                     break;
                 case MSG_PLAY:
                     mCallback.onPlay();
                     break;
                 case MSG_PLAY_MEDIA_ID:
-                    mCallback.onPlayFromMediaId((String) msg.obj, originalBundle);
+                    mCallback.onPlayFromMediaId((String) obj, msg.getData());
                     break;
                 case MSG_PLAY_SEARCH:
-                    mCallback.onPlayFromSearch((String) msg.obj, originalBundle);
+                    mCallback.onPlayFromSearch((String) obj, msg.getData());
                     break;
                 case MSG_PLAY_URI:
-                    mCallback.onPlayFromUri((Uri) msg.obj, originalBundle);
+                    mCallback.onPlayFromUri((Uri) obj, msg.getData());
                     break;
                 case MSG_SKIP_TO_ITEM:
-                    mCallback.onSkipToQueueItem((Long) msg.obj);
+                    mCallback.onSkipToQueueItem((Long) obj);
                     break;
                 case MSG_PAUSE:
                     mCallback.onPause();
@@ -1532,20 +1516,20 @@
                     mCallback.onRewind();
                     break;
                 case MSG_SEEK_TO:
-                    mCallback.onSeekTo((Long) msg.obj);
+                    mCallback.onSeekTo((Long) obj);
                     break;
                 case MSG_RATE:
-                    mCallback.onSetRating((Rating) msg.obj);
+                    mCallback.onSetRating((Rating) obj);
                     break;
                 case MSG_CUSTOM_ACTION:
-                    mCallback.onCustomAction((String) msg.obj, originalBundle);
+                    mCallback.onCustomAction((String) obj, msg.getData());
                     break;
                 case MSG_ADJUST_VOLUME:
                     synchronized (mLock) {
                         vp = mVolumeProvider;
                     }
                     if (vp != null) {
-                        vp.onAdjustVolume((int) msg.obj);
+                        vp.onAdjustVolume((int) obj);
                     }
                     break;
                 case MSG_SET_VOLUME:
@@ -1553,14 +1537,14 @@
                         vp = mVolumeProvider;
                     }
                     if (vp != null) {
-                        vp.onSetVolumeTo((int) msg.obj);
+                        vp.onSetVolumeTo((int) obj);
                     }
                     break;
                 case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
                     mCallback.handleMediaPlayPauseKeySingleTapIfPending();
                     break;
             }
-            mCurrentData = null;
+            mCurrentControllerInfo = null;
         }
     }
 }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index f54bfc1..3f0b6c5 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -30,6 +30,7 @@
 import android.media.MediaSession2;
 import android.media.MediaSessionService2;
 import android.media.SessionToken2;
+import android.media.browse.MediaBrowser;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -818,19 +819,31 @@
 
     /**
      * Information of a remote user of {@link MediaSession} or {@link MediaBrowserService}.
-     * This can be used to decide whether the remote user is trusted app.
+     * This can be used to decide whether the remote user is trusted app, and also differentiate
+     * caller of {@link MediaSession} and {@link MediaBrowserService} callbacks.
+     * <p>
+     * See {@link #equals(Object)} to take a look at how it differentiate media controller.
      *
      * @see #isTrustedForMediaControl(RemoteUserInfo)
      */
     public static final class RemoteUserInfo {
-        private String mPackageName;
-        private int mPid;
-        private int mUid;
+        private final String mPackageName;
+        private final int mPid;
+        private final int mUid;
+        private final IBinder mCallerBinder;
 
-        public RemoteUserInfo(String packageName, int pid, int uid) {
+        public RemoteUserInfo(@NonNull String packageName, int pid, int uid) {
+            this(packageName, pid, uid, null);
+        }
+
+        /**
+         * @hide
+         */
+        public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) {
             mPackageName = packageName;
             mPid = pid;
             mUid = uid;
+            mCallerBinder = callerBinder;
         }
 
         /**
@@ -854,15 +867,29 @@
             return mUid;
         }
 
+        /**
+         * Returns equality of two RemoteUserInfo. Two RemoteUserInfos are the same only if they're
+         * sent to the same controller (either {@link MediaController} or
+         * {@link MediaBrowser}. If it's not nor one of them is triggered by the key presses, they
+         * would be considered as different one.
+         * <p>
+         * If you only want to compare the caller's package, compare them with the
+         * {@link #getPackageName()}, {@link #getPid()}, and/or {@link #getUid()} directly.
+         *
+         * @param obj the reference object with which to compare.
+         * @return {@code true} if equals, {@code false} otherwise
+         */
         @Override
         public boolean equals(Object obj) {
             if (!(obj instanceof RemoteUserInfo)) {
                 return false;
             }
+            if (this == obj) {
+                return true;
+            }
             RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj;
-            return TextUtils.equals(mPackageName, otherUserInfo.mPackageName)
-                    && mPid == otherUserInfo.mPid
-                    && mUid == otherUserInfo.mUid;
+            return (mCallerBinder == null || otherUserInfo.mCallerBinder == null) ? false
+                    : mCallerBinder.equals(otherUserInfo.mCallerBinder);
         }
 
         @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 4e9e566..0b58c9d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -505,5 +505,6 @@
                 callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
             }
         }
+        mDeviceManager.onProfileConnectionStateChanged(device, state, bluetoothProfile);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 15f6983..e9d96ae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.util.Log;
 
@@ -323,16 +324,31 @@
             if (cachedDevice.getHiSyncId() == hiSyncId) {
                 if (firstMatchedIndex != -1) {
                     /* Found the second one */
-                    mCachedDevices.remove(i);
-                    mHearingAidDevicesNotAddedInCache.add(cachedDevice);
+                    int indexToRemoveFromUi;
+                    CachedBluetoothDevice deviceToRemoveFromUi;
+
                     // Since the hiSyncIds have been updated for a connected pair of hearing aids,
                     // we remove the entry of one the hearing aids from the UI. Unless the
-                    // hiSyncId get updated, the system does not know its a hearing aid, so we add
+                    // hiSyncId get updated, the system does not know it is a hearing aid, so we add
                     // both the hearing aids as separate entries in the UI first, then remove one
-                    // of them after the hiSyncId is populated.
-                    log("onHiSyncIdChanged: removed device=" + cachedDevice + ", with hiSyncId="
-                        + hiSyncId);
-                    mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
+                    // of them after the hiSyncId is populated. We will choose the device that
+                    // is not connected to be removed.
+                    if (cachedDevice.isConnected()) {
+                        indexToRemoveFromUi = firstMatchedIndex;
+                        deviceToRemoveFromUi = mCachedDevices.get(firstMatchedIndex);
+                        mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice);
+                    } else {
+                        indexToRemoveFromUi = i;
+                        deviceToRemoveFromUi = cachedDevice;
+                        mCachedDevicesMapForHearingAids.put(hiSyncId,
+                                                            mCachedDevices.get(firstMatchedIndex));
+                    }
+
+                    mCachedDevices.remove(indexToRemoveFromUi);
+                    mHearingAidDevicesNotAddedInCache.add(deviceToRemoveFromUi);
+                    log("onHiSyncIdChanged: removed from UI device=" + deviceToRemoveFromUi
+                        + ", with hiSyncId=" + hiSyncId);
+                    mBtManager.getEventManager().dispatchDeviceRemoved(deviceToRemoveFromUi);
                     break;
                 } else {
                     mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice);
@@ -342,6 +358,72 @@
         }
     }
 
+    private CachedBluetoothDevice getHearingAidOtherDevice(CachedBluetoothDevice thisDevice,
+                                                           long hiSyncId) {
+        if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+            return null;
+        }
+
+        // Searched the lists for the other side device with the matching hiSyncId.
+        for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) {
+            if ((hiSyncId == notCachedDevice.getHiSyncId()) &&
+                (!Objects.equals(notCachedDevice, thisDevice))) {
+                return notCachedDevice;
+            }
+        }
+
+        CachedBluetoothDevice cachedDevice = mCachedDevicesMapForHearingAids.get(hiSyncId);
+        if (!Objects.equals(cachedDevice, thisDevice)) {
+            return cachedDevice;
+        }
+        return null;
+    }
+
+    private void hearingAidSwitchDisplayDevice(CachedBluetoothDevice toDisplayDevice,
+                                           CachedBluetoothDevice toHideDevice, long hiSyncId)
+    {
+        log("hearingAidSwitchDisplayDevice: toDisplayDevice=" + toDisplayDevice
+            + ", toHideDevice=" + toHideDevice);
+
+        // Remove the "toHideDevice" device from the UI.
+        mHearingAidDevicesNotAddedInCache.add(toHideDevice);
+        mCachedDevices.remove(toHideDevice);
+        mBtManager.getEventManager().dispatchDeviceRemoved(toHideDevice);
+
+        // Add the "toDisplayDevice" device to the UI.
+        mHearingAidDevicesNotAddedInCache.remove(toDisplayDevice);
+        mCachedDevices.add(toDisplayDevice);
+        mCachedDevicesMapForHearingAids.put(hiSyncId, toDisplayDevice);
+        mBtManager.getEventManager().dispatchDeviceAdded(toDisplayDevice);
+    }
+
+    public synchronized void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
+                                                             int state, int bluetoothProfile) {
+        if (bluetoothProfile == BluetoothProfile.HEARING_AID
+            && cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
+            && cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
+
+            long hiSyncId = cachedDevice.getHiSyncId();
+
+            CachedBluetoothDevice otherDevice = getHearingAidOtherDevice(cachedDevice, hiSyncId);
+            if (otherDevice == null) {
+                // no other side device. Nothing to do.
+                return;
+            }
+
+            if (state == BluetoothProfile.STATE_CONNECTED &&
+                mHearingAidDevicesNotAddedInCache.contains(cachedDevice)) {
+                hearingAidSwitchDisplayDevice(cachedDevice, otherDevice, hiSyncId);
+            } else if (state == BluetoothProfile.STATE_DISCONNECTED
+                       && otherDevice.isConnected()) {
+                CachedBluetoothDevice mapDevice = mCachedDevicesMapForHearingAids.get(hiSyncId);
+                if ((mapDevice != null) && (Objects.equals(cachedDevice, mapDevice))) {
+                    hearingAidSwitchDisplayDevice(otherDevice, cachedDevice, hiSyncId);
+                }
+            }
+        }
+    }
+
     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
         final long hiSyncId = device.getHiSyncId();
 
@@ -353,9 +435,16 @@
                 // TODO: Look for more cleanups on unpairing the device.
                 mHearingAidDevicesNotAddedInCache.remove(i);
                 if (device == cachedDevice) continue;
+                log("onDeviceUnpaired: Unpair device=" + cachedDevice);
                 cachedDevice.unpair();
             }
         }
+
+        CachedBluetoothDevice mappedDevice = mCachedDevicesMapForHearingAids.get(hiSyncId);
+        if ((mappedDevice != null) && (!Objects.equals(device, mappedDevice))) {
+            log("onDeviceUnpaired: Unpair mapped device=" + mappedDevice);
+            mappedDevice.unpair();
+        }
     }
 
     public synchronized void dispatchAudioModeChanged() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 466980c..1080690 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -98,5 +98,8 @@
 
         verify(mBluetoothCallback).onProfileConnectionStateChanged(mCachedBluetoothDevice,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
+
+        verify(mCachedDeviceManager).onProfileConnectionStateChanged(mCachedBluetoothDevice,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index bab3cab..16ed85c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -235,7 +235,7 @@
      * Test to verify onHiSyncIdChanged() for hearing aid devices with same HiSyncId.
      */
     @Test
-    public void testOnDeviceAdded_sameHiSyncId_populateInDifferentLists() {
+    public void testOnHiSyncIdChanged_sameHiSyncId_populateInDifferentLists() {
         CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
             mLocalProfileManager, mDevice1);
         assertThat(cachedDevice1).isNotNull();
@@ -261,16 +261,53 @@
         assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1);
         Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
         assertThat(devices).contains(cachedDevice2);
-        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values())
-            .contains(cachedDevice2);
-        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids)
+            .containsKey(HISYNCID1);
+    }
+
+    /**
+     * Test to verify onHiSyncIdChanged() for 2 hearing aid devices with same HiSyncId but one
+     * device is connected and other is disconnected. The connected device should be chosen.
+     */
+    @Test
+    public void testOnHiSyncIdChanged_sameHiSyncIdAndOneConnected_chooseConnectedDevice() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+        cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+
+        /* Device 1 is connected and Device 2 is disconnected */
+        when(mHearingAidProfile.getConnectionStatus(mDevice1)).
+            thenReturn(BluetoothProfile.STATE_CONNECTED);
+        when(mHearingAidProfile.getConnectionStatus(mDevice2)).
+            thenReturn(BluetoothProfile.STATE_DISCONNECTED);
+
+        // Since both devices do not have hiSyncId, they should be added in mCachedDevices.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty();
+
+        cachedDevice1.setHiSyncId(HISYNCID1);
+        cachedDevice2.setHiSyncId(HISYNCID1);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1);
+
+        // Only the connected device, device 1, should be visible to UI.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).
+            containsExactly(HISYNCID1, cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).
+            containsExactly(cachedDevice2);
     }
 
     /**
      * Test to verify onHiSyncIdChanged() for hearing aid devices with different HiSyncId.
      */
     @Test
-    public void testOnDeviceAdded_differentHiSyncId_populateInSameList() {
+    public void testOnHiSyncIdChanged_differentHiSyncId_populateInSameList() {
         CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
             mLocalProfileManager, mDevice1);
         assertThat(cachedDevice1).isNotNull();
@@ -303,6 +340,153 @@
     }
 
     /**
+     * Test to verify onProfileConnectionStateChanged() for single hearing aid device connection.
+     */
+    @Test
+    public void testOnProfileConnectionStateChanged_singleDeviceConnected_visible() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+
+        // Since both devices do not have hiSyncId, they should be added in mCachedDevices.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty();
+
+        cachedDevice1.setHiSyncId(HISYNCID1);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1);
+
+        // Connect the Device 1
+        mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1,
+            BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID);
+
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).
+            containsExactly(HISYNCID1, cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+
+        // Disconnect the Device 1
+        mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1,
+            BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID);
+
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).
+            containsExactly(HISYNCID1, cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+    }
+
+    /**
+     * Test to verify onProfileConnectionStateChanged() for two hearing aid devices where both
+     * devices are disconnected and they get connected.
+     */
+    @Test
+    public void testOnProfileConnectionStateChanged_twoDevicesConnected_oneDeviceVisible() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+        cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+        // Since both devices do not have hiSyncId, they should be added in mCachedDevices.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty();
+
+        cachedDevice1.setHiSyncId(HISYNCID1);
+        cachedDevice2.setHiSyncId(HISYNCID1);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1);
+
+        // There should be one cached device but can be either one.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1);
+
+        // Connect the Device 1
+        mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1,
+            BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID);
+
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).
+            containsExactly(HISYNCID1, cachedDevice1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice2);
+        assertThat(mCachedDeviceManager.mCachedDevices).contains(cachedDevice1);
+
+        when(mHearingAidProfile.getConnectionStatus(mDevice1)).
+            thenReturn(BluetoothProfile.STATE_CONNECTED);
+        when(mHearingAidProfile.getConnectionStatus(mDevice2)).
+            thenReturn(BluetoothProfile.STATE_DISCONNECTED);
+
+        // Connect the Device 2
+        mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice2,
+            BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID);
+
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1);
+        assertThat(mCachedDeviceManager.mCachedDevices).hasSize(1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1);
+    }
+
+    /**
+     * Test to verify onProfileConnectionStateChanged() for two hearing aid devices where both
+     * devices are connected and they get disconnected.
+     */
+    @Test
+    public void testOnProfileConnectionStateChanged_twoDevicesDisconnected_oneDeviceVisible() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+            mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+        cachedDevice1.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+        cachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mHearingAidProfile.getConnectionStatus(mDevice1)).
+            thenReturn(BluetoothProfile.STATE_CONNECTED);
+        when(mHearingAidProfile.getConnectionStatus(mDevice2)).
+            thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+        // Since both devices do not have hiSyncId, they should be added in mCachedDevices.
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty();
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty();
+
+        cachedDevice1.setHiSyncId(HISYNCID1);
+        cachedDevice2.setHiSyncId(HISYNCID1);
+        mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1);
+
+        /* Disconnect the Device 1 */
+        mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice1,
+            BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID);
+
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).containsExactly(cachedDevice2);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1);
+        assertThat(mCachedDeviceManager.mCachedDevices).contains(cachedDevice2);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids)
+            .containsExactly(HISYNCID1, cachedDevice2);
+
+        when(mHearingAidProfile.getConnectionStatus(mDevice1)).
+            thenReturn(BluetoothProfile.STATE_DISCONNECTED);
+        when(mHearingAidProfile.getConnectionStatus(mDevice2)).
+            thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+        /* Disconnect the Device 2 */
+        mCachedDeviceManager.onProfileConnectionStateChanged(cachedDevice2,
+            BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.HEARING_AID);
+
+        assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1);
+        assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1);
+        assertThat(mCachedDeviceManager.mCachedDevices).hasSize(1);
+        assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1);
+    }
+
+    /**
      * Test to verify OnDeviceUnpaired() for a paired hearing Aid device pair.
      */
     @Test
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index db03ed2..dfa4bf9 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -28,7 +28,7 @@
     android:clipToPadding="false"
     android:orientation="vertical"
     android:layout_centerHorizontal="true">
-    <com.android.systemui.statusbar.AlphaOptimizedTextView
+    <view class="com.android.keyguard.KeyguardSliceView$TitleView"
               android:id="@+id/title"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
index 461d62e..6c1ae99 100644
--- a/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
+++ b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
@@ -29,7 +29,7 @@
         </vector>
     </aapt:attr>
 
-    <!-- Repeat all animations 3 times but don't fade out at the end -->
+    <!-- Repeat all animations 5 times but don't fade out at the end -->
     <target android:name="root">
         <aapt:attr name="android:animation">
             <set android:ordering="sequentially">
@@ -67,6 +67,34 @@
                                 android:valueFrom="0"
                                 android:valueTo="1"
                                 android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
             </set>
         </aapt:attr>
     </target>
@@ -117,6 +145,40 @@
                         <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="?attr/rotateButtonStartAngle"
+                                android:valueTo="?attr/rotateButtonStartAngle"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="?attr/rotateButtonStartAngle"
+                                android:valueTo="?attr/rotateButtonEndAngle">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="?attr/rotateButtonStartAngle"
+                                android:valueTo="?attr/rotateButtonStartAngle"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="?attr/rotateButtonStartAngle"
+                                android:valueTo="?attr/rotateButtonEndAngle">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
             </set>
         </aapt:attr>
     </target>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index f066e34..f5abd06 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -47,6 +47,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.statusbar.AlphaOptimizedTextView;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
@@ -244,7 +245,7 @@
      * @param charSequence Original text.
      * @return Optimal string.
      */
-    private CharSequence findBestLineBreak(CharSequence charSequence) {
+    private static CharSequence findBestLineBreak(CharSequence charSequence) {
         if (TextUtils.isEmpty(charSequence)) {
             return charSequence;
         }
@@ -370,27 +371,6 @@
         mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        // Find best ellipsis strategy for the title.
-        // Done on onMeasure since TextView#getLayout needs a measure pass to calculate its bounds.
-        Layout layout = mTitle.getLayout();
-        if (layout != null) {
-            final int lineCount = layout.getLineCount();
-            if (lineCount > 0) {
-                if (layout.getEllipsisCount(lineCount - 1) == 0) {
-                    CharSequence title = mTitle.getText();
-                    CharSequence bestLineBreak = findBestLineBreak(title);
-                    if (!TextUtils.equals(title, bestLineBreak)) {
-                        mTitle.setText(bestLineBreak);
-                    }
-                }
-            }
-        }
-    }
-
     public void refresh() {
         Slice slice = SliceManager.getInstance(getContext()).bindSlice(mKeyguardSliceUri);
         onChanged(slice);
@@ -553,6 +533,46 @@
         }
     }
 
+    /**
+     * A text view that will split its contents in 2 lines when possible.
+     */
+    static class TitleView extends AlphaOptimizedTextView {
+
+        public TitleView(Context context) {
+            super(context);
+        }
+
+        public TitleView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public TitleView(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+
+        public TitleView(Context context, AttributeSet attrs, int defStyleAttr,
+                int defStyleRes) {
+            super(context, attrs, defStyleAttr, defStyleRes);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+            Layout layout = getLayout();
+            int lineCount = layout.getLineCount();
+            boolean ellipsizing = layout.getEllipsisCount(lineCount - 1) != 0;
+            if (lineCount > 0 && !ellipsizing) {
+                CharSequence title = getText();
+                CharSequence bestLineBreak = findBestLineBreak(title);
+                if (!TextUtils.equals(title, bestLineBreak)) {
+                    setText(bestLineBreak);
+                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+                }
+            }
+        }
+    }
+
     private class SliceViewTransitionListener implements LayoutTransition.TransitionListener {
         @Override
         public void startTransition(LayoutTransition transition, ViewGroup container, View view,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index e9d78d9..e2047bf 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -69,7 +69,7 @@
                 new DozeScreenState(wrappedService, handler, params, wakeLock),
                 createDozeScreenBrightness(context, wrappedService, sensorManager, host, params,
                         handler),
-                new DozeWallpaperState(context, params)
+                new DozeWallpaperState(context)
         });
 
         return machine;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 9d110fb..47f86fe 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -38,40 +38,33 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final IWallpaperManager mWallpaperManagerService;
-    private boolean mKeyguardVisible;
     private boolean mIsAmbientMode;
     private final DozeParameters mDozeParameters;
 
-    public DozeWallpaperState(Context context, DozeParameters dozeParameters) {
+    public DozeWallpaperState(Context context) {
         this(IWallpaperManager.Stub.asInterface(
                 ServiceManager.getService(Context.WALLPAPER_SERVICE)),
-                dozeParameters, KeyguardUpdateMonitor.getInstance(context));
+                DozeParameters.getInstance(context));
     }
 
     @VisibleForTesting
-    DozeWallpaperState(IWallpaperManager wallpaperManagerService, DozeParameters parameters,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+    DozeWallpaperState(IWallpaperManager wallpaperManagerService, DozeParameters parameters) {
         mWallpaperManagerService = wallpaperManagerService;
         mDozeParameters = parameters;
-        keyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() {
-            @Override
-            public void onKeyguardVisibilityChanged(boolean showing) {
-                mKeyguardVisible = showing;
-            }
-        });
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         final boolean isAmbientMode;
         switch (newState) {
+            case DOZE:
             case DOZE_AOD:
             case DOZE_AOD_PAUSING:
             case DOZE_AOD_PAUSED:
             case DOZE_REQUEST_PULSE:
             case DOZE_PULSING:
             case DOZE_PULSE_DONE:
-                isAmbientMode = mDozeParameters.getAlwaysOn();
+                isAmbientMode = true;
                 break;
             default:
                 isAmbientMode = false;
@@ -81,7 +74,9 @@
         if (isAmbientMode) {
             animated = mDozeParameters.shouldControlScreenOff();
         } else {
-            animated = !mDozeParameters.getDisplayNeedsBlanking();
+            boolean wakingUpFromPulse = oldState == DozeMachine.State.DOZE_PULSING
+                    && newState == DozeMachine.State.FINISH;
+            animated = !mDozeParameters.getDisplayNeedsBlanking() || wakingUpFromPulse;
         }
 
         if (isAmbientMode != mIsAmbientMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index ba265d8..d040f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -18,13 +18,13 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.car.user.CarUserManagerHelper;
 import android.content.Context;
 import android.view.View;
 import android.view.ViewStub;
 
 import android.support.v7.widget.GridLayoutManager;
 
-import com.android.settingslib.users.UserManagerHelper;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.StatusBar;
 
@@ -37,7 +37,7 @@
     private final UserGridRecyclerView mUserGridView;
     private final int mShortAnimDuration;
     private final StatusBar mStatusBar;
-    private final UserManagerHelper mUserManagerHelper;
+    private final CarUserManagerHelper mCarUserManagerHelper;
     private int mCurrentForegroundUserId;
     private boolean mShowing;
 
@@ -52,7 +52,7 @@
         mUserGridView.buildAdapter();
         mUserGridView.setUserSelectionListener(this::onUserSelected);
 
-        mUserManagerHelper = new UserManagerHelper(context);
+        mCarUserManagerHelper = new CarUserManagerHelper(context);
         updateCurrentForegroundUser();
 
         mShortAnimDuration = mContainer.getResources()
@@ -84,11 +84,11 @@
     }
 
     private boolean foregroundUserChanged() {
-        return mCurrentForegroundUserId != mUserManagerHelper.getForegroundUserId();
+        return mCurrentForegroundUserId != mCarUserManagerHelper.getCurrentForegroundUserId();
     }
 
     private void updateCurrentForegroundUser() {
-        mCurrentForegroundUserId = mUserManagerHelper.getForegroundUserId();
+        mCurrentForegroundUserId = mCarUserManagerHelper.getCurrentForegroundUserId();
     }
 
     private void onUserSelected(UserGridRecyclerView.UserRecord record) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 68e47f7..dda25ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -21,6 +21,7 @@
 import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
 import android.app.Dialog;
+import android.car.user.CarUserManagerHelper;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.pm.UserInfo;
@@ -41,7 +42,6 @@
 import androidx.car.widget.PagedListView;
 
 import com.android.internal.util.UserIcons;
-import com.android.settingslib.users.UserManagerHelper;
 import com.android.systemui.R;
 
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -53,16 +53,16 @@
  * One of the uses of this is for the lock screen in auto.
  */
 public class UserGridRecyclerView extends PagedListView implements
-        UserManagerHelper.OnUsersUpdateListener {
+        CarUserManagerHelper.OnUsersUpdateListener {
     private UserSelectionListener mUserSelectionListener;
     private UserAdapter mAdapter;
-    private UserManagerHelper mUserManagerHelper;
+    private CarUserManagerHelper mCarUserManagerHelper;
     private Context mContext;
 
     public UserGridRecyclerView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
-        mUserManagerHelper = new UserManagerHelper(mContext);
+        mCarUserManagerHelper = new CarUserManagerHelper(mContext);
     }
 
     /**
@@ -71,7 +71,7 @@
     @Override
     public void onFinishInflate() {
         super.onFinishInflate();
-        mUserManagerHelper.registerOnUsersUpdateListener(this);
+        mCarUserManagerHelper.registerOnUsersUpdateListener(this);
     }
 
     /**
@@ -80,7 +80,7 @@
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mUserManagerHelper.unregisterOnUsersUpdateListener();
+        mCarUserManagerHelper.unregisterOnUsersUpdateListener();
     }
 
     /**
@@ -89,7 +89,7 @@
      * @return the adapter
      */
     public void buildAdapter() {
-        List<UserRecord> userRecords = createUserRecords(mUserManagerHelper
+        List<UserRecord> userRecords = createUserRecords(mCarUserManagerHelper
                 .getAllUsers());
         mAdapter = new UserAdapter(mContext, userRecords);
         super.setAdapter(mAdapter);
@@ -102,19 +102,20 @@
                 // Don't display guests in the switcher.
                 continue;
             }
-            boolean isForeground = mUserManagerHelper.getForegroundUserId() == userInfo.id;
+            boolean isForeground =
+                mCarUserManagerHelper.getCurrentForegroundUserId() == userInfo.id;
             UserRecord record = new UserRecord(userInfo, false /* isStartGuestSession */,
                     false /* isAddUser */, isForeground);
             userRecords.add(record);
         }
 
         // Add guest user record if the foreground user is not a guest
-        if (!mUserManagerHelper.foregroundUserIsGuestUser()) {
+        if (!mCarUserManagerHelper.isForegroundUserGuest()) {
             userRecords.add(addGuestUserRecord());
         }
 
         // Add add user record if the foreground user can add users
-        if (mUserManagerHelper.foregroundUserCanAddUsers()) {
+        if (mCarUserManagerHelper.canForegroundUserAddUsers()) {
             userRecords.add(addUserRecord());
         }
 
@@ -148,7 +149,7 @@
     @Override
     public void onUsersUpdate() {
         mAdapter.clearUsers();
-        mAdapter.updateUsers(createUserRecords(mUserManagerHelper.getAllUsers()));
+        mAdapter.updateUsers(createUserRecords(mCarUserManagerHelper.getAllUsers()));
         mAdapter.notifyDataSetChanged();
     }
 
@@ -212,7 +213,7 @@
 
                 // If the user selects Guest, start the guest session.
                 if (userRecord.mIsStartGuestSession) {
-                    mUserManagerHelper.startNewGuestSession(mGuestName);
+                    mCarUserManagerHelper.startNewGuestSession(mGuestName);
                     return;
                 }
 
@@ -239,14 +240,14 @@
                     return;
                 }
                 // If the user doesn't want to be a guest or add a user, switch to the user selected
-                mUserManagerHelper.switchToUser(userRecord.mInfo);
+                mCarUserManagerHelper.switchToUser(userRecord.mInfo);
             });
 
         }
 
         private Bitmap getUserRecordIcon(UserRecord userRecord) {
             if (userRecord.mIsStartGuestSession) {
-                return mUserManagerHelper.getGuestDefaultIcon();
+                return mCarUserManagerHelper.getGuestDefaultIcon();
             }
 
             if (userRecord.mIsAddUser) {
@@ -254,7 +255,7 @@
                     .getDrawable(R.drawable.car_add_circle_round));
             }
 
-            return mUserManagerHelper.getUserIcon(userRecord.mInfo);
+            return mCarUserManagerHelper.getUserIcon(userRecord.mInfo);
         }
 
         @Override
@@ -272,7 +273,10 @@
 
             @Override
             protected UserInfo doInBackground(String... userNames) {
-                return mUserManagerHelper.createNewUser(userNames[0]);
+                // Default to create a non admin user for now. Need to add logic
+                // for user to choose whether they want to create an admin or non-admin
+                // user later.
+                return mCarUserManagerHelper.createNewNonAdminUser(userNames[0]);
             }
 
             @Override
@@ -282,7 +286,7 @@
             @Override
             protected void onPostExecute(UserInfo user) {
                 if (user != null) {
-                    mUserManagerHelper.switchToUser(user);
+                    mCarUserManagerHelper.switchToUser(user);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 2a37845..68359bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -608,7 +608,7 @@
     private int computeRotationProposalTimeout() {
         if (mAccessibilityFeedbackEnabled) return 20000;
         if (mHoveringRotationSuggestion) return 16000;
-        return 6000;
+        return 10000;
     }
 
     private boolean isRotateSuggestionIntroduced() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
index 5c80bb5..6ac4462 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
@@ -45,13 +45,11 @@
     private DozeWallpaperState mDozeWallpaperState;
     @Mock IWallpaperManager mIWallpaperManager;
     @Mock DozeParameters mDozeParameters;
-    @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDozeWallpaperState = new DozeWallpaperState(mIWallpaperManager, mDozeParameters,
-                mKeyguardUpdateMonitor);
+        mDozeWallpaperState = new DozeWallpaperState(mIWallpaperManager, mDozeParameters);
     }
 
     @Test
@@ -100,4 +98,28 @@
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
         verify(mIWallpaperManager).setInAmbientMode(eq(false), eq(false));
     }
+
+    @Test
+    public void testTransitionTo_requestPulseIsAmbientMode() throws RemoteException {
+        mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE,
+                DozeMachine.State.DOZE_REQUEST_PULSE);
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(false));
+    }
+
+    @Test
+    public void testTransitionTo_pulseIsAmbientMode() throws RemoteException {
+        mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
+                DozeMachine.State.DOZE_PULSING);
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(false));
+    }
+
+    @Test
+    public void testTransitionTo_animatesWhenWakingUpFromPulse() throws RemoteException {
+        mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
+                DozeMachine.State.DOZE_PULSING);
+        reset(mIWallpaperManager);
+        mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_PULSING,
+                DozeMachine.State.FINISH);
+        verify(mIWallpaperManager).setInAmbientMode(eq(false), eq(true));
+    }
 }
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 2a6f7c8..06329e57 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -1500,7 +1500,10 @@
                 userId = getUserOrWorkProfileId(clientPackage, userId);
                 if (userId != mCurrentUserId) {
                     int firstSdkInt = Build.VERSION.FIRST_SDK_INT;
-                    if (firstSdkInt == 0) firstSdkInt = Build.VERSION.SDK_INT;
+                    if (firstSdkInt < Build.VERSION_CODES.BASE) {
+                        Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be " +
+                                "at least VERSION_CODES.BASE");
+                    }
                     File baseDir;
                     if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
                         baseDir = Environment.getUserSystemDirectory(userId);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index b3aa0bf..442354b 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -235,6 +235,7 @@
      * @param packageName The package that made the original volume request.
      * @param pid The pid that made the original volume request.
      * @param uid The uid that made the original volume request.
+     * @param caller caller binder. can be {@code null} if it's from the volume key.
      * @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.
@@ -244,8 +245,9 @@
      * @param flags Any of the flags from {@link AudioManager}.
      * @param useSuggested True to use adjustSuggestedStreamVolume instead of
      */
-    public void adjustVolume(String packageName, int pid, int uid, boolean asSystemService,
-            int direction, int flags, boolean useSuggested) {
+    public void adjustVolume(String packageName, int pid, int uid,
+            ISessionControllerCallback caller, boolean asSystemService, int direction, int flags,
+            boolean useSuggested) {
         int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
         if (isPlaybackActive() || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
@@ -266,7 +268,7 @@
                 Log.w(TAG, "Muting remote playback is not supported");
                 return;
             }
-            mSessionCb.adjustVolume(packageName, pid, uid, asSystemService, direction);
+            mSessionCb.adjustVolume(packageName, pid, uid, caller, asSystemService, direction);
 
             int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
             mOptimisticVolume = volumeBefore + direction;
@@ -285,7 +287,8 @@
         }
     }
 
-    public void setVolumeTo(String packageName, int pid, int uid, int value, int flags) {
+    private void setVolumeTo(String packageName, int pid, int uid,
+            ISessionControllerCallback caller, int value, int flags) {
         if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
             int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
             mAudioManagerInternal.setStreamVolumeForUid(stream, value, flags, packageName, uid);
@@ -295,7 +298,7 @@
                 return;
             }
             value = Math.max(0, Math.min(value, mMaxVolume));
-            mSessionCb.setVolumeTo(packageName, pid, uid, value);
+            mSessionCb.setVolumeTo(packageName, pid, uid, caller, value);
 
             int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
             mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
@@ -611,6 +614,7 @@
                 try {
                     holder.mCallback.onVolumeInfoChanged(info);
                 } catch (DeadObjectException e) {
+                    mControllerCallbackHolders.remove(i);
                     logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
                 } catch (RemoteException e) {
                     logCallbackException("Unexpected exception in pushVolumeUpdate", holder, e);
@@ -629,8 +633,8 @@
                 try {
                     holder.mCallback.onEvent(event, data);
                 } catch (DeadObjectException e) {
-                    logCallbackException("Removing dead callback in pushEvent", holder, e);
                     mControllerCallbackHolders.remove(i);
+                    logCallbackException("Removing dead callback in pushEvent", holder, e);
                 } catch (RemoteException e) {
                     logCallbackException("unexpected exception in pushEvent", holder, e);
                 }
@@ -705,14 +709,6 @@
         return -1;
     }
 
-    private String getPackageName(int uid) {
-        String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
-        if (packages != null && packages.length > 0) {
-            return packages[0];
-        }
-        return null;
-    }
-
     private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
         @Override
         public void run() {
@@ -913,14 +909,13 @@
 
         public boolean sendMediaButton(String packageName, int pid, int uid,
                 boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
-            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
             try {
                 if (asSystemService) {
                     mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
-                            Process.SYSTEM_UID, mediaButtonIntent, sequenceId, cb);
+                            Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
                 } else {
-                    mCb.onMediaButton(packageName, pid, uid, mediaButtonIntent, sequenceId, cb);
+                    mCb.onMediaButton(packageName, pid, uid,
+                            createMediaButtonIntent(keyEvent), sequenceId, cb);
                 }
                 return true;
             } catch (RemoteException e) {
@@ -929,205 +924,239 @@
             return false;
         }
 
-        public void sendCommand(String packageName, int pid, int uid, String command, Bundle args,
-                ResultReceiver cb) {
+        public boolean sendMediaButton(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, boolean asSystemService,
+                KeyEvent keyEvent) {
             try {
-                mCb.onCommand(packageName, pid, uid, command, args, cb);
+                if (asSystemService) {
+                    mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
+                            Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null);
+                } else {
+                    mCb.onMediaButtonFromController(packageName, pid, uid, caller,
+                            createMediaButtonIntent(keyEvent));
+                }
+                return true;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
+            }
+            return false;
+        }
+
+        public void sendCommand(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) {
+            try {
+                mCb.onCommand(packageName, pid, uid, caller, command, args, cb);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in sendCommand.", e);
             }
         }
 
-        public void sendCustomAction(String packageName, int pid, int uid, String action,
+        public void sendCustomAction(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String action,
                 Bundle args) {
             try {
-                mCb.onCustomAction(packageName, pid, uid, action, args);
+                mCb.onCustomAction(packageName, pid, uid, caller, action, args);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in sendCustomAction.", e);
             }
         }
 
-        public void prepare(String packageName, int pid, int uid) {
+        public void prepare(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             try {
-                mCb.onPrepare(packageName, pid, uid);
+                mCb.onPrepare(packageName, pid, uid, caller);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in prepare.", e);
             }
         }
 
-        public void prepareFromMediaId(String packageName, int pid, int uid, String mediaId,
-                Bundle extras) {
+        public void prepareFromMediaId(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String mediaId, Bundle extras) {
             try {
-                mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras);
+                mCb.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in prepareFromMediaId.", e);
             }
         }
 
-        public void prepareFromSearch(String packageName, int pid, int uid, String query,
-                Bundle extras) {
+        public void prepareFromSearch(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String query, Bundle extras) {
             try {
-                mCb.onPrepareFromSearch(packageName, pid, uid, query, extras);
+                mCb.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in prepareFromSearch.", e);
             }
         }
 
-        public void prepareFromUri(String packageName, int pid, int uid, Uri uri,
-                Bundle extras) {
+        public void prepareFromUri(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, Uri uri, Bundle extras) {
             try {
-                mCb.onPrepareFromUri(packageName, pid, uid, uri, extras);
+                mCb.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in prepareFromUri.", e);
             }
         }
 
-        public void play(String packageName, int pid, int uid) {
+        public void play(String packageName, int pid, int uid, ISessionControllerCallback caller) {
             try {
-                mCb.onPlay(packageName, pid, uid);
+                mCb.onPlay(packageName, pid, uid, caller);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in play.", e);
             }
         }
 
-        public void playFromMediaId(String packageName, int pid, int uid, String mediaId,
-                Bundle extras) {
+        public void playFromMediaId(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String mediaId, Bundle extras) {
             try {
-                mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras);
+                mCb.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in playFromMediaId.", e);
             }
         }
 
-        public void playFromSearch(String packageName, int pid, int uid, String query,
-                Bundle extras) {
+        public void playFromSearch(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, String query, Bundle extras) {
             try {
-                mCb.onPlayFromSearch(packageName, pid, uid, query, extras);
+                mCb.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in playFromSearch.", e);
             }
         }
 
-        public void playFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
+        public void playFromUri(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, Uri uri, Bundle extras) {
             try {
-                mCb.onPlayFromUri(packageName, pid, uid, uri, extras);
+                mCb.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in playFromUri.", e);
             }
         }
 
-        public void skipToTrack(String packageName, int pid, int uid, long id) {
+        public void skipToTrack(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, long id) {
             try {
-                mCb.onSkipToTrack(packageName, pid, uid, id);
+                mCb.onSkipToTrack(packageName, pid, uid, caller, id);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in skipToTrack", e);
             }
         }
 
-        public void pause(String packageName, int pid, int uid) {
+        public void pause(String packageName, int pid, int uid, ISessionControllerCallback caller) {
             try {
-                mCb.onPause(packageName, pid, uid);
+                mCb.onPause(packageName, pid, uid, caller);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in pause.", e);
             }
         }
 
-        public void stop(String packageName, int pid, int uid) {
+        public void stop(String packageName, int pid, int uid, ISessionControllerCallback caller) {
             try {
-                mCb.onStop(packageName, pid, uid);
+                mCb.onStop(packageName, pid, uid, caller);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in stop.", e);
             }
         }
 
-        public void next(String packageName, int pid, int uid) {
+        public void next(String packageName, int pid, int uid, ISessionControllerCallback caller) {
             try {
-                mCb.onNext(packageName, pid, uid);
+                mCb.onNext(packageName, pid, uid, caller);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in next.", e);
             }
         }
 
-        public void previous(String packageName, int pid, int uid) {
+        public void previous(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             try {
-                mCb.onPrevious(packageName, pid, uid);
+                mCb.onPrevious(packageName, pid, uid, caller);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in previous.", e);
             }
         }
 
-        public void fastForward(String packageName, int pid, int uid) {
+        public void fastForward(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             try {
-                mCb.onFastForward(packageName, pid, uid);
+                mCb.onFastForward(packageName, pid, uid, caller);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in fastForward.", e);
             }
         }
 
-        public void rewind(String packageName, int pid, int uid) {
+        public void rewind(String packageName, int pid, int uid,
+                ISessionControllerCallback caller) {
             try {
-                mCb.onRewind(packageName, pid, uid);
+                mCb.onRewind(packageName, pid, uid, caller);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in rewind.", e);
             }
         }
 
-        public void seekTo(String packageName, int pid, int uid, long pos) {
+        public void seekTo(String packageName, int pid, int uid, ISessionControllerCallback caller,
+                long pos) {
             try {
-                mCb.onSeekTo(packageName, pid, uid, pos);
+                mCb.onSeekTo(packageName, pid, uid, caller, pos);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in seekTo.", e);
             }
         }
 
-        public void rate(String packageName, int pid, int uid, Rating rating) {
+        public void rate(String packageName, int pid, int uid, ISessionControllerCallback caller,
+                Rating rating) {
             try {
-                mCb.onRate(packageName, pid, uid, rating);
+                mCb.onRate(packageName, pid, uid, caller, rating);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in rate.", e);
             }
         }
 
-        public void adjustVolume(String packageName, int pid, int uid, boolean asSystemService,
-                int direction) {
+        public void adjustVolume(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, boolean asSystemService, int direction) {
             try {
                 if (asSystemService) {
                     mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(),
-                            Process.SYSTEM_UID, direction);
+                            Process.SYSTEM_UID, null, direction);
                 } else {
-                    mCb.onAdjustVolume(packageName, pid, uid, direction);
+                    mCb.onAdjustVolume(packageName, pid, uid, caller, direction);
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in adjustVolume.", e);
             }
         }
 
-        public void setVolumeTo(String packageName, int pid, int uid, int value) {
+        public void setVolumeTo(String packageName, int pid, int uid,
+                ISessionControllerCallback caller, int value) {
             try {
-                mCb.onSetVolumeTo(packageName, pid, uid, value);
+                mCb.onSetVolumeTo(packageName, pid, uid, caller, value);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in setVolumeTo.", e);
             }
         }
+
+        private Intent createMediaButtonIntent(KeyEvent keyEvent) {
+            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+            return mediaButtonIntent;
+        }
     }
 
     class ControllerStub extends ISessionController.Stub {
         @Override
-        public void sendCommand(String packageName, String command, Bundle args,
-                ResultReceiver cb) {
+        public void sendCommand(String packageName, ISessionControllerCallback caller,
+                String command, Bundle args, ResultReceiver cb) {
             mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
-                    command, args, cb);
+                    caller, command, args, cb);
         }
 
         @Override
-        public boolean sendMediaButton(String packageName, boolean asSystemService,
-                KeyEvent mediaButtonIntent) {
+        public boolean sendMediaButton(String packageName, ISessionControllerCallback cb,
+                boolean asSystemService, KeyEvent keyEvent) {
             return mSessionCb.sendMediaButton(packageName, Binder.getCallingPid(),
-                    Binder.getCallingUid(), asSystemService, mediaButtonIntent, 0, null);
+                    Binder.getCallingUid(), cb, asSystemService, keyEvent);
         }
 
         @Override
-        public void registerCallbackListener(ISessionControllerCallback cb) {
+        public void registerCallbackListener(String packageName, ISessionControllerCallback cb) {
             synchronized (mLock) {
                 // If this session is already destroyed tell the caller and
                 // don't add them.
@@ -1141,24 +1170,24 @@
                 }
                 if (getControllerHolderIndexForCb(cb) < 0) {
                     mControllerCallbackHolders.add(new ISessionControllerCallbackHolder(cb,
-                          Binder.getCallingUid()));
+                            packageName, Binder.getCallingUid()));
                     if (DEBUG) {
-                        Log.d(TAG, "registering controller callback " + cb);
+                        Log.d(TAG, "registering controller callback " + cb + " from controller"
+                                + packageName);
                     }
                 }
             }
         }
 
         @Override
-        public void unregisterCallbackListener(ISessionControllerCallback cb)
-                throws RemoteException {
+        public void unregisterCallbackListener(ISessionControllerCallback cb) {
             synchronized (mLock) {
                 int index = getControllerHolderIndexForCb(cb);
                 if (index != -1) {
                     mControllerCallbackHolders.remove(index);
                 }
                 if (DEBUG) {
-                    Log.d(TAG, "unregistering callback " + cb + ". index=" + index);
+                    Log.d(TAG, "unregistering callback " + cb.asBinder());
                 }
             }
         }
@@ -1204,13 +1233,13 @@
         }
 
         @Override
-        public void adjustVolume(String packageName, boolean asSystemService, int direction,
-                int flags) {
+        public void adjustVolume(String packageName, ISessionControllerCallback caller,
+                boolean asSystemService, int direction, int flags) {
             int pid = Binder.getCallingPid();
             int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaSessionRecord.this.adjustVolume(packageName, pid, uid, asSystemService,
+                MediaSessionRecord.this.adjustVolume(packageName, pid, uid, caller, asSystemService,
                         direction, flags, false /* useSuggested */);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -1218,115 +1247,128 @@
         }
 
         @Override
-        public void setVolumeTo(String packageName, int value, int flags) {
+        public void setVolumeTo(String packageName, ISessionControllerCallback caller,
+                int value, int flags) {
             int pid = Binder.getCallingPid();
             int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaSessionRecord.this.setVolumeTo(packageName, pid, uid, value, flags);
+                MediaSessionRecord.this.setVolumeTo(packageName, pid, uid, caller, value, flags);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override
-        public void prepare(String packageName) {
-            mSessionCb.prepare(packageName, Binder.getCallingPid(), Binder.getCallingUid());
+        public void prepare(String packageName, ISessionControllerCallback caller) {
+            mSessionCb.prepare(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void prepareFromMediaId(String packageName, String mediaId, Bundle extras) {
+        public void prepareFromMediaId(String packageName, ISessionControllerCallback caller,
+                String mediaId, Bundle extras) {
             mSessionCb.prepareFromMediaId(packageName, Binder.getCallingPid(),
-                    Binder.getCallingUid(), mediaId, extras);
+                    Binder.getCallingUid(), caller, mediaId, extras);
         }
 
         @Override
-        public void prepareFromSearch(String packageName, String query, Bundle extras) {
+        public void prepareFromSearch(String packageName, ISessionControllerCallback caller,
+                String query, Bundle extras) {
             mSessionCb.prepareFromSearch(packageName, Binder.getCallingPid(),
-                    Binder.getCallingUid(), query, extras);
+                    Binder.getCallingUid(), caller, query, extras);
         }
 
         @Override
-        public void prepareFromUri(String packageName, Uri uri, Bundle extras) {
+        public void prepareFromUri(String packageName, ISessionControllerCallback caller,
+                Uri uri, Bundle extras) {
             mSessionCb.prepareFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
-                    uri, extras);
+                    caller, uri, extras);
         }
 
         @Override
-        public void play(String packageName) {
-            mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid());
+        public void play(String packageName, ISessionControllerCallback caller) {
+            mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void playFromMediaId(String packageName, String mediaId, Bundle extras) {
+        public void playFromMediaId(String packageName, ISessionControllerCallback caller,
+                String mediaId, Bundle extras) {
             mSessionCb.playFromMediaId(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
-                    mediaId, extras);
+                    caller, mediaId, extras);
         }
 
         @Override
-        public void playFromSearch(String packageName, String query, Bundle extras) {
+        public void playFromSearch(String packageName, ISessionControllerCallback caller,
+                String query, Bundle extras) {
             mSessionCb.playFromSearch(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
-                    query, extras);
+                    caller, query, extras);
         }
 
         @Override
-        public void playFromUri(String packageName, Uri uri, Bundle extras) {
+        public void playFromUri(String packageName, ISessionControllerCallback caller,
+                Uri uri, Bundle extras) {
             mSessionCb.playFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
-                    uri, extras);
+                    caller, uri, extras);
         }
 
         @Override
-        public void skipToQueueItem(String packageName, long id) {
-            mSessionCb.skipToTrack(packageName, Binder.getCallingPid(), Binder.getCallingUid(), id);
+        public void skipToQueueItem(String packageName, ISessionControllerCallback caller,
+                long id) {
+            mSessionCb.skipToTrack(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
+                    caller, id);
         }
 
         @Override
-        public void pause(String packageName) {
-            mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid());
+        public void pause(String packageName, ISessionControllerCallback caller) {
+            mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void stop(String packageName) {
-            mSessionCb.stop(packageName, Binder.getCallingPid(), Binder.getCallingUid());
+        public void stop(String packageName, ISessionControllerCallback caller) {
+            mSessionCb.stop(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void next(String packageName) {
-            mSessionCb.next(packageName, Binder.getCallingPid(), Binder.getCallingUid());
+        public void next(String packageName, ISessionControllerCallback caller) {
+            mSessionCb.next(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void previous(String packageName) {
-            mSessionCb.previous(packageName, Binder.getCallingPid(), Binder.getCallingUid());
+        public void previous(String packageName, ISessionControllerCallback caller) {
+            mSessionCb.previous(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
+                    caller);
         }
 
         @Override
-        public void fastForward(String packageName) {
-            mSessionCb.fastForward(packageName, Binder.getCallingPid(), Binder.getCallingUid());
+        public void fastForward(String packageName, ISessionControllerCallback caller) {
+            mSessionCb.fastForward(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
+                    caller);
         }
 
         @Override
-        public void rewind(String packageName) {
-            mSessionCb.rewind(packageName, Binder.getCallingPid(), Binder.getCallingUid());
+        public void rewind(String packageName, ISessionControllerCallback caller) {
+            mSessionCb.rewind(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller);
         }
 
         @Override
-        public void seekTo(String packageName, long pos) {
-            mSessionCb.seekTo(packageName, Binder.getCallingPid(), Binder.getCallingUid(), pos);
+        public void seekTo(String packageName, ISessionControllerCallback caller, long pos) {
+            mSessionCb.seekTo(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller,
+                    pos);
         }
 
         @Override
-        public void rate(String packageName, Rating rating) {
-            mSessionCb.rate(packageName, Binder.getCallingPid(), Binder.getCallingUid(), rating);
+        public void rate(String packageName, ISessionControllerCallback caller, Rating rating) {
+            mSessionCb.rate(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller,
+                    rating);
         }
 
         @Override
-        public void sendCustomAction(String packageName, String action, Bundle args) {
+        public void sendCustomAction(String packageName, ISessionControllerCallback caller,
+                String action, Bundle args) {
             mSessionCb.sendCustomAction(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
-                    action, args);
+                    caller, action, args);
         }
 
-
         @Override
         public MediaMetadata getMetadata() {
             synchronized (mLock) {
@@ -1372,10 +1414,13 @@
     private class ISessionControllerCallbackHolder {
         private final ISessionControllerCallback mCallback;
         private final String mPackageName;
+        private final int mUid;
 
-        ISessionControllerCallbackHolder(ISessionControllerCallback callback, int uid) {
+        ISessionControllerCallbackHolder(ISessionControllerCallback callback, String packageName,
+                int uid) {
             mCallback = callback;
-            mPackageName = getPackageName(uid);
+            mPackageName = packageName;
+            mUid = uid;
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index a6e9389..6fff367 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1851,7 +1851,7 @@
                     }
                 });
             } else {
-                session.adjustVolume(packageName, pid, uid, asSystemService,
+                session.adjustVolume(packageName, pid, uid, null, asSystemService,
                         direction, flags, true);
             }
         }
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 9ef6c66..492c6d6 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -184,6 +184,8 @@
 
     private final PowerManager.WakeLock mWakeLock;
 
+    private final boolean mUseBpfTrafficStats;
+
     private IConnectivityManager mConnManager;
 
     @VisibleForTesting
@@ -347,6 +349,7 @@
         mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
         mSystemDir = checkNotNull(systemDir, "missing systemDir");
         mBaseDir = checkNotNull(baseDir, "missing baseDir");
+        mUseBpfTrafficStats = new File("/sys/fs/bpf/traffic_uid_stats_map").exists();
 
         LocalServices.addService(NetworkStatsManagerInternal.class,
                 new NetworkStatsManagerInternalImpl());
@@ -947,7 +950,7 @@
     }
 
     private boolean checkBpfStatsEnable() {
-        return new File("/sys/fs/bpf/traffic_uid_stats_map").exists();
+        return mUseBpfTrafficStats;
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7f141ee..61b5415 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -25,22 +25,18 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
-import android.content.pm.Signature;
 import android.metrics.LogMaker;
 import android.os.Build;
 import android.os.UserHandle;
-import android.print.PrintManager;
 import android.provider.Settings.Secure;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.RankingHelperProto;
 import android.service.notification.RankingHelperProto.RecordProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
@@ -156,6 +152,8 @@
             }
         }
 
+        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
         updateChannelsBypassingDnd();
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2650ef0..f4853f3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -14090,13 +14090,9 @@
     public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
             PersistableBundle appExtras, PersistableBundle launcherExtras, String dialogMessage,
             String callingPackage, int userId) {
-        try {
-            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS, null);
-        } catch (SecurityException e) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_USERS,
-                    "Callers need to have either " + Manifest.permission.SUSPEND_APPS + " or "
-                            + Manifest.permission.MANAGE_USERS);
-        }
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
+                "setPackagesSuspendedAsUser");
+
         final int callingUid = Binder.getCallingUid();
         if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
                 && getPackageUid(callingPackage, 0, userId) != callingUid) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 8905950..98c6ec4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -33,8 +33,10 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -115,7 +117,9 @@
     @Mock PackageManager mPm;
     @Mock IContentProvider mTestIContentProvider;
     @Mock Context mContext;
+    @Mock ZenModeHelper mMockZenModeHelper;
 
+    private NotificationManager.Policy mTestNotificationPolicy;
     private Notification mNotiGroupGSortA;
     private Notification mNotiGroupGSortB;
     private Notification mNotiNoGroup;
@@ -172,12 +176,12 @@
         when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
                 .thenReturn(SOUND_URI);
 
-        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
-        mHelper = new RankingHelper(getContext(), mPm, mHandler, mockZenModeHelper,
+        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+        mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mUsageStats, new String[] {ImportanceExtractor.class.getName()});
-
-        when(mockZenModeHelper.getNotificationPolicy()).thenReturn(new NotificationManager.Policy(
-                0, 0, 0));
+        resetZenModeHelper();
 
         mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("A")
@@ -299,6 +303,11 @@
         return null;
     }
 
+    private void resetZenModeHelper() {
+        reset(mMockZenModeHelper);
+        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+    }
+
     @Test
     public void testFindAfterRankingWithASplitGroup() throws Exception {
         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -1136,44 +1145,86 @@
     public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
         // create notification channel that can't bypass dnd
         // expected result: areChannelsBypassingDnd = false
+        // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
         NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
         mHelper.createNotificationChannel(PKG, UID, channel, true, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+        resetZenModeHelper();
 
-        //  create notification channel that can bypass dnd
+        // create notification channel that can bypass dnd
         // expected result: areChannelsBypassingDnd = true
         NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel2.setBypassDnd(true);
         mHelper.createNotificationChannel(PKG, UID, channel2, true, true);
         assertTrue(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
 
         // delete channels
         mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
         assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+        resetZenModeHelper();
+
         mHelper.deleteNotificationChannel(PKG, UID, channel2.getId());
         assertFalse(mHelper.areChannelsBypassingDnd());
-
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
     }
 
     @Test
     public void testUpdateCanChannelsBypassDnd() throws Exception {
         // create notification channel that can't bypass dnd
         // expected result: areChannelsBypassingDnd = false
+        // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
         NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
         mHelper.createNotificationChannel(PKG, UID, channel, true, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+        resetZenModeHelper();
 
         // update channel so it CAN bypass dnd:
         // expected result: areChannelsBypassingDnd = true
         channel.setBypassDnd(true);
         mHelper.updateNotificationChannel(PKG, UID, channel, true);
         assertTrue(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
 
         // update channel so it can't bypass dnd:
         // expected result: areChannelsBypassingDnd = false
         channel.setBypassDnd(false);
         mHelper.updateNotificationChannel(PKG, UID, channel, true);
         assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
+    }
+
+    @Test
+    public void testSetupNewZenModeHelper_canBypass() {
+        // start notification policy off with mAreChannelsBypassingDnd = true, but
+        // RankingHelper should change to false
+        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+        mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+                mUsageStats, new String[] {ImportanceExtractor.class.getName()});
+        assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+        resetZenModeHelper();
+    }
+
+    @Test
+    public void testSetupNewZenModeHelper_cannotBypass() {
+        // start notification policy off with mAreChannelsBypassingDnd = false
+        mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
+        when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+        mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+                mUsageStats, new String[] {ImportanceExtractor.class.getName()});
+        assertFalse(mHelper.areChannelsBypassingDnd());
+        verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+        resetZenModeHelper();
     }
 
     @Test