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);
+ }
+ }
}