Merge "Fix FastDataOutput performance bug."
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 94a6576..d6b7b08 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -116,14 +116,14 @@
     method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
     method public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
     method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessionsForUser(@Nullable android.content.ComponentName, int);
-    method public void registerRemoteVolumeControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.RemoteVolumeControllerCallback);
-    method public void unregisterRemoteVolumeControllerCallback(@NonNull android.media.session.MediaSessionManager.RemoteVolumeControllerCallback);
+    method public void registerRemoteSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.RemoteSessionCallback);
+    method public void unregisterRemoteSessionCallback(@NonNull android.media.session.MediaSessionManager.RemoteSessionCallback);
     field public static final int RESULT_MEDIA_KEY_HANDLED = 1; // 0x1
     field public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0; // 0x0
   }
 
-  public static interface MediaSessionManager.RemoteVolumeControllerCallback {
-    method public void onSessionChanged(@Nullable android.media.session.MediaSession.Token);
+  public static interface MediaSessionManager.RemoteSessionCallback {
+    method public void onDefaultRemoteSessionChanged(@Nullable android.media.session.MediaSession.Token);
     method public void onVolumeChanged(@NonNull android.media.session.MediaSession.Token, int);
   }
 
diff --git a/core/java/android/os/image/OWNERS b/core/java/android/os/image/OWNERS
index 389b55b..08a51ff 100644
--- a/core/java/android/os/image/OWNERS
+++ b/core/java/android/os/image/OWNERS
@@ -1 +1,3 @@
+include /packages/DynamicSystemInstallationService/OWNERS
+
 [email protected]
diff --git a/core/java/android/provider/BaseColumns.java b/core/java/android/provider/BaseColumns.java
index b216e2b..667bb29 100644
--- a/core/java/android/provider/BaseColumns.java
+++ b/core/java/android/provider/BaseColumns.java
@@ -19,12 +19,16 @@
 public interface BaseColumns {
     /**
      * The unique ID for a row.
+     *
+     * <p>Type: INTEGER (long)</p>
      */
     // @Column(Cursor.FIELD_TYPE_INTEGER)
     public static final String _ID = "_id";
 
     /**
      * The count of rows in a directory.
+     *
+     * <p>Type: INTEGER</p>
      */
     // @Column(Cursor.FIELD_TYPE_INTEGER)
     public static final String _COUNT = "_count";
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 1ee2f19..92a1883 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -40,6 +40,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.RemoteException;
+import android.os.StrictMode;
 import android.text.format.DateUtils;
 import android.text.format.TimeMigrationUtils;
 import android.util.Log;
@@ -2619,8 +2620,14 @@
             intent.setData(ContentUris.withAppendedId(CalendarContract.CONTENT_URI, alarmTime));
             intent.putExtra(ALARM_TIME, alarmTime);
             intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+
+            // Disable strict mode VM policy violations temporarily for intents that contain a
+            // content URI but don't have FLAG_GRANT_READ_URI_PERMISSION.
+            StrictMode.VmPolicy oldVmPolicy = StrictMode.allowVmViolations();
             PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
                     PendingIntent.FLAG_IMMUTABLE);
+            StrictMode.setVmPolicy(oldVmPolicy);
+
             manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pi);
         }
 
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index c6c4ba8..1b1be43 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -40,7 +40,6 @@
 X(DrawDrawable)
 X(DrawPicture)
 X(DrawImage)
-X(DrawImageNine)
 X(DrawImageRect)
 X(DrawImageLattice)
 X(DrawTextBlob)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index a495ec4..a2028ca 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -319,23 +319,6 @@
     BitmapPalette palette;
     void draw(SkCanvas* c, const SkMatrix&) const { c->drawImage(image.get(), x, y, &paint); }
 };
-struct DrawImageNine final : Op {
-    static const auto kType = Type::DrawImageNine;
-    DrawImageNine(sk_sp<const SkImage>&& image, const SkIRect& center, const SkRect& dst,
-                  const SkPaint* paint)
-            : image(std::move(image)), center(center), dst(dst) {
-        if (paint) {
-            this->paint = *paint;
-        }
-    }
-    sk_sp<const SkImage> image;
-    SkIRect center;
-    SkRect dst;
-    SkPaint paint;
-    void draw(SkCanvas* c, const SkMatrix&) const {
-        c->drawImageNine(image.get(), center, dst, &paint);
-    }
-};
 struct DrawImageRect final : Op {
     static const auto kType = Type::DrawImageRect;
     DrawImageRect(sk_sp<const SkImage>&& image, const SkRect* src, const SkRect& dst,
@@ -633,10 +616,6 @@
                                 const SkPaint* paint, BitmapPalette palette) {
     this->push<DrawImage>(0, std::move(image), x, y, paint, palette);
 }
-void DisplayListData::drawImageNine(sk_sp<const SkImage> image, const SkIRect& center,
-                                    const SkRect& dst, const SkPaint* paint) {
-    this->push<DrawImageNine>(0, std::move(image), center, dst, paint);
-}
 void DisplayListData::drawImageRect(sk_sp<const SkImage> image, const SkRect* src,
                                     const SkRect& dst, const SkPaint* paint,
                                     SkCanvas::SrcRectConstraint constraint, BitmapPalette palette) {
@@ -944,10 +923,6 @@
                                   const SkPaint* paint) {
     fDL->drawImage(sk_ref_sp(img), x, y, paint, BitmapPalette::Unknown);
 }
-void RecordingCanvas::onDrawImageNine(const SkImage* img, const SkIRect& center, const SkRect& dst,
-                                      const SkPaint* paint) {
-    fDL->drawImageNine(sk_ref_sp(img), center, dst, paint);
-}
 void RecordingCanvas::onDrawImageRect(const SkImage* img, const SkRect* src, const SkRect& dst,
                                       const SkPaint* paint, SrcRectConstraint constraint) {
     fDL->drawImageRect(sk_ref_sp(img), src, dst, paint, constraint, BitmapPalette::Unknown);
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 4851148..9e2b0a9 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -188,7 +188,6 @@
 
     void onDrawImage(const SkImage*, SkScalar, SkScalar, const SkPaint*) override;
     void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, const SkPaint*) override;
-    void onDrawImageNine(const SkImage*, const SkIRect&, const SkRect&, const SkPaint*) override;
     void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*,
                          SrcRectConstraint) override;
 
diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
index 0eb526a..d5b46d5 100644
--- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h
+++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
@@ -90,11 +90,6 @@
         mOutput << mIdent << "drawImage" << std::endl;
     }
 
-    void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst,
-                         const SkPaint*) override {
-        mOutput << mIdent << "drawImageNine" << std::endl;
-    }
-
     void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*,
                          SrcRectConstraint) override {
         mOutput << mIdent << "drawImageRect" << std::endl;
diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h
index 594afd0..40b5747 100644
--- a/libs/hwui/tests/common/CallCountingCanvas.h
+++ b/libs/hwui/tests/common/CallCountingCanvas.h
@@ -119,12 +119,6 @@
         drawImageRectCount++;
     }
 
-    int drawImageNineCount = 0;
-    void onDrawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst,
-                         const SkPaint* paint) override {
-        drawImageNineCount++;
-    }
-
     int drawImageLatticeCount = 0;
     void onDrawImageLattice(const SkImage* image, const SkCanvas::Lattice& lattice,
                             const SkRect& dst, const SkPaint* paint) override {
@@ -171,4 +165,4 @@
 
 } /* namespace test */
 } /* namespace uirenderer */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h
index 76ae0853..8467be9 100644
--- a/libs/hwui/tests/unit/FatalTestCanvas.h
+++ b/libs/hwui/tests/unit/FatalTestCanvas.h
@@ -74,9 +74,6 @@
                          SrcRectConstraint) {
         ADD_FAILURE() << "onDrawImageRect not expected in this test";
     }
-    void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, const SkPaint*) {
-        ADD_FAILURE() << "onDrawImageNine not expected in this test";
-    }
     void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst,
                             const SkPaint*) {
         ADD_FAILURE() << "onDrawImageLattice not expected in this test";
diff --git a/media/java/android/media/IRemoteVolumeControllerCallback.aidl b/media/java/android/media/IRemoteSessionCallback.aidl
similarity index 95%
rename from media/java/android/media/IRemoteVolumeControllerCallback.aidl
rename to media/java/android/media/IRemoteSessionCallback.aidl
index 34c6361..e16c87e 100644
--- a/media/java/android/media/IRemoteVolumeControllerCallback.aidl
+++ b/media/java/android/media/IRemoteSessionCallback.aidl
@@ -25,7 +25,7 @@
  * TODO add in better support for multiple remote sessions.
  * @hide
  */
-oneway interface IRemoteVolumeControllerCallback {
+oneway interface IRemoteSessionCallback {
     void onVolumeChanged(in MediaSession.Token sessionToken, int flags);
     // sets the default session to use with the slider, replaces remoteSliderVisibility
     // on IVolumeController
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index d0304f2..36f7bed 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -23,9 +23,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.app.ActivityThread;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -102,7 +99,6 @@
 
         RouteInfo mDefaultAudioVideo;
         RouteInfo mBluetoothA2dpRoute;
-        volatile boolean mHasActiveBluetoothDevices;
 
         RouteInfo mSelectedRoute;
 
@@ -176,20 +172,14 @@
                     new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
             appContext.registerReceiver(new VolumeChangeReceiver(),
                     new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
-            IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
-            intentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
-            appContext.registerReceiver(new BluetoothStateChangedReceiver(), intentFilter);
 
             mDisplayService.registerDisplayListener(this, mHandler);
 
             AudioRoutesInfo newAudioRoutes = null;
             try {
                 newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
-                mHasActiveBluetoothDevices = mAudioService.isBluetoothA2dpOn();
             } catch (RemoteException e) {
             }
-
             if (newAudioRoutes != null) {
                 // This will select the active BT route if there is one and the current
                 // selected route is the default system route, or if there is no selected
@@ -263,8 +253,7 @@
             }
 
             if (audioRoutesChanged) {
-                Log.v(TAG, "Audio routes updated: " + newRoutes + ", hasActiveBTDevices="
-                        + mHasActiveBluetoothDevices);
+                Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn());
                 if (mSelectedRoute == null || mSelectedRoute == mDefaultAudioVideo
                         || mSelectedRoute == mBluetoothA2dpRoute) {
                     if (forceUseDefaultRoute || mBluetoothA2dpRoute == null) {
@@ -288,6 +277,15 @@
             return mStreamVolume.get(streamType);
         }
 
+        boolean isBluetoothA2dpOn() {
+            try {
+                return mBluetoothA2dpRoute != null && mAudioService.isBluetoothA2dpOn();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error querying Bluetooth A2DP state", e);
+                return false;
+            }
+        }
+
         void updateDiscoveryRequest() {
             // What are we looking for today?
             int routeTypes = 0;
@@ -396,7 +394,7 @@
         }
 
         void updateSelectedRouteForId(String routeId) {
-            RouteInfo selectedRoute = sStatic.mHasActiveBluetoothDevices
+            RouteInfo selectedRoute = isBluetoothA2dpOn()
                     ? mBluetoothA2dpRoute : mDefaultAudioVideo;
             final int count = mRoutes.size();
             for (int i = 0; i < count; i++) {
@@ -1045,7 +1043,7 @@
         Log.v(TAG, "Selecting route: " + route);
         assert(route != null);
         final RouteInfo oldRoute = sStatic.mSelectedRoute;
-        final RouteInfo currentSystemRoute = sStatic.mHasActiveBluetoothDevices
+        final RouteInfo currentSystemRoute = sStatic.isBluetoothA2dpOn()
                 ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo;
         boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
                 || oldRoute == sStatic.mBluetoothA2dpRoute);
@@ -1108,8 +1106,7 @@
 
     static void selectDefaultRouteStatic() {
         // TODO: Be smarter about the route types here; this selects for all valid.
-        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
-                && sStatic.mHasActiveBluetoothDevices) {
+        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute && sStatic.isBluetoothA2dpOn()) {
             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
         } else {
             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
@@ -1446,8 +1443,13 @@
         if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
                 selectedRoute == sStatic.mDefaultAudioVideo) {
             dispatchRouteVolumeChanged(selectedRoute);
-        } else if (sStatic.mHasActiveBluetoothDevices) {
-            dispatchRouteVolumeChanged(sStatic.mBluetoothA2dpRoute);
+        } else if (sStatic.mBluetoothA2dpRoute != null) {
+            try {
+                dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
+                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
+            }
         } else {
             dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
         }
@@ -3170,17 +3172,4 @@
             }
         }
     }
-
-    static class BluetoothStateChangedReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
-                case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
-                    sStatic.mHasActiveBluetoothDevices =
-                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) != null;
-                    break;
-            }
-        }
-    }
 }
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index f157d7f..66d5794 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -17,7 +17,7 @@
 
 import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
-import android.media.IRemoteVolumeControllerCallback;
+import android.media.IRemoteSessionCallback;
 import android.media.Session2Token;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.IOnMediaKeyEventDispatchedListener;
@@ -57,8 +57,8 @@
     void addSession2TokensListener(in ISession2TokensListener listener, int userId);
     void removeSession2TokensListener(in ISession2TokensListener listener);
 
-    void registerRemoteVolumeControllerCallback(in IRemoteVolumeControllerCallback rvc);
-    void unregisterRemoteVolumeControllerCallback(in IRemoteVolumeControllerCallback rvc);
+    void registerRemoteSessionCallback(in IRemoteSessionCallback rvc);
+    void unregisterRemoteSessionCallback(in IRemoteSessionCallback rvc);
 
     // For PhoneWindowManager to precheck media keys
     boolean isGlobalPriorityActive();
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 00ee914..f22bcd8 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -27,10 +27,11 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.media.AudioManager;
-import android.media.IRemoteVolumeControllerCallback;
+import android.media.IRemoteSessionCallback;
 import android.media.MediaFrameworkPlatformInitializer;
 import android.media.MediaSession2;
 import android.media.Session2Token;
+import android.media.VolumeProvider;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -87,8 +88,8 @@
     private final OnMediaKeyEventSessionChangedListenerStub
             mOnMediaKeyEventSessionChangedListenerStub =
             new OnMediaKeyEventSessionChangedListenerStub();
-    private final RemoteVolumeControllerCallbackStub mRemoteVolumeControllerCallbackStub =
-            new RemoteVolumeControllerCallbackStub();
+    private final RemoteSessionCallbackStub mRemoteSessionCallbackStub =
+            new RemoteSessionCallbackStub();
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
@@ -108,8 +109,8 @@
     @GuardedBy("mLock")
     private MediaSession.Token mCurMediaKeyEventSession;
     @GuardedBy("mLock")
-    private final Map<RemoteVolumeControllerCallback, Executor>
-            mRemoteVolumeControllerCallbacks = new ArrayMap<>();
+    private final Map<RemoteSessionCallback, Executor>
+            mRemoteSessionCallbacks = new ArrayMap<>();
 
     private Context mContext;
     private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
@@ -482,27 +483,29 @@
      * Set the remote volume controller callback to receive volume updates on.
      * Only for use by System UI and Settings application.
      *
+     * @param executor The executor on which the callback should be invoked
      * @param callback The volume controller callback to receive updates on.
+     *
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public void registerRemoteVolumeControllerCallback(
+    public void registerRemoteSessionCallback(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull RemoteVolumeControllerCallback callback) {
+            @NonNull RemoteSessionCallback callback) {
         Objects.requireNonNull(executor, "executor shouldn't be null");
         Objects.requireNonNull(callback, "callback shouldn't be null");
         boolean shouldRegisterCallback = false;
         synchronized (mLock) {
-            int prevCallbackCount = mRemoteVolumeControllerCallbacks.size();
-            mRemoteVolumeControllerCallbacks.put(callback, executor);
-            if (prevCallbackCount == 0 && mRemoteVolumeControllerCallbacks.size() == 1) {
+            int prevCallbackCount = mRemoteSessionCallbacks.size();
+            mRemoteSessionCallbacks.put(callback, executor);
+            if (prevCallbackCount == 0 && mRemoteSessionCallbacks.size() == 1) {
                 shouldRegisterCallback = true;
             }
         }
         if (shouldRegisterCallback) {
             try {
-                mService.registerRemoteVolumeControllerCallback(
-                        mRemoteVolumeControllerCallbackStub);
+                mService.registerRemoteSessionCallback(
+                        mRemoteSessionCallbackStub);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to register remote volume controller callback", e);
             }
@@ -511,27 +514,27 @@
 
     /**
      * Unregisters the remote volume controller callback which was previously registered with
-     * {@link #registerRemoteVolumeControllerCallback(Executor, RemoteVolumeControllerCallback)}.
+     * {@link #registerRemoteSessionCallback(Executor, RemoteSessionCallback)}.
      * Only for use by System UI and Settings application.
      *
      * @param callback The volume controller callback to receive updates on.
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public void unregisterRemoteVolumeControllerCallback(
-            @NonNull RemoteVolumeControllerCallback callback) {
+    public void unregisterRemoteSessionCallback(
+            @NonNull RemoteSessionCallback callback) {
         Objects.requireNonNull(callback, "callback shouldn't be null");
         boolean shouldUnregisterCallback = false;
         synchronized (mLock) {
-            if (mRemoteVolumeControllerCallbacks.remove(callback) != null
-                    && mRemoteVolumeControllerCallbacks.size() == 0) {
+            if (mRemoteSessionCallbacks.remove(callback) != null
+                    && mRemoteSessionCallbacks.size() == 0) {
                 shouldUnregisterCallback = true;
             }
         }
         try {
             if (shouldUnregisterCallback) {
-                mService.unregisterRemoteVolumeControllerCallback(
-                        mRemoteVolumeControllerCallbackStub);
+                mService.unregisterRemoteSessionCallback(
+                        mRemoteSessionCallbackStub);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to unregister remote volume controller callback", e);
@@ -1107,26 +1110,34 @@
     }
 
     /**
-     * Callback to receive changes in the remote volume controller.
+     * Callback to receive changes in the existing remote sessions. A remote session is a
+     * {@link MediaSession} that is connected to a remote player via
+     * {@link MediaSession#setPlaybackToRemote(VolumeProvider)}
      *
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public interface RemoteVolumeControllerCallback {
+    public interface RemoteSessionCallback {
         /**
-         * Called when the volume is changed.
+         * Called when the volume is changed for the given session. Flags that are defined in
+         * {@link AudioManager} will also be sent and will contain information about how to
+         * handle the volume change. For example, {@link AudioManager#FLAG_SHOW_UI} indicates that a
+         * toast showing the volume should be shown.
          *
          * @param sessionToken the remote media session token
-         * @param flags any of the flags from {@link AudioManager}
+         * @param flags extra information about how to handle the volume change
          */
         void onVolumeChanged(@NonNull MediaSession.Token sessionToken, int flags);
 
         /**
-         * Called when the session for the default remote controller is changed.
+         * Called when the default remote session is changed where the default remote session
+         * denotes an active remote session that has the highest priority for receiving key events.
+         * Null will be sent if there are currently no active remote sessions.
          *
-         * @param sessionToken the remote media session token
+         * @param sessionToken the token of the default remote session, a session with the highest
+         *                     priority for receiving key events.
          */
-        void onSessionChanged(@Nullable MediaSession.Token sessionToken);
+        void onDefaultRemoteSessionChanged(@Nullable MediaSession.Token sessionToken);
     }
 
     /**
@@ -1362,27 +1373,27 @@
         }
     }
 
-    private final class RemoteVolumeControllerCallbackStub
-            extends IRemoteVolumeControllerCallback.Stub {
+    private final class RemoteSessionCallbackStub
+            extends IRemoteSessionCallback.Stub {
         @Override
         public void onVolumeChanged(MediaSession.Token sessionToken, int flags) {
-            Map<RemoteVolumeControllerCallback, Executor> callbacks = new ArrayMap<>();
+            Map<RemoteSessionCallback, Executor> callbacks = new ArrayMap<>();
             synchronized (mLock) {
-                callbacks.putAll(mRemoteVolumeControllerCallbacks);
+                callbacks.putAll(mRemoteSessionCallbacks);
             }
-            for (Map.Entry<RemoteVolumeControllerCallback, Executor> e : callbacks.entrySet()) {
+            for (Map.Entry<RemoteSessionCallback, Executor> e : callbacks.entrySet()) {
                 e.getValue().execute(() -> e.getKey().onVolumeChanged(sessionToken, flags));
             }
         }
 
         @Override
         public void onSessionChanged(MediaSession.Token sessionToken) {
-            Map<RemoteVolumeControllerCallback, Executor> callbacks = new ArrayMap<>();
+            Map<RemoteSessionCallback, Executor> callbacks = new ArrayMap<>();
             synchronized (mLock) {
-                callbacks.putAll(mRemoteVolumeControllerCallbacks);
+                callbacks.putAll(mRemoteSessionCallbacks);
             }
-            for (Map.Entry<RemoteVolumeControllerCallback, Executor> e : callbacks.entrySet()) {
-                e.getValue().execute(() -> e.getKey().onSessionChanged(sessionToken));
+            for (Map.Entry<RemoteSessionCallback, Executor> e : callbacks.entrySet()) {
+                e.getValue().execute(() -> e.getKey().onDefaultRemoteSessionChanged(sessionToken));
             }
         }
     }
diff --git a/packages/DynamicSystemInstallationService/OWNERS b/packages/DynamicSystemInstallationService/OWNERS
index 60910c4..c1b7ec4 100644
--- a/packages/DynamicSystemInstallationService/OWNERS
+++ b/packages/DynamicSystemInstallationService/OWNERS
@@ -1,3 +1,3 @@
[email protected]
 [email protected]
 [email protected]
[email protected]
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
index 12ef639..fbf8a2f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
@@ -33,7 +33,7 @@
 import android.media.session.MediaSession.Token;
 import android.media.session.MediaSessionManager;
 import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
-import android.media.session.MediaSessionManager.RemoteVolumeControllerCallback;
+import android.media.session.MediaSessionManager.RemoteSessionCallback;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.Handler;
@@ -100,8 +100,8 @@
         mMgr.addOnActiveSessionsChangedListener(mSessionsListener, null, mHandler);
         mInit = true;
         postUpdateSessions();
-        mMgr.registerRemoteVolumeControllerCallback(mHandlerExecutor,
-                mRemoteVolumeControllerCallback);
+        mMgr.registerRemoteSessionCallback(mHandlerExecutor,
+                mRemoteSessionCallback);
     }
 
     protected void postUpdateSessions() {
@@ -116,7 +116,7 @@
         if (D.BUG) Log.d(TAG, "destroy");
         mInit = false;
         mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
-        mMgr.unregisterRemoteVolumeControllerCallback(mRemoteVolumeControllerCallback);
+        mMgr.unregisterRemoteSessionCallback(mRemoteSessionCallback);
     }
 
     /**
@@ -142,11 +142,11 @@
         mCallbacks.onRemoteVolumeChanged(token, flags);
     }
 
-    private void onUpdateRemoteControllerH(Token sessionToken) {
+    private void onUpdateRemoteSessionListH(Token sessionToken) {
         final MediaController controller =
                 sessionToken != null ? new MediaController(mContext, sessionToken) : null;
         final String pkg = controller != null ? controller.getPackageName() : null;
-        if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
+        if (D.BUG) Log.d(TAG, "onUpdateRemoteSessionListH " + pkg);
         // this may be our only indication that a remote session is changed, refresh
         postUpdateSessions();
     }
@@ -336,8 +336,8 @@
                 }
             };
 
-    private final RemoteVolumeControllerCallback mRemoteVolumeControllerCallback =
-            new RemoteVolumeControllerCallback() {
+    private final RemoteSessionCallback mRemoteSessionCallback =
+            new RemoteSessionCallback() {
                 @Override
                 public void onVolumeChanged(@NonNull MediaSession.Token sessionToken,
                         int flags) {
@@ -346,15 +346,17 @@
                 }
 
                 @Override
-                public void onSessionChanged(@Nullable MediaSession.Token sessionToken) {
-                    mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget();
+                public void onDefaultRemoteSessionChanged(
+                        @Nullable MediaSession.Token sessionToken) {
+                    mHandler.obtainMessage(H.UPDATE_REMOTE_SESSION_LIST,
+                            sessionToken).sendToTarget();
                 }
     };
 
     private final class H extends Handler {
         private static final int UPDATE_SESSIONS = 1;
         private static final int REMOTE_VOLUME_CHANGED = 2;
-        private static final int UPDATE_REMOTE_CONTROLLER = 3;
+        private static final int UPDATE_REMOTE_SESSION_LIST = 3;
 
         private H(Looper looper) {
             super(looper);
@@ -369,8 +371,8 @@
                 case REMOTE_VOLUME_CHANGED:
                     onRemoteVolumeChangedH((Token) msg.obj, msg.arg1);
                     break;
-                case UPDATE_REMOTE_CONTROLLER:
-                    onUpdateRemoteControllerH((Token) msg.obj);
+                case UPDATE_REMOTE_SESSION_LIST:
+                    onUpdateRemoteSessionListH((Token) msg.obj);
                     break;
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 3fafa5c..ea60f0d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -375,6 +375,17 @@
     }
 
     /**
+     * @return true if the current bouncer is password
+     */
+    public boolean dispatchBackKeyEventPreIme() {
+        if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
+                == SecurityMode.Password) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Allows the media keys to work when the keyguard is showing.
      * The media keys should be of no interest to the actual keyguard view(s),
      * so intercepting them here should not be of any harm.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index d42a53c..d58b95c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -43,6 +43,10 @@
 
     abstract CharSequence getTitle();
 
+    void animateForIme(float interpolatedFraction) {
+        return;
+    }
+
     boolean disallowInterceptTouch(MotionEvent event) {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index aaa5efe..92b65b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -136,24 +136,14 @@
 
     @Override
     public void startAppearAnimation() {
+        // Reset state, and let IME animation reveal the view as it slides in
         setAlpha(0f);
         setTranslationY(0f);
-        animate()
-                .alpha(1)
-                .withLayer()
-                .setDuration(300)
-                .setInterpolator(mLinearOutSlowInInterpolator);
     }
 
     @Override
-    public boolean startDisappearAnimation(Runnable finishRunnable) {
-        animate()
-                .alpha(0f)
-                .translationY(mDisappearYTranslation)
-                .setInterpolator(mFastOutLinearInInterpolator)
-                .setDuration(100)
-                .withEndAction(finishRunnable);
-        return true;
+    public void animateForIme(float interpolatedFraction) {
+        setAlpha(interpolatedFraction);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 5e33917..0f1c3c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -194,14 +194,18 @@
     @Override
     public void onResume(int reason) {
         super.onResume(reason);
-        // Wait a bit to focus the field so the focusable flag on the window is already set then.
-        mMainExecutor.execute(() -> {
-            if (mView.isShown() && mPasswordEntry.isEnabled()) {
-                mPasswordEntry.requestFocus();
-                if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
-                    mInputMethodManager.showSoftInput(
-                            mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
-                }
+
+        mPasswordEntry.requestFocus();
+        if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
+            showInput();
+        }
+    }
+
+    private void showInput() {
+        mPasswordEntry.post(() -> {
+            if (mPasswordEntry.isFocused() && mView.isShown()) {
+                mInputMethodManager.showSoftInput(
+                        mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index b62ea6b..42f3cc7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -119,21 +119,24 @@
                 @Override
                 public WindowInsets onProgress(WindowInsets windowInsets,
                         List<WindowInsetsAnimation> list) {
-                    int translationY = 0;
                     if (mDisappearAnimRunning) {
                         mSecurityViewFlipper.setTranslationY(
                                 mInitialBounds.bottom - mFinalBounds.bottom);
                     } else {
+                        int translationY = 0;
+                        float interpolatedFraction = 1f;
                         for (WindowInsetsAnimation animation : list) {
                             if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
                                 continue;
                             }
+                            interpolatedFraction = animation.getInterpolatedFraction();
+
                             final int paddingBottom = (int) MathUtils.lerp(
                                     mInitialBounds.bottom - mFinalBounds.bottom, 0,
-                                    animation.getInterpolatedFraction());
+                                    interpolatedFraction);
                             translationY += paddingBottom;
                         }
-                        mSecurityViewFlipper.setTranslationY(translationY);
+                        mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction);
                     }
                     return windowInsets;
                 }
@@ -141,7 +144,7 @@
                 @Override
                 public void onEnd(WindowInsetsAnimation animation) {
                     if (!mDisappearAnimRunning) {
-                        mSecurityViewFlipper.setTranslationY(0);
+                        mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f);
                     }
                 }
             };
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index b8439af..7773fe9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -83,6 +83,16 @@
         return "";
     }
 
+    /**
+      * Translate the entire view, and optionally inform the wrapped view of the progress
+      * so it can animate with the parent.
+      */
+    public void animateForIme(int translationY, float interpolatedFraction) {
+        super.setTranslationY(translationY);
+        KeyguardInputView v = getSecurityView();
+        if (v != null) v.animateForIme(interpolatedFraction);
+    }
+
     @Override
     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
         return p instanceof LayoutParams;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index db0713c..713daaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -451,10 +451,6 @@
         }
     }
 
-    public boolean onBackPressed() {
-        return mKeyguardViewController != null && mKeyguardViewController.handleBackKey();
-    }
-
     /**
      * @return True if and only if the security method should be shown before showing the
      * notifications on Keyguard, like SIM PIN/PUK.
@@ -494,6 +490,14 @@
         return mKeyguardViewController.interceptMediaKey(event);
     }
 
+    /**
+     * @return true if the pre IME back event should be handled
+     */
+    public boolean dispatchBackKeyEventPreIme() {
+        ensureView();
+        return mKeyguardViewController.dispatchBackKeyEventPreIme();
+    }
+
     public void notifyKeyguardAuthenticated(boolean strongAuth) {
         ensureView();
         mKeyguardViewController.finish(strongAuth, KeyguardUpdateMonitor.getCurrentUser());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
index a4fc3a3..619aadb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
@@ -162,6 +162,11 @@
         return mInteractionEventHandler.dispatchKeyEvent(event);
     }
 
+    @Override
+    public boolean dispatchKeyEventPreIme(KeyEvent event) {
+        return mInteractionEventHandler.dispatchKeyEventPreIme(event);
+    }
+
     protected void setInteractionEventHandler(InteractionEventHandler listener) {
         mInteractionEventHandler = listener;
     }
@@ -361,6 +366,8 @@
         boolean interceptMediaKey(KeyEvent event);
 
         boolean dispatchKeyEvent(KeyEvent event);
+
+        boolean dispatchKeyEventPreIme(KeyEvent event);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 2ac9f30..5595ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -348,6 +348,11 @@
             }
 
             @Override
+            public boolean dispatchKeyEventPreIme(KeyEvent event) {
+                return mService.dispatchKeyEventPreIme(event);
+            }
+
+            @Override
             public boolean dispatchKeyEvent(KeyEvent event) {
                 boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
                 switch (event.getKeyCode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a18d87c..a991d3615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3520,6 +3520,23 @@
                 && mStatusBarKeyguardViewManager.interceptMediaKey(event);
     }
 
+    /**
+     * While IME is active and a BACK event is detected, check with
+     * {@link StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme(KeyEvent)} to see if the event
+     * should be handled before routing to IME, in order to prevent the user having to hit back
+     * twice to exit bouncer.
+     */
+    public boolean dispatchKeyEventPreIme(KeyEvent event) {
+        switch (event.getKeyCode()) {
+            case KeyEvent.KEYCODE_BACK:
+                if (mState == StatusBarState.KEYGUARD
+                        && mStatusBarKeyguardViewManager.dispatchBackKeyEventPreIme()) {
+                    return onBackPressed();
+                }
+        }
+        return false;
+    }
+
     protected boolean shouldUnlockOnMenuPressed() {
         return mDeviceInteractive && mState != StatusBarState.SHADE
             && mStatusBarKeyguardViewManager.shouldDismissOnMenuPressed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 055b78a..b4c687d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -955,6 +955,13 @@
         return mBouncer.interceptMediaKey(event);
     }
 
+    /**
+     * @return true if the pre IME back event should be handled
+     */
+    public boolean dispatchBackKeyEventPreIme() {
+        return mBouncer.dispatchBackKeyEventPreIme();
+    }
+
     public void readyForKeyguardDone() {
         mViewMediatorCallback.readyForKeyguardDone();
     }
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index a10764b..046d927 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -22,6 +22,7 @@
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
 per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS
 per-file ConnectivityService.java = file:/services/core/java/com/android/server/net/OWNERS
+per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
 per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
 per-file IpSecService.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file MmsServiceBroker.java = file:/telephony/OWNERS
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 371fd3d..16695d1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -22,12 +22,19 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.database.ContentObserver;
 import android.hardware.hdmi.HdmiControlManager;
+import android.net.Uri;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.Settings.Global;
+import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -88,6 +95,23 @@
     @Nullable private final CecSettings mSystemConfig;
     @Nullable private final CecSettings mVendorOverride;
 
+    private final ArrayMap<Setting, Set<SettingChangeListener>>
+            mSettingChangeListeners = new ArrayMap<>();
+
+    private SettingsObserver mSettingsObserver;
+
+    /**
+     * Listener used to get notifications when value of a setting changes.
+     */
+    public interface SettingChangeListener {
+        /**
+         * Called when value of a setting changes.
+         *
+         * @param setting name of a CEC setting that changed
+         */
+        void onChange(@NonNull @CecSettingName String setting);
+    }
+
     /**
      * Setting storage input/output helper class.
      */
@@ -159,6 +183,18 @@
         }
     }
 
+    private class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            String setting = uri.getLastPathSegment();
+            HdmiCecConfig.this.notifyGlobalSettingChanged(setting);
+        }
+    }
+
     @VisibleForTesting
     HdmiCecConfig(@NonNull Context context,
                   @NonNull StorageAdapter storageAdapter,
@@ -311,6 +347,7 @@
         } else if (storage == STORAGE_SHARED_PREFS) {
             Slog.d(TAG, "Setting '" + storageKey + "' shared pref.");
             mStorageAdapter.storeSharedPref(storageKey, value);
+            notifySettingChanged(setting);
         }
     }
 
@@ -318,6 +355,103 @@
         return Integer.decode(value.getIntValue());
     }
 
+    private void notifyGlobalSettingChanged(String setting) {
+        switch (setting) {
+            case Global.HDMI_CONTROL_ENABLED:
+                notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
+                break;
+            case Global.HDMI_CEC_VERSION:
+                notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION);
+                break;
+            case Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP:
+                notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
+                break;
+        }
+    }
+
+    private void notifySettingChanged(@NonNull @CecSettingName String name) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        notifySettingChanged(setting);
+    }
+
+    private void notifySettingChanged(@NonNull Setting setting) {
+        Set<SettingChangeListener> listeners = mSettingChangeListeners.get(setting);
+        if (listeners == null) {
+            return;  // No listeners registered, do nothing.
+        }
+        for (SettingChangeListener listener: listeners) {
+            listener.onChange(setting.getName());
+        }
+    }
+
+    /**
+     * This method registers Global Setting change observer.
+     * Needs to be called once after initialization of HdmiCecConfig.
+     */
+    public void registerGlobalSettingsObserver(Looper looper) {
+        Handler handler = new Handler(looper);
+        mSettingsObserver = new SettingsObserver(handler);
+        ContentResolver resolver = mContext.getContentResolver();
+        String[] settings = new String[] {
+                Global.HDMI_CONTROL_ENABLED,
+                Global.HDMI_CEC_VERSION,
+                Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
+        };
+        for (String setting: settings) {
+            resolver.registerContentObserver(Global.getUriFor(setting), false,
+                                             mSettingsObserver, UserHandle.USER_ALL);
+        }
+    }
+
+    /**
+     * This method unregisters Global Setting change observer.
+     */
+    public void unregisterGlobalSettingsObserver() {
+        ContentResolver resolver = mContext.getContentResolver();
+        resolver.unregisterContentObserver(mSettingsObserver);
+    }
+
+    /**
+     * Register change listener for a given setting name.
+     */
+    public void registerChangeListener(@NonNull @CecSettingName String name,
+                                       SettingChangeListener listener) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        @Storage int storage = getStorage(setting);
+        if (storage != STORAGE_GLOBAL_SETTINGS && storage != STORAGE_SHARED_PREFS) {
+            throw new IllegalArgumentException("Change listeners for setting '" + name
+                    + "' not supported.");
+        }
+        if (!mSettingChangeListeners.containsKey(setting)) {
+            mSettingChangeListeners.put(setting, new HashSet<>());
+        }
+        mSettingChangeListeners.get(setting).add(listener);
+    }
+
+    /**
+     * Remove change listener for a given setting name.
+     */
+    public void removeChangeListener(@NonNull @CecSettingName String name,
+                                     SettingChangeListener listener) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        if (mSettingChangeListeners.containsKey(setting)) {
+            Set<SettingChangeListener> listeners = mSettingChangeListeners.get(setting);
+            listeners.remove(listener);
+            if (listeners.isEmpty()) {
+                mSettingChangeListeners.remove(setting);
+            }
+        }
+    }
+
     /**
      * Returns a list of all settings based on the XML metadata.
      */
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c15fdca..b78954dcb 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -498,6 +498,7 @@
         if (mMessageValidator == null) {
             mMessageValidator = new HdmiCecMessageValidator(this);
         }
+        mHdmiCecConfig.registerGlobalSettingsObserver(mIoLooper);
     }
 
     private void bootCompleted() {
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index 98ebec2c..ab64f97 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
@@ -129,14 +130,17 @@
     @NonNull private final Context mContext;
 
     /**
-     * The {@link ThreadingDomain} used to supply the {@link android.os.Handler} and shared lock
-     * object used by the controller and related components.
+     * The {@link ThreadingDomain} used to supply the shared lock object used by the controller and
+     * related components.
      *
      * <p>Most operations are executed on the associated handler thread <em>but not all</em>, hence
      * the requirement for additional synchronization using a shared lock.
      */
     @NonNull private final ThreadingDomain mThreadingDomain;
 
+    /** A handler associated with the {@link #mThreadingDomain}. */
+    @NonNull private final Handler mHandler;
+
     /** The shared lock from {@link #mThreadingDomain}. */
     @NonNull private final Object mSharedLock;
 
@@ -146,7 +150,8 @@
 
     LocationTimeZoneManagerService(Context context) {
         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
-        mThreadingDomain = new HandlerThreadingDomain(FgThread.getHandler());
+        mHandler = FgThread.getHandler();
+        mThreadingDomain = new HandlerThreadingDomain(mHandler);
         mSharedLock = mThreadingDomain.getLockObject();
     }
 
@@ -193,6 +198,7 @@
         } else {
             proxy = new RealLocationTimeZoneProviderProxy(
                     mContext,
+                    mHandler,
                     mThreadingDomain,
                     PRIMARY_LOCATION_TIME_ZONE_SERVICE_ACTION,
                     R.bool.config_enablePrimaryLocationTimeZoneOverlay,
@@ -214,6 +220,7 @@
         } else {
             proxy = new RealLocationTimeZoneProviderProxy(
                     mContext,
+                    mHandler,
                     mThreadingDomain,
                     SECONDARY_LOCATION_TIME_ZONE_SERVICE_ACTION,
                     R.bool.config_enableSecondaryLocationTimeZoneOverlay,
diff --git a/services/core/java/com/android/server/location/timezone/OWNERS b/services/core/java/com/android/server/location/timezone/OWNERS
new file mode 100644
index 0000000..28aff18
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
[email protected]
+include /core/java/android/app/timedetector/OWNERS
diff --git a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
index 1bb5cec..6cc148a 100644
--- a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.service.timezone.ITimeZoneProvider;
@@ -43,20 +44,20 @@
 
     @NonNull private final ServiceWatcher mServiceWatcher;
 
-    @GuardedBy("mProxyLock")
+    @GuardedBy("mSharedLock")
     @Nullable private ManagerProxy mManagerProxy;
 
-    @GuardedBy("mProxyLock")
+    @GuardedBy("mSharedLock")
     @NonNull private TimeZoneProviderRequest mRequest;
 
     RealLocationTimeZoneProviderProxy(
-            @NonNull Context context, @NonNull ThreadingDomain threadingDomain,
-            @NonNull String action, int enableOverlayResId,
-            int nonOverlayPackageResId) {
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull ThreadingDomain threadingDomain, @NonNull String action,
+            int enableOverlayResId, int nonOverlayPackageResId) {
         super(context, threadingDomain);
         mManagerProxy = null;
         mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
-        mServiceWatcher = new ServiceWatcher(context, action, this::onBind, this::onUnbind,
+        mServiceWatcher = new ServiceWatcher(context, handler, action, this::onBind, this::onUnbind,
                 enableOverlayResId, nonOverlayPackageResId);
     }
 
@@ -76,21 +77,6 @@
     }
 
     private void onBind(IBinder binder, ComponentName componentName) {
-        processServiceWatcherCallbackOnThreadingDomainThread(() -> onBindOnHandlerThread(binder));
-    }
-
-    private void onUnbind() {
-        processServiceWatcherCallbackOnThreadingDomainThread(this::onUnbindOnHandlerThread);
-    }
-
-    private void processServiceWatcherCallbackOnThreadingDomainThread(@NonNull Runnable runnable) {
-        // For simplicity, this code just post()s the runnable to the mThreadingDomain Thread in all
-        // cases. This adds a delay if ServiceWatcher and ThreadingDomain happen to be using the
-        // same thread, but nothing here should be performance critical.
-        mThreadingDomain.post(runnable);
-    }
-
-    private void onBindOnHandlerThread(@NonNull IBinder binder) {
         mThreadingDomain.assertCurrentThread();
 
         ITimeZoneProvider provider = ITimeZoneProvider.Stub.asInterface(binder);
@@ -108,7 +94,7 @@
         }
     }
 
-    private void onUnbindOnHandlerThread() {
+    private void onUnbind() {
         mThreadingDomain.assertCurrentThread();
 
         synchronized (mSharedLock) {
@@ -129,7 +115,7 @@
         }
     }
 
-    @GuardedBy("mProxyLock")
+    @GuardedBy("mSharedLock")
     private void trySendCurrentRequest() {
         TimeZoneProviderRequest request = mRequest;
         mServiceWatcher.runOnBinder(binder -> {
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 8a13969..7afa81a 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -439,7 +439,6 @@
             }
         }
     }
-
     private class BluetoothBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 17ceb15..1b27ef4 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -43,7 +43,7 @@
 import android.content.pm.ParceledListSlice;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
-import android.media.IRemoteVolumeControllerCallback;
+import android.media.IRemoteSessionCallback;
 import android.media.Session2Token;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.IOnMediaKeyEventDispatchedListener;
@@ -142,7 +142,7 @@
 
     // Used to notify System UI and Settings when remote volume was changed.
     @GuardedBy("mLock")
-    final RemoteCallbackList<IRemoteVolumeControllerCallback> mRemoteVolumeControllers =
+    final RemoteCallbackList<IRemoteSessionCallback> mRemoteVolumeControllers =
             new RemoteCallbackList<>();
 
     private SessionPolicyProvider mCustomSessionPolicyProvider;
@@ -304,7 +304,7 @@
             MediaSession.Token token = session.getSessionToken();
             for (int i = size - 1; i >= 0; i--) {
                 try {
-                    IRemoteVolumeControllerCallback cb =
+                    IRemoteSessionCallback cb =
                             mRemoteVolumeControllers.getBroadcastItem(i);
                     cb.onVolumeChanged(token, flags);
                 } catch (Exception e) {
@@ -713,7 +713,7 @@
 
             for (int i = size - 1; i >= 0; i--) {
                 try {
-                    IRemoteVolumeControllerCallback cb =
+                    IRemoteSessionCallback cb =
                             mRemoteVolumeControllers.getBroadcastItem(i);
                     cb.onSessionChanged(token);
                 } catch (Exception e) {
@@ -1839,7 +1839,7 @@
         }
 
         @Override
-        public void registerRemoteVolumeControllerCallback(IRemoteVolumeControllerCallback rvc) {
+        public void registerRemoteSessionCallback(IRemoteSessionCallback rvc) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
@@ -1854,7 +1854,7 @@
         }
 
         @Override
-        public void unregisterRemoteVolumeControllerCallback(IRemoteVolumeControllerCallback rvc) {
+        public void unregisterRemoteSessionCallback(IRemoteSessionCallback rvc) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 8071672..966be99 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -255,6 +255,9 @@
      */
     private static final int MIN_CPU_TIME_PER_UID_FREQ = 10;
 
+    /** Number of entries in CpuCyclesPerUidCluster atom stored in an array for each cluster. */
+    private static final int CPU_CYCLES_PER_UID_CLUSTER_VALUES = 3;
+
     private final Object mThermalLock = new Object();
     @GuardedBy("mThermalLock")
     private IThermalService mThermalService;
@@ -447,6 +450,12 @@
                         synchronized (mCpuTimePerUidLock) {
                             return pullCpuTimePerUidLocked(atomTag, data);
                         }
+                    case FrameworkStatsLog.CPU_CYCLES_PER_UID_CLUSTER:
+                        // Use the same lock as CPU_TIME_PER_UID_FREQ because data is pulled from
+                        // the same source.
+                        synchronized (mCpuTimePerUidFreqLock) {
+                            return pullCpuCyclesPerUidClusterLocked(atomTag, data);
+                        }
                     case FrameworkStatsLog.CPU_TIME_PER_UID_FREQ:
                         synchronized (mCpuTimePerUidFreqLock) {
                             return pullCpuTimePerUidFreqLocked(atomTag, data);
@@ -785,6 +794,7 @@
         registerKernelWakelock();
         registerCpuTimePerFreq();
         registerCpuTimePerUid();
+        registerCpuCyclesPerUidCluster();
         registerCpuTimePerUidFreq();
         registerCpuActiveTime();
         registerCpuClusterTime();
@@ -1502,6 +1512,97 @@
         return StatsManager.PULL_SUCCESS;
     }
 
+    private void registerCpuCyclesPerUidCluster() {
+        int tagId = FrameworkStatsLog.CPU_CYCLES_PER_UID_CLUSTER;
+        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                .setAdditiveFields(new int[] {3, 4, 5})
+                .build();
+        mStatsManager.setPullAtomCallback(
+                tagId,
+                metadata,
+                DIRECT_EXECUTOR,
+                mStatsCallbackImpl
+        );
+    }
+
+    int pullCpuCyclesPerUidClusterLocked(int atomTag, List<StatsEvent> pulledData) {
+        PowerProfile powerProfile = new PowerProfile(mContext);
+        // Frequency index to frequency mapping.
+        long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
+        // Frequency index to cluster mapping.
+        int[] freqClusters = new int[freqs.length];
+        // Frequency index to power mapping.
+        double[] freqPowers = new double[freqs.length];
+        // Number of clusters.
+        int clusters;
+
+        // Initialize frequency mappings.
+        {
+            int cluster = 0;
+            int freqClusterIndex = 0;
+            long lastFreq = -1;
+            for (int freqIndex = 0; freqIndex < freqs.length; ++freqIndex, ++freqClusterIndex) {
+                long currFreq = freqs[freqIndex];
+                if (currFreq <= lastFreq) {
+                    cluster++;
+                    freqClusterIndex = 0;
+                }
+                freqClusters[freqIndex] = cluster;
+                freqPowers[freqIndex] =
+                        powerProfile.getAveragePowerForCpuCore(cluster, freqClusterIndex);
+                lastFreq = currFreq;
+            }
+
+            clusters = cluster + 1;
+        }
+
+        // Aggregate 0: mcycles, 1: runtime ms, 2: power profile estimate for the same uids for
+        // each cluster.
+        SparseArray<double[]> aggregated = new SparseArray<>();
+        mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+            if (UserHandle.isIsolated(uid)) {
+                // Skip individual isolated uids because they are recycled and quickly removed from
+                // the underlying data source.
+                return;
+            } else if (UserHandle.isSharedAppGid(uid)) {
+                // All shared app gids are accounted together.
+                uid = LAST_SHARED_APPLICATION_GID;
+            } else {
+                // Everything else is accounted under their base uid.
+                uid = UserHandle.getAppId(uid);
+            }
+
+            double[] values = aggregated.get(uid);
+            if (values == null) {
+                values = new double[clusters * CPU_CYCLES_PER_UID_CLUSTER_VALUES];
+                aggregated.put(uid, values);
+            }
+
+            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+                int cluster = freqClusters[freqIndex];
+                long timeMs = cpuFreqTimeMs[freqIndex];
+                values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES] += freqs[freqIndex] * timeMs;
+                values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES + 1] += timeMs;
+                values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES + 2] +=
+                        freqPowers[freqIndex] * timeMs;
+            }
+        });
+
+        int size = aggregated.size();
+        for (int i = 0; i < size; ++i) {
+            int uid = aggregated.keyAt(i);
+            double[] values = aggregated.valueAt(i);
+            for (int cluster = 0; cluster < clusters; ++cluster) {
+                pulledData.add(FrameworkStatsLog.buildStatsEvent(
+                        atomTag, uid, cluster,
+                        (long) (values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES] / 1e6),
+                        (long) values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES + 1],
+                        (long) (values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES + 2] / 1e3)));
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
     private void registerCpuTimePerUidFreq() {
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/OWNERS b/services/tests/servicestests/src/com/android/internal/location/timezone/OWNERS
new file mode 100644
index 0000000..28aff18
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/internal/location/timezone/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
[email protected]
+include /core/java/android/app/timedetector/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index 777713e..798cf85 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -18,13 +18,17 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.hdmi.HdmiControlManager;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings.Global;
 
@@ -32,21 +36,30 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @SmallTest
 @Presubmit
 @RunWith(JUnit4.class)
 public final class HdmiCecConfigTest {
     private static final String TAG = "HdmiCecConfigTest";
 
+    private static final int TIMEOUT_CONTENT_CHANGE_SEC = 3;
+
+    private final TestLooper mTestLooper = new TestLooper();
+
     private Context mContext;
 
     @Mock private HdmiCecConfig.StorageAdapter mStorageAdapter;
+    @Mock private HdmiCecConfig.SettingChangeListener mSettingChangeListener;
 
     @Before
     public void setUp() throws Exception {
@@ -1019,4 +1032,119 @@
                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
                 Integer.toString(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED));
     }
+
+    @Test
+    public void registerChangeListener_SharedPref_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                        + "<cec-settings>"
+                        + "  <setting name=\"system_audio_mode_muting\""
+                        + "           value-type=\"int\""
+                        + "           user-configurable=\"true\">"
+                        + "    <allowed-values>"
+                        + "      <value int-value=\"0\" />"
+                        + "      <value int-value=\"1\" />"
+                        + "    </allowed-values>"
+                        + "    <default-value int-value=\"1\" />"
+                        + "  </setting>"
+                        + "</cec-settings>", null);
+        hdmiCecConfig.registerChangeListener(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
+                mSettingChangeListener);
+        hdmiCecConfig.setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
+                HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED);
+        verify(mSettingChangeListener).onChange(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING);
+    }
+
+    @Test
+    public void removeChangeListener_SharedPref_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                        + "<cec-settings>"
+                        + "  <setting name=\"system_audio_mode_muting\""
+                        + "           value-type=\"int\""
+                        + "           user-configurable=\"true\">"
+                        + "    <allowed-values>"
+                        + "      <value int-value=\"0\" />"
+                        + "      <value int-value=\"1\" />"
+                        + "    </allowed-values>"
+                        + "    <default-value int-value=\"1\" />"
+                        + "  </setting>"
+                        + "</cec-settings>", null);
+        hdmiCecConfig.registerChangeListener(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
+                mSettingChangeListener);
+        hdmiCecConfig.removeChangeListener(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
+                mSettingChangeListener);
+        hdmiCecConfig.setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
+                HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED);
+        verify(mSettingChangeListener, never()).onChange(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING);
+    }
+
+    /**
+     * Externally modified Global Settings still need to be supported. This test verifies that
+     * setting change notification is being forwarded to listeners registered via HdmiCecConfig.
+     */
+    @Test
+    @Ignore("b/175381065")
+    public void globalSettingObserver_BasicSanity() throws Exception {
+        CountDownLatch notifyLatch = new CountDownLatch(1);
+        // Get current value of the setting in the system.
+        String originalValue = Global.getString(mContext.getContentResolver(),
+                Global.HDMI_CONTROL_ENABLED);
+        try {
+            HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                    mContext, mStorageAdapter,
+                    "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                            + "<cec-settings>"
+                            + "  <setting name=\"hdmi_cec_enabled\""
+                            + "           value-type=\"int\""
+                            + "           user-configurable=\"true\">"
+                            + "    <allowed-values>"
+                            + "      <value int-value=\"0\" />"
+                            + "      <value int-value=\"1\" />"
+                            + "    </allowed-values>"
+                            + "    <default-value int-value=\"1\" />"
+                            + "  </setting>"
+                            + "</cec-settings>", null);
+            hdmiCecConfig.registerGlobalSettingsObserver(mTestLooper.getLooper());
+            HdmiCecConfig.SettingChangeListener latchUpdateListener =
+                    new HdmiCecConfig.SettingChangeListener() {
+                        @Override
+                        public void onChange(
+                                @NonNull @HdmiControlManager.CecSettingName String setting) {
+                            notifyLatch.countDown();
+                            assertThat(setting).isEqualTo(
+                                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
+                        }
+                    };
+            hdmiCecConfig.registerChangeListener(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                    latchUpdateListener);
+
+            // Flip the value of the setting.
+            String valueToSet = ((originalValue == null || originalValue.equals("1")) ? "0" : "1");
+            Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED,
+                    valueToSet);
+            assertThat(Global.getString(mContext.getContentResolver(),
+                    Global.HDMI_CONTROL_ENABLED)).isEqualTo(valueToSet);
+            mTestLooper.dispatchAll();
+
+            if (!notifyLatch.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for the notify callback");
+            }
+            hdmiCecConfig.unregisterGlobalSettingsObserver();
+        } finally {
+            // Restore the previous value of the setting in the system.
+            Global.putString(mContext.getContentResolver(), Global.HDMI_CONTROL_ENABLED,
+                    originalValue);
+        }
+    }
 }