diff options
18 files changed, 571 insertions, 329 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 259f1e9d4c53..f625a73f222d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4492,8 +4492,8 @@ package android.app { method public void openOptionsMenu(); method public void overrideActivityTransition(int, @AnimRes int, @AnimRes int); method public void overrideActivityTransition(int, @AnimRes int, @AnimRes int, @ColorInt int); - method public void overridePendingTransition(int, int); - method public void overridePendingTransition(int, int, int); + method @Deprecated public void overridePendingTransition(int, int); + method @Deprecated public void overridePendingTransition(int, int, int); method public void postponeEnterTransition(); method public void recreate(); method public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index d9c7a27cb95e..125e7270b0e2 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6621,7 +6621,9 @@ public class Activity extends ContextThemeWrapper * the incoming activity. Use 0 for no animation. * @param exitAnim A resource ID of the animation resource to use for * the outgoing activity. Use 0 for no animation. + * @deprecated Use {@link #overrideActivityTransition(int, int, int)}} instead. */ + @Deprecated public void overridePendingTransition(int enterAnim, int exitAnim) { overridePendingTransition(enterAnim, exitAnim, 0); } @@ -6644,7 +6646,9 @@ public class Activity extends ContextThemeWrapper * the outgoing activity. Use 0 for no animation. * @param backgroundColor The background color to use for the background during the animation if * the animation requires a background. Set to 0 to not override the default color. + * @deprecated Use {@link #overrideActivityTransition(int, int, int, int)}} instead. */ + @Deprecated public void overridePendingTransition(int enterAnim, int exitAnim, int backgroundColor) { ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(), enterAnim, exitAnim, backgroundColor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 8d29901c3a07..bcc37baa5b00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -143,8 +143,12 @@ public class TransitionAnimationHelper { Animation a = null; if (animAttr != 0) { if (overrideType == ANIM_FROM_STYLE && !isTask) { - a = loadCustomActivityTransition(animAttr, options, enter, transitionAnimation); - if (a == null) { + final TransitionInfo.AnimationOptions.CustomActivityTransition customTransition = + getCustomActivityTransition(animAttr, options); + if (customTransition != null) { + a = loadCustomActivityTransition( + customTransition, options, enter, transitionAnimation); + } else { a = transitionAnimation .loadAnimationAttr(options.getPackageName(), options.getAnimations(), animAttr, translucent); @@ -161,10 +165,8 @@ public class TransitionAnimationHelper { return a; } - static Animation loadCustomActivityTransition(int animAttr, - TransitionInfo.AnimationOptions options, boolean enter, - TransitionAnimation transitionAnimation) { - Animation a = null; + static TransitionInfo.AnimationOptions.CustomActivityTransition getCustomActivityTransition( + int animAttr, TransitionInfo.AnimationOptions options) { boolean isOpen = false; switch (animAttr) { case R.styleable.WindowAnimation_activityOpenEnterAnimation: @@ -178,17 +180,19 @@ public class TransitionAnimationHelper { return null; } - final TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim = - options.getCustomActivityTransition(isOpen); - if (transitionAnim != null) { - a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(), - enter ? transitionAnim.getCustomEnterResId() - : transitionAnim.getCustomExitResId()); - if (a != null && transitionAnim.getCustomBackgroundColor() != 0) { - a.setBackdropColor(transitionAnim.getCustomBackgroundColor()); - } - } + return options.getCustomActivityTransition(isOpen); + } + static Animation loadCustomActivityTransition( + @NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim, + TransitionInfo.AnimationOptions options, boolean enter, + TransitionAnimation transitionAnimation) { + final Animation a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(), + enter ? transitionAnim.getCustomEnterResId() + : transitionAnim.getCustomExitResId()); + if (a != null && transitionAnim.getCustomBackgroundColor() != 0) { + a.setBackdropColor(transitionAnim.getCustomBackgroundColor()); + } return a; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 2ad1ab722d55..288f0cc4456d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -966,13 +966,24 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + if (!handleOnAnimationStart( + transit, apps, wallpapers, nonApps, finishedCallback)) { + // Usually we rely on animation completion to synchronize occluded status, + // but there was no animation to play, so just update it now. + setOccluded(true /* isOccluded */, false /* animate */); + } + } + + private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { if (apps == null || apps.length == 0 || apps[0] == null) { if (DEBUG) { Log.d(TAG, "No apps provided to the OccludeByDream runner; " + "skipping occluding animation."); } finishedCallback.onAnimationFinished(); - return; + return false; } final RemoteAnimationTarget primary = apps[0]; @@ -982,7 +993,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Log.w(TAG, "The occluding app isn't Dream; " + "finishing up. Please check that the config is correct."); finishedCallback.onAnimationFinished(); - return; + return false; } final SyncRtSurfaceTransactionApplier applier = @@ -1031,6 +1042,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mOccludeByDreamAnimator.start(); }); + return true; } }; diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt index 030c54fbd87d..9952cfd4e85b 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt @@ -62,8 +62,6 @@ constructor( BluetoothAdapter.OnMetadataChangedListener { private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList() - private val stylusBatteryCallbacks: CopyOnWriteArrayList<StylusBatteryCallback> = - CopyOnWriteArrayList() // This map should only be accessed on the handler private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap() @@ -106,14 +104,6 @@ constructor( stylusCallbacks.remove(callback) } - fun registerBatteryCallback(callback: StylusBatteryCallback) { - stylusBatteryCallbacks.add(callback) - } - - fun unregisterBatteryCallback(callback: StylusBatteryCallback) { - stylusBatteryCallbacks.remove(callback) - } - override fun onInputDeviceAdded(deviceId: Int) { if (!hasStarted) return @@ -195,7 +185,7 @@ constructor( "${device.address}: $isCharging" } - executeStylusBatteryCallbacks { cb -> + executeStylusCallbacks { cb -> cb.onStylusBluetoothChargingStateChanged(inputDeviceId, device, isCharging) } } @@ -221,7 +211,7 @@ constructor( onStylusUsed() } - executeStylusBatteryCallbacks { cb -> + executeStylusCallbacks { cb -> cb.onStylusUsiBatteryStateChanged(deviceId, eventTimeMillis, batteryState) } } @@ -329,10 +319,6 @@ constructor( stylusCallbacks.forEach(run) } - private fun executeStylusBatteryCallbacks(run: (cb: StylusBatteryCallback) -> Unit) { - stylusBatteryCallbacks.forEach(run) - } - private fun registerBatteryListener(deviceId: Int) { try { inputManager.addInputDeviceBatteryListener(deviceId, executor, this) @@ -378,13 +364,6 @@ constructor( fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {} fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {} fun onStylusFirstUsed() {} - } - - /** - * Callback interface to receive stylus battery events from the StylusManager. All callbacks are - * runs on the same background handler. - */ - interface StylusBatteryCallback { fun onStylusBluetoothChargingStateChanged( inputDeviceId: Int, btDevice: BluetoothDevice, diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt index 27cafb10c07d..3667392b515e 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt @@ -37,7 +37,7 @@ constructor( private val inputManager: InputManager, private val stylusUsiPowerUi: StylusUsiPowerUI, private val featureFlags: FeatureFlags, -) : CoreStartable, StylusManager.StylusCallback, StylusManager.StylusBatteryCallback { +) : CoreStartable, StylusManager.StylusCallback { override fun onStylusAdded(deviceId: Int) { // On some devices, the addition of a new internal stylus indicates the use of a @@ -74,7 +74,6 @@ constructor( stylusUsiPowerUi.init() stylusManager.registerCallback(this) - stylusManager.registerBatteryCallback(this) stylusManager.startListener() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt index f8bf4b91e11a..4525ad27b749 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt @@ -65,8 +65,6 @@ class StylusManagerTest : SysuiTestCase() { @Mock lateinit var uiEventLogger: UiEventLogger @Mock lateinit var stylusCallback: StylusManager.StylusCallback @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback - @Mock lateinit var stylusBatteryCallback: StylusManager.StylusBatteryCallback - @Mock lateinit var otherStylusBatteryCallback: StylusManager.StylusBatteryCallback private lateinit var mockitoSession: StaticMockitoSession private lateinit var stylusManager: StylusManager @@ -123,7 +121,6 @@ class StylusManagerTest : SysuiTestCase() { stylusManager.startListener() stylusManager.registerCallback(stylusCallback) - stylusManager.registerBatteryCallback(stylusBatteryCallback) clearInvocations(inputManager) } @@ -434,23 +431,6 @@ class StylusManagerTest : SysuiTestCase() { } @Test - fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() { - stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) - stylusManager.registerBatteryCallback(otherStylusBatteryCallback) - - stylusManager.onMetadataChanged( - bluetoothDevice, - BluetoothDevice.METADATA_MAIN_CHARGING, - "true".toByteArray() - ) - - verify(stylusBatteryCallback, times(1)) - .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true) - verify(otherStylusBatteryCallback, times(1)) - .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true) - } - - @Test fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -460,7 +440,7 @@ class StylusManagerTest : SysuiTestCase() { "true".toByteArray() ) - verify(stylusBatteryCallback, times(1)) + verify(stylusCallback, times(1)) .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true) } @@ -474,7 +454,7 @@ class StylusManagerTest : SysuiTestCase() { "false".toByteArray() ) - verify(stylusBatteryCallback, times(1)) + verify(stylusCallback, times(1)) .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, false) } @@ -486,7 +466,7 @@ class StylusManagerTest : SysuiTestCase() { "true".toByteArray() ) - verifyNoMoreInteractions(stylusBatteryCallback) + verifyNoMoreInteractions(stylusCallback) } @Test @@ -499,8 +479,7 @@ class StylusManagerTest : SysuiTestCase() { "true".toByteArray() ) - verify(stylusBatteryCallback, never()) - .onStylusBluetoothChargingStateChanged(any(), any(), any()) + verify(stylusCallback, never()).onStylusBluetoothChargingStateChanged(any(), any(), any()) } @Test @@ -614,7 +593,7 @@ class StylusManagerTest : SysuiTestCase() { fun onBatteryStateChanged_executesBatteryCallbacks() { stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) - verify(stylusBatteryCallback, times(1)) + verify(stylusCallback, times(1)) .onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt index 82b80f53d6b0..3db0ecc4e8df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt @@ -96,7 +96,6 @@ class StylusUsiPowerStartableTest : SysuiTestCase() { startable.start() verify(stylusManager, times(1)).registerCallback(startable) - verify(stylusManager, times(1)).registerBatteryCallback(startable) } @Test diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java new file mode 100644 index 000000000000..961313de0c97 --- /dev/null +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; +import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO; +import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK; +import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; +import static android.media.MediaRoute2Info.TYPE_DOCK; +import static android.media.MediaRoute2Info.TYPE_HDMI; +import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; +import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; +import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioRoutesInfo; +import android.media.IAudioRoutesObserver; +import android.media.IAudioService; +import android.media.MediaRoute2Info; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + + +/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController { + + private static final String TAG = "APDeviceRoutesController"; + + private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE"; + + @NonNull + private final Context mContext; + @NonNull + private final AudioManager mAudioManager; + @NonNull + private final IAudioService mAudioService; + + @NonNull + private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + @NonNull + private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver(); + + private int mDeviceVolume; + private MediaRoute2Info mDeviceRoute; + + @VisibleForTesting + /* package */ AudioPoliciesDeviceRouteController(@NonNull Context context, + @NonNull AudioManager audioManager, + @NonNull IAudioService audioService, + @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { + Objects.requireNonNull(context); + Objects.requireNonNull(audioManager); + Objects.requireNonNull(audioService); + Objects.requireNonNull(onDeviceRouteChangedListener); + + mContext = context; + mOnDeviceRouteChangedListener = onDeviceRouteChangedListener; + + mAudioManager = audioManager; + mAudioService = audioService; + + AudioRoutesInfo newAudioRoutes = null; + try { + newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); + } catch (RemoteException e) { + Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e); + } + + mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes); + } + + @Override + public boolean selectRoute(@Nullable Integer type) { + // No-op as the controller does not support selection from the outside of the class. + return false; + } + + @Override + @NonNull + public synchronized MediaRoute2Info getDeviceRoute() { + return mDeviceRoute; + } + + @Override + public synchronized boolean updateVolume(int volume) { + if (mDeviceVolume == volume) { + return false; + } + + mDeviceVolume = volume; + mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute) + .setVolume(volume) + .build(); + + return true; + } + + private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) { + int name = R.string.default_audio_route_name; + int type = TYPE_BUILTIN_SPEAKER; + + if (newRoutes != null) { + if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) { + type = TYPE_WIRED_HEADPHONES; + name = R.string.default_audio_route_name_headphones; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) { + type = TYPE_WIRED_HEADSET; + name = R.string.default_audio_route_name_headphones; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) { + type = TYPE_DOCK; + name = R.string.default_audio_route_name_dock_speakers; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) { + type = TYPE_HDMI; + name = R.string.default_audio_route_name_external_device; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) { + type = TYPE_USB_DEVICE; + name = R.string.default_audio_route_name_usb; + } + } + + synchronized (this) { + return new MediaRoute2Info.Builder( + DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString()) + .setVolumeHandling(mAudioManager.isVolumeFixed() + ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED + : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) + .setVolume(mDeviceVolume) + .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) + .setType(type) + .addFeature(FEATURE_LIVE_AUDIO) + .addFeature(FEATURE_LIVE_VIDEO) + .addFeature(FEATURE_LOCAL_PLAYBACK) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) + .build(); + } + } + + private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) { + mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); + } + + private class AudioRoutesObserver extends IAudioRoutesObserver.Stub { + + @Override + public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) { + MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes); + synchronized (AudioPoliciesDeviceRouteController.this) { + mDeviceRoute = deviceRoute; + } + notifyDeviceRouteUpdate(deviceRoute); + } + } + +} diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index 8bd6416a6ddb..d7893eed800b 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -16,32 +16,14 @@ package com.android.server.media; -import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; -import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO; -import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK; -import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; -import static android.media.MediaRoute2Info.TYPE_DOCK; -import static android.media.MediaRoute2Info.TYPE_HDMI; -import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; -import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; -import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.media.AudioManager; -import android.media.AudioRoutesInfo; import android.media.IAudioRoutesObserver; import android.media.IAudioService; import android.media.MediaRoute2Info; -import android.os.RemoteException; import android.os.ServiceManager; -import android.util.Slog; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; - -import java.util.Objects; /** * Controls device routes. @@ -49,145 +31,65 @@ import java.util.Objects; * <p>A device route is a system wired route, for example, built-in speaker, wired * headsets and headphones, dock, hdmi, or usb devices. * - * <p>Thread safe. - * * @see SystemMediaRoute2Provider */ -/* package */ final class DeviceRouteController { - - private static final String TAG = "WiredRoutesController"; - - private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE"; - - @NonNull - private final Context mContext; - @NonNull - private final AudioManager mAudioManager; - @NonNull - private final IAudioService mAudioService; - - @NonNull - private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; - @NonNull - private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver(); - - private int mDeviceVolume; - private MediaRoute2Info mDeviceRoute; +/* package */ interface DeviceRouteController { + /** + * Returns a new instance of {@link DeviceRouteController}. + */ /* package */ static DeviceRouteController createInstance(@NonNull Context context, @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { AudioManager audioManager = context.getSystemService(AudioManager.class); IAudioService audioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); - return new DeviceRouteController(context, + return new LegacyDeviceRouteController(context, audioManager, audioService, onDeviceRouteChangedListener); } - @VisibleForTesting - /* package */ DeviceRouteController(@NonNull Context context, - @NonNull AudioManager audioManager, - @NonNull IAudioService audioService, - @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { - Objects.requireNonNull(context); - Objects.requireNonNull(audioManager); - Objects.requireNonNull(audioService); - Objects.requireNonNull(onDeviceRouteChangedListener); - - mContext = context; - mOnDeviceRouteChangedListener = onDeviceRouteChangedListener; - - mAudioManager = audioManager; - mAudioService = audioService; - - AudioRoutesInfo newAudioRoutes = null; - try { - newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); - } catch (RemoteException e) { - Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e); - } - - mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes); - } - + /** + * Select the route with the given built-in or wired {@link MediaRoute2Info.Type}. + * + * <p>If the type is {@code null} then unselects the route and falls back to the default device + * route observed from + * {@link com.android.server.audio.AudioService#startWatchingRoutes(IAudioRoutesObserver)}. + * + * @param type device type. May be {@code null} to unselect currently selected route. + * @return whether the selection succeeds. If the selection fails the state of the controller + * remains intact. + */ + boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type); + + /** + * Returns currently selected device (built-in or wired) route. + * + * @return non-null device route. + */ @NonNull - /* package */ synchronized MediaRoute2Info getDeviceRoute() { - return mDeviceRoute; - } - - /* package */ synchronized boolean updateVolume(int volume) { - if (mDeviceVolume == volume) { - return false; - } - - mDeviceVolume = volume; - mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute) - .setVolume(volume) - .build(); - - return true; - } - - private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) { - int name = R.string.default_audio_route_name; - int type = TYPE_BUILTIN_SPEAKER; - - if (newRoutes != null) { - if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) { - type = TYPE_WIRED_HEADPHONES; - name = com.android.internal.R.string.default_audio_route_name_headphones; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) { - type = TYPE_WIRED_HEADSET; - name = com.android.internal.R.string.default_audio_route_name_headphones; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) { - type = TYPE_DOCK; - name = com.android.internal.R.string.default_audio_route_name_dock_speakers; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) { - type = TYPE_HDMI; - name = com.android.internal.R.string.default_audio_route_name_external_device; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) { - type = TYPE_USB_DEVICE; - name = com.android.internal.R.string.default_audio_route_name_usb; - } - } - - synchronized (this) { - return new MediaRoute2Info.Builder( - DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString()) - .setVolumeHandling(mAudioManager.isVolumeFixed() - ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED - : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) - .setVolume(mDeviceVolume) - .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) - .setType(type) - .addFeature(FEATURE_LIVE_AUDIO) - .addFeature(FEATURE_LIVE_VIDEO) - .addFeature(FEATURE_LOCAL_PLAYBACK) - .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) - .build(); - } - } - - private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) { - mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); - } - - /* package */ interface OnDeviceRouteChangedListener { + MediaRoute2Info getDeviceRoute(); + + /** + * Updates device route volume. + * + * @param volume specifies a volume for the device route or 0 for unknown. + * @return {@code true} if updated successfully and {@code false} otherwise. + */ + boolean updateVolume(int volume); + + /** + * Interface for receiving events when device route has changed. + */ + interface OnDeviceRouteChangedListener { + + /** + * Called when device route has changed. + * + * @param deviceRoute non-null device route. + */ void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute); } - private class AudioRoutesObserver extends IAudioRoutesObserver.Stub { - - @Override - public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) { - MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes); - synchronized (DeviceRouteController.this) { - mDeviceRoute = deviceRoute; - } - notifyDeviceRouteUpdate(deviceRoute); - } - } - } diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java new file mode 100644 index 000000000000..971d11f24b9c --- /dev/null +++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; +import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO; +import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK; +import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; +import static android.media.MediaRoute2Info.TYPE_DOCK; +import static android.media.MediaRoute2Info.TYPE_HDMI; +import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; +import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; +import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioRoutesInfo; +import android.media.IAudioRoutesObserver; +import android.media.IAudioService; +import android.media.MediaRoute2Info; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + +/** + * Controls device routes. + * + * <p>A device route is a system wired route, for example, built-in speaker, wired + * headsets and headphones, dock, hdmi, or usb devices. + * + * <p>Thread safe. + * + * @see SystemMediaRoute2Provider + */ +/* package */ final class LegacyDeviceRouteController implements DeviceRouteController { + + private static final String TAG = "LDeviceRouteController"; + + private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE"; + + @NonNull + private final Context mContext; + @NonNull + private final AudioManager mAudioManager; + @NonNull + private final IAudioService mAudioService; + + @NonNull + private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + @NonNull + private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver(); + + private int mDeviceVolume; + private MediaRoute2Info mDeviceRoute; + + @VisibleForTesting + /* package */ LegacyDeviceRouteController(@NonNull Context context, + @NonNull AudioManager audioManager, + @NonNull IAudioService audioService, + @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { + Objects.requireNonNull(context); + Objects.requireNonNull(audioManager); + Objects.requireNonNull(audioService); + Objects.requireNonNull(onDeviceRouteChangedListener); + + mContext = context; + mOnDeviceRouteChangedListener = onDeviceRouteChangedListener; + + mAudioManager = audioManager; + mAudioService = audioService; + + AudioRoutesInfo newAudioRoutes = null; + try { + newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); + } catch (RemoteException e) { + Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e); + } + + mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes); + } + + @Override + public boolean selectRoute(@Nullable Integer type) { + // No-op as the controller does not support selection from the outside of the class. + return false; + } + + @Override + @NonNull + public synchronized MediaRoute2Info getDeviceRoute() { + return mDeviceRoute; + } + + @Override + public synchronized boolean updateVolume(int volume) { + if (mDeviceVolume == volume) { + return false; + } + + mDeviceVolume = volume; + mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute) + .setVolume(volume) + .build(); + + return true; + } + + private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) { + int name = R.string.default_audio_route_name; + int type = TYPE_BUILTIN_SPEAKER; + + if (newRoutes != null) { + if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) { + type = TYPE_WIRED_HEADPHONES; + name = com.android.internal.R.string.default_audio_route_name_headphones; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) { + type = TYPE_WIRED_HEADSET; + name = com.android.internal.R.string.default_audio_route_name_headphones; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) { + type = TYPE_DOCK; + name = com.android.internal.R.string.default_audio_route_name_dock_speakers; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) { + type = TYPE_HDMI; + name = com.android.internal.R.string.default_audio_route_name_external_device; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) { + type = TYPE_USB_DEVICE; + name = com.android.internal.R.string.default_audio_route_name_usb; + } + } + + synchronized (this) { + return new MediaRoute2Info.Builder( + DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString()) + .setVolumeHandling(mAudioManager.isVolumeFixed() + ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED + : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) + .setVolume(mDeviceVolume) + .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) + .setType(type) + .addFeature(FEATURE_LIVE_AUDIO) + .addFeature(FEATURE_LIVE_VIDEO) + .addFeature(FEATURE_LOCAL_PLAYBACK) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) + .build(); + } + } + + private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) { + mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); + } + + private class AudioRoutesObserver extends IAudioRoutesObserver.Stub { + + @Override + public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) { + MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes); + synchronized (LegacyDeviceRouteController.this) { + mDeviceRoute = deviceRoute; + } + notifyDeviceRouteUpdate(deviceRoute); + } + } + +} diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 53e841d50b33..73440b7f2eec 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -892,6 +892,7 @@ abstract public class ManagedServices { return allowedComponents; } + @NonNull protected List<String> getAllowedPackages(int userId) { final List<String> allowedPackages = new ArrayList<>(); synchronized (mApproved) { @@ -1181,25 +1182,6 @@ abstract public class ManagedServices { return installed; } - protected Set<String> getAllowedPackages() { - final Set<String> allowedPackages = new ArraySet<>(); - synchronized (mApproved) { - for (int k = 0; k < mApproved.size(); k++) { - ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.valueAt(k); - for (int i = 0; i < allowedByType.size(); i++) { - final ArraySet<String> allowed = allowedByType.valueAt(i); - for (int j = 0; j < allowed.size(); j++) { - String pkgName = getPackageName(allowed.valueAt(j)); - if (!TextUtils.isEmpty(pkgName)) { - allowedPackages.add(pkgName); - } - } - } - } - } - return allowedPackages; - } - private void trimApprovedListsAccordingToInstalledServices(int userId) { synchronized (mApproved) { final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0d394570ab8e..53b03d58beae 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2709,16 +2709,18 @@ public class NotificationManagerService extends SystemService { } private void sendRegisteredOnlyBroadcast(String action) { - Intent intent = new Intent(action); - getContext().sendBroadcastAsUser(intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), - UserHandle.ALL, null); + int[] userIds = mUmInternal.getProfileIds(mAmi.getCurrentUserId(), true); + Intent intent = new Intent(action).addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + for (int userId : userIds) { + getContext().sendBroadcastAsUser(intent, UserHandle.of(userId), null); + } // explicitly send the broadcast to all DND packages, even if they aren't currently running - intent.setFlags(0); - final Set<String> dndApprovedPackages = mConditionProviders.getAllowedPackages(); - for (String pkg : dndApprovedPackages) { - intent.setPackage(pkg); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - getContext().sendBroadcastAsUser(intent, UserHandle.ALL); + for (int userId : userIds) { + for (String pkg : mConditionProviders.getAllowedPackages(userId)) { + Intent pkgIntent = new Intent(action).setPackage(pkg).setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + getContext().sendBroadcastAsUser(pkgIntent, UserHandle.of(userId)); + } } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 45dc49dba2ca..37a59da33ad8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2977,7 +2977,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_H: - if (down && event.isMetaPressed()) { + if (event.isMetaPressed()) { return handleHomeShortcuts(displayId, focusedToken, event); } break; @@ -3018,11 +3018,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { enterStageSplitFromRunningApp(true /* leftOrTop */); return key_consumed; - } else if (!down && event.isMetaPressed()) { - boolean backKeyHandled = backKeyPress(); - if (backKeyHandled) { - return key_consumed; - } } break; case KeyEvent.KEYCODE_DPAD_RIGHT: @@ -3031,14 +3026,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { return key_consumed; } break; - case KeyEvent.KEYCODE_GRAVE: - if (!down && event.isMetaPressed()) { - boolean backKeyHandled = backKeyPress(); - if (backKeyHandled) { - return key_consumed; - } - } - break; case KeyEvent.KEYCODE_SLASH: if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) { toggleKeyboardShortcutsMenu(event.getDeviceId()); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f73c68a42ec7..939cf1ae471b 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -888,8 +888,11 @@ public class AppTransition implements Dump { } else { int animAttr = mapOpenCloseTransitTypes(transit, enter); if (animAttr != 0) { - a = loadCustomActivityAnimation(animAttr, enter, container); - if (a == null) { + final CustomAppTransition customAppTransition = + getCustomAppTransition(animAttr, container); + if (customAppTransition != null) { + a = loadCustomActivityAnimation(customAppTransition, enter, container); + } else { if (canCustomizeAppTransition) { a = loadAnimationAttr(lp, animAttr, transit); } else { @@ -911,7 +914,7 @@ public class AppTransition implements Dump { return a; } - Animation loadCustomActivityAnimation(int animAttr, boolean enter, WindowContainer container) { + CustomAppTransition getCustomAppTransition(int animAttr, WindowContainer container) { ActivityRecord customAnimationSource = container.asActivityRecord(); if (customAnimationSource == null) { return null; @@ -927,31 +930,28 @@ public class AppTransition implements Dump { return null; } } - final CustomAppTransition custom; switch (animAttr) { case WindowAnimation_activityOpenEnterAnimation: case WindowAnimation_activityOpenExitAnimation: - custom = customAnimationSource.getCustomAnimation(true /* open */); - break; + return customAnimationSource.getCustomAnimation(true /* open */); case WindowAnimation_activityCloseEnterAnimation: case WindowAnimation_activityCloseExitAnimation: - custom = customAnimationSource.getCustomAnimation(false /* open */); - break; - default: - return null; - } - if (custom != null) { - final Animation a = mTransitionAnimation.loadAppTransitionAnimation( - customAnimationSource.packageName, enter - ? custom.mEnterAnim : custom.mExitAnim); - if (a != null && custom.mBackgroundColor != 0) { - a.setBackdropColor(custom.mBackgroundColor); - a.setShowBackdrop(true); - } - return a; + return customAnimationSource.getCustomAnimation(false /* open */); } return null; } + private Animation loadCustomActivityAnimation(@NonNull CustomAppTransition custom, + boolean enter, WindowContainer container) { + final ActivityRecord customAnimationSource = container.asActivityRecord(); + final Animation a = mTransitionAnimation.loadAppTransitionAnimation( + customAnimationSource.packageName, enter + ? custom.mEnterAnim : custom.mExitAnim); + if (a != null && custom.mBackgroundColor != 0) { + a.setBackdropColor(custom.mBackgroundColor); + a.setShowBackdrop(true); + } + return a; + } int getAppRootTaskClipMode() { return mNextAppTransitionRequests.contains(TRANSIT_RELAUNCH) diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java index 24ed42cab63a..24e48517f280 100644 --- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java @@ -50,7 +50,7 @@ import java.util.Arrays; import java.util.Collection; @RunWith(Enclosed.class) -public class DeviceRouteControllerTest { +public class LegacyDeviceRouteControllerTest { private static final String DEFAULT_ROUTE_NAME = "default_route"; private static final String DEFAULT_HEADPHONES_NAME = "headphone"; @@ -97,7 +97,7 @@ public class DeviceRouteControllerTest { // Default route should be initialized even when AudioService returns null. when(mAudioService.startWatchingRoutes(any())).thenReturn(null); - DeviceRouteController deviceRouteController = new DeviceRouteController( + LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController( mContext, mAudioManager, mAudioService, @@ -122,7 +122,7 @@ public class DeviceRouteControllerTest { AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute(); when(mAudioService.startWatchingRoutes(any())).thenReturn(fakeBluetoothAudioRoute); - DeviceRouteController deviceRouteController = new DeviceRouteController( + LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController( mContext, mAudioManager, mAudioService, @@ -236,7 +236,7 @@ public class DeviceRouteControllerTest { when(mResources.getText(mExpectedRouteNameResource)) .thenReturn(mExpectedRouteNameValue); - DeviceRouteController deviceRouteController = new DeviceRouteController( + LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController( mContext, mAudioManager, mAudioService, @@ -269,7 +269,7 @@ public class DeviceRouteControllerTest { @Captor private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor; - private DeviceRouteController mDeviceRouteController; + private LegacyDeviceRouteController mDeviceRouteController; private IAudioRoutesObserver.Stub mAudioRoutesObserver; @Before @@ -287,7 +287,7 @@ public class DeviceRouteControllerTest { when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture())) .thenReturn(audioRoutesInfo); - mDeviceRouteController = new DeviceRouteController( + mDeviceRouteController = new LegacyDeviceRouteController( mContext, mAudioManager, mAudioService, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 6f37e601abce..ce076217f37b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -24,6 +24,8 @@ import static android.service.notification.NotificationListenerService.META_DATA import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT; import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -1201,28 +1203,11 @@ public class ManagedServicesTest extends UiServiceTestCase { mIpm, approvalLevel); loadXml(service); - List<String> allowedPackagesForUser0 = new ArrayList<>(); - allowedPackagesForUser0.add("this.is.a.package.name"); - allowedPackagesForUser0.add("another.package"); - allowedPackagesForUser0.add("secondary"); - - List<String> actual = service.getAllowedPackages(0); - assertEquals(3, actual.size()); - for (String pkg : allowedPackagesForUser0) { - assertTrue(actual.contains(pkg)); - } - - List<String> allowedPackagesForUser10 = new ArrayList<>(); - allowedPackagesForUser10.add("this.is.another.package"); - allowedPackagesForUser10.add("package"); - allowedPackagesForUser10.add("this.is.another.package"); - allowedPackagesForUser10.add("component"); - - actual = service.getAllowedPackages(10); - assertEquals(4, actual.size()); - for (String pkg : allowedPackagesForUser10) { - assertTrue(actual.contains(pkg)); - } + assertThat(service.getAllowedPackages(0)).containsExactly("this.is.a.package.name", + "another.package", "secondary"); + assertThat(service.getAllowedPackages(10)).containsExactly("this.is.another.package", + "package", "this.is.another.package", "component"); + assertThat(service.getAllowedPackages(999)).isEmpty(); } } @@ -1263,31 +1248,6 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - public void testGetAllowedPackages() throws Exception { - ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, - mIpm, APPROVAL_BY_COMPONENT); - loadXml(service); - service.mApprovalLevel = APPROVAL_BY_PACKAGE; - loadXml(service); - - List<String> allowedPackages = new ArrayList<>(); - allowedPackages.add("this.is.a.package.name"); - allowedPackages.add("another.package"); - allowedPackages.add("secondary"); - allowedPackages.add("this.is.another.package"); - allowedPackages.add("package"); - allowedPackages.add("component"); - allowedPackages.add("bananas!"); - allowedPackages.add("non.user.set.package"); - - Set<String> actual = service.getAllowedPackages(); - assertEquals(allowedPackages.size(), actual.size()); - for (String pkg : allowedPackages) { - assertTrue(actual.contains(pkg)); - } - } - - @Test public void testOnUserRemoved() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index f08d0f5f71a4..354420f46c2f 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -29,6 +29,7 @@ import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; +import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; @@ -50,7 +51,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; - import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; @@ -237,6 +237,7 @@ import com.android.server.utils.quota.MultiRateLimiter; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.android.collect.Lists; import com.google.common.collect.ImmutableList; import org.junit.After; @@ -245,10 +246,13 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.BufferedInputStream; @@ -440,6 +444,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager); mContext.addMockSystemService(NotificationManager.class, mMockNm); + doNothing().when(mContext).sendBroadcastAsUser(any(), any()); doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); setDpmAppOppsExemptFromDismissal(false); @@ -7828,6 +7833,75 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void onZenModeChanged_sendsBroadcasts() throws Exception { + when(mAmi.getCurrentUserId()).thenReturn(100); + when(mUmInternal.getProfileIds(eq(100), anyBoolean())).thenReturn(new int[]{100, 101, 102}); + when(mConditionProviders.getAllowedPackages(anyInt())).then(new Answer<List<String>>() { + @Override + public List<String> answer(InvocationOnMock invocation) { + int userId = invocation.getArgument(0); + switch (userId) { + case 100: + return Lists.newArrayList("a", "b", "c"); + case 101: + return Lists.newArrayList(); + case 102: + return Lists.newArrayList("b"); + default: + throw new IllegalArgumentException( + "Why would you ask for packages of userId " + userId + "?"); + } + } + }); + + mService.getBinderService().setZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, null, + "testing!"); + waitForIdle(); + + InOrder inOrder = inOrder(mContext); + // Verify broadcasts for registered receivers + inOrder.verify(mContext).sendBroadcastAsUser(eqIntent( + new Intent(ACTION_INTERRUPTION_FILTER_CHANGED).setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY)), eq(UserHandle.of(100)), eq(null)); + inOrder.verify(mContext).sendBroadcastAsUser(eqIntent( + new Intent(ACTION_INTERRUPTION_FILTER_CHANGED).setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY)), eq(UserHandle.of(101)), eq(null)); + inOrder.verify(mContext).sendBroadcastAsUser(eqIntent( + new Intent(ACTION_INTERRUPTION_FILTER_CHANGED).setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY)), eq(UserHandle.of(102)), eq(null)); + + // Verify broadcast for packages that manage DND. + inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(new Intent( + ACTION_INTERRUPTION_FILTER_CHANGED).setPackage("a").setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(100))); + inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(new Intent( + ACTION_INTERRUPTION_FILTER_CHANGED).setPackage("b").setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(100))); + inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(new Intent( + ACTION_INTERRUPTION_FILTER_CHANGED).setPackage("c").setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(100))); + inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(new Intent( + ACTION_INTERRUPTION_FILTER_CHANGED).setPackage("b").setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(102))); + } + + private static Intent eqIntent(Intent wanted) { + return ArgumentMatchers.argThat( + new ArgumentMatcher<Intent>() { + @Override + public boolean matches(Intent argument) { + return wanted.filterEquals(argument) + && wanted.getFlags() == argument.getFlags(); + } + + @Override + public String toString() { + return wanted.toString(); + } + }); + } + + @Test public void testAreNotificationsEnabledForPackage() throws Exception { mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid); |