diff options
7 files changed, 192 insertions, 9 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index bb7d0c8f7952..5183a828b8c7 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3010,8 +3010,13 @@ package android.companion.virtual { method public void onIntentIntercepted(@NonNull android.content.Intent); } + public static interface VirtualDeviceManager.SoundEffectListener { + method public void onPlaySoundEffect(int); + } + public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable { method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); + method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); method @NonNull public android.content.Context createContext(); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback); @@ -3030,6 +3035,7 @@ package android.companion.virtual { method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); + method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index e96a2c18037b..4f49b8dbd0dc 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -18,6 +18,7 @@ package android.companion.virtual; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; +import android.companion.virtual.IVirtualDeviceSoundEffectListener; import android.companion.virtual.VirtualDevice; import android.companion.virtual.VirtualDeviceParams; import android.hardware.display.IVirtualDisplayCallback; @@ -41,10 +42,12 @@ interface IVirtualDeviceManager { * @param params The parameters for creating this virtual device. See {@link * VirtualDeviceManager.VirtualDeviceParams}. * @param activityListener The listener to listen for activity changes in a virtual device. + * @param soundEffectListener The listener to listen for sound effect playback requests. */ IVirtualDevice createVirtualDevice( in IBinder token, String packageName, int associationId, - in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener); + in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener, + in IVirtualDeviceSoundEffectListener soundEffectListener); /** * Returns the details of all available virtual devices. @@ -92,4 +95,13 @@ interface IVirtualDeviceManager { * if there's none. */ int getAudioRecordingSessionId(int deviceId); + + /** + * Triggers sound effect playback on virtual device. + * + * @param deviceId id of the virtual device. + * @param sound effect type corresponding to + * {@code android.media.AudioManager.SystemSoundEffect} + */ + void playSoundEffect(int deviceId, int effectType); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl new file mode 100644 index 000000000000..91c209fa098e --- /dev/null +++ b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual; + +/** + * Interface to listen for sound effect playback on Virtual Device. + * + * @hide + */ +oneway interface IVirtualDeviceSoundEffectListener { + + + /** + * Called when there's sound effect to be played on Virtual Device. + * + * @param sound effect type corresponding to + * {@code android.media.AudioManager.SystemSoundEffect} + */ + void onPlaySoundEffect(int effectType); +} diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 3bc1628d3576..1bc3091a3605 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -69,6 +69,8 @@ import android.util.ArrayMap; import android.util.Log; import android.view.Surface; +import com.android.internal.annotations.GuardedBy; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -333,10 +335,15 @@ public final class VirtualDeviceManager { * @hide */ public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) { - //TODO - handle requests to play sound effects by custom callbacks or SoundPool asociated - // with device session id. - // For now, this is intentionally left empty and effectively disables sound effects for - // virtual devices with custom device audio policy. + if (mService == null) { + Log.w(TAG, "Failed to dispatch sound effect; no virtual device manager service."); + return; + } + try { + mService.playSoundEffect(deviceId, effectType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -359,6 +366,10 @@ public final class VirtualDeviceManager { private final ArrayMap<IntentInterceptorCallback, VirtualIntentInterceptorDelegate> mIntentInterceptorListeners = new ArrayMap<>(); + private final Object mSoundEffectListenersLock = new Object(); + @GuardedBy("mSoundEffectListenersLock") + private final ArrayMap<SoundEffectListener, SoundEffectListenerDelegate> + mSoundEffectListeners = new ArrayMap<>(); private final IVirtualDeviceActivityListener mActivityListenerBinder = new IVirtualDeviceActivityListener.Stub() { @@ -387,6 +398,22 @@ public final class VirtualDeviceManager { } } }; + private final IVirtualDeviceSoundEffectListener mSoundEffectListener = + new IVirtualDeviceSoundEffectListener.Stub() { + @Override + public void onPlaySoundEffect(int soundEffect) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSoundEffectListenersLock) { + for (int i = 0; i < mSoundEffectListeners.size(); i++) { + mSoundEffectListeners.valueAt(i).onPlaySoundEffect(soundEffect); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; @Nullable private VirtualCameraDevice mVirtualCameraDevice; @NonNull @@ -407,7 +434,8 @@ public final class VirtualDeviceManager { mContext.getPackageName(), associationId, params, - mActivityListenerBinder); + mActivityListenerBinder, + mSoundEffectListener); final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs(); for (int i = 0; i < virtualSensorConfigs.size(); ++i) { mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i))); @@ -947,6 +975,35 @@ public final class VirtualDeviceManager { } /** + * Adds a sound effect listener. + * + * @param executor The executor where the listener is executed on. + * @param soundEffectListener The listener to add. + * @see #removeActivityListener(ActivityListener) + */ + public void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor, + @NonNull SoundEffectListener soundEffectListener) { + final SoundEffectListenerDelegate delegate = + new SoundEffectListenerDelegate(Objects.requireNonNull(executor), + Objects.requireNonNull(soundEffectListener)); + synchronized (mSoundEffectListenersLock) { + mSoundEffectListeners.put(soundEffectListener, delegate); + } + } + + /** + * Removes a sound effect listener previously added with {@link #addActivityListener}. + * + * @param soundEffectListener The listener to remove. + * @see #addActivityListener(Executor, ActivityListener) + */ + public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) { + synchronized (mSoundEffectListenersLock) { + mSoundEffectListeners.remove(soundEffectListener); + } + } + + /** * Registers an intent interceptor that will intercept an intent attempting to launch * when matching the provided IntentFilter and calls the callback with the intercepted * intent. @@ -1090,4 +1147,38 @@ public final class VirtualDeviceManager { } } } + + /** + * Listener for system sound effect playback on virtual device. + * @hide + */ + @SystemApi + public interface SoundEffectListener { + + /** + * Called when there's a system sound effect to be played on virtual device. + * + * @param effectType - system sound effect type, see + * {@code android.media.AudioManager.SystemSoundEffect} + */ + void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType); + } + + /** + * A wrapper for {@link SoundEffectListener} that executes callbacks on the given executor. + */ + private static class SoundEffectListenerDelegate { + @NonNull private final SoundEffectListener mSoundEffectListener; + @NonNull private final Executor mExecutor; + + private SoundEffectListenerDelegate(Executor executor, + SoundEffectListener soundEffectCallback) { + mSoundEffectListener = soundEffectCallback; + mExecutor = executor; + } + + public void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType) { + mExecutor.execute(() -> mSoundEffectListener.onPlaySoundEffect(effectType)); + } + } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index db163dcae395..5985ce45e4d5 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -34,6 +34,7 @@ import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceIntentInterceptor; +import android.companion.virtual.IVirtualDeviceSoundEffectListener; import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; @@ -119,6 +120,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final VirtualDeviceParams mParams; private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>(); private final IVirtualDeviceActivityListener mActivityListener; + private final IVirtualDeviceSoundEffectListener mSoundEffectListener; @GuardedBy("mVirtualDeviceLock") private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>(); @NonNull @@ -170,6 +172,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub OnDeviceCloseListener onDeviceCloseListener, PendingTrampolineCallback pendingTrampolineCallback, IVirtualDeviceActivityListener activityListener, + IVirtualDeviceSoundEffectListener soundEffectListener, Consumer<ArraySet<Integer>> runningAppsChangedCallback, VirtualDeviceParams params) { this( @@ -184,6 +187,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub onDeviceCloseListener, pendingTrampolineCallback, activityListener, + soundEffectListener, runningAppsChangedCallback, params); } @@ -201,6 +205,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub OnDeviceCloseListener onDeviceCloseListener, PendingTrampolineCallback pendingTrampolineCallback, IVirtualDeviceActivityListener activityListener, + IVirtualDeviceSoundEffectListener soundEffectListener, Consumer<ArraySet<Integer>> runningAppsChangedCallback, VirtualDeviceParams params) { super(PermissionEnforcer.fromContext(context)); @@ -209,6 +214,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mAssociationInfo = associationInfo; mPendingTrampolineCallback = pendingTrampolineCallback; mActivityListener = activityListener; + mSoundEffectListener = soundEffectListener; mRunningAppsChangedCallback = runningAppsChangedCallback; mOwnerUid = ownerUid; mDeviceId = deviceId; @@ -937,6 +943,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub Toast.LENGTH_LONG, mContext.getMainLooper()); } + void playSoundEffect(int effectType) { + try { + mSoundEffectListener.onPlaySoundEffect(effectType); + } catch (RemoteException exception) { + Slog.w(TAG, "Unable to invoke sound effect listener", exception); + } + } + /** * Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true if * the intent matches any filter notifying the DisplayPolicyController to abort the diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index b0f2464ff52a..47ec80e5de5e 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -30,6 +30,7 @@ import android.companion.CompanionDeviceManager; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceManager; +import android.companion.virtual.IVirtualDeviceSoundEffectListener; import android.companion.virtual.VirtualDevice; import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; @@ -222,7 +223,8 @@ public class VirtualDeviceManagerService extends SystemService { String packageName, int associationId, @NonNull VirtualDeviceParams params, - @NonNull IVirtualDeviceActivityListener activityListener) { + @NonNull IVirtualDeviceActivityListener activityListener, + @NonNull IVirtualDeviceSoundEffectListener soundEffectListener) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CREATE_VIRTUAL_DEVICE, "createVirtualDevice"); @@ -246,7 +248,7 @@ public class VirtualDeviceManagerService extends SystemService { VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), associationInfo, token, callingUid, deviceId, cameraAccessController, this::onDeviceClosed, mPendingTrampolineCallback, activityListener, - runningAppsChangedCallback, params); + soundEffectListener, runningAppsChangedCallback, params); mVirtualDevices.put(deviceId, virtualDevice); return virtualDevice; } @@ -364,6 +366,18 @@ public class VirtualDeviceManagerService extends SystemService { } } + @Override // Binder call + public void playSoundEffect(int deviceId, int effectType) { + VirtualDeviceImpl virtualDevice; + synchronized (mVirtualDeviceManagerLock) { + virtualDevice = mVirtualDevices.get(deviceId); + } + + if (virtualDevice != null) { + virtualDevice.playSoundEffect(effectType); + } + } + @Nullable private AssociationInfo getAssociationInfo(String packageName, int associationId) { final UserHandle userHandle = getCallingUserHandle(); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 00d4a6dc9a8c..6a4435f480f5 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -51,6 +51,7 @@ import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceIntentInterceptor; +import android.companion.virtual.IVirtualDeviceSoundEffectListener; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; @@ -75,6 +76,7 @@ import android.hardware.input.VirtualMouseScrollEvent; import android.hardware.input.VirtualNavigationTouchpadConfig; import android.hardware.input.VirtualTouchEvent; import android.hardware.input.VirtualTouchscreenConfig; +import android.media.AudioManager; import android.net.MacAddress; import android.net.Uri; import android.os.Binder; @@ -223,6 +225,8 @@ public class VirtualDeviceManagerServiceTest { @Mock private IVirtualDeviceActivityListener mActivityListener; @Mock + private IVirtualDeviceSoundEffectListener mSoundEffectListener; + @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; @Mock private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener; @@ -1572,6 +1576,13 @@ public class VirtualDeviceManagerServiceTest { intent.filterEquals(blockedAppIntent)), any(), any()); } + @Test + public void playSoundEffect_callsSoundEffectListener() throws Exception { + mVdm.playSoundEffect(mDeviceImpl.getDeviceId(), AudioManager.FX_KEY_CLICK); + + verify(mSoundEffectListener).onPlaySoundEffect(AudioManager.FX_KEY_CLICK); + } + private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) { VirtualDeviceParams params = new VirtualDeviceParams.Builder() .setBlockedActivities(getBlockedActivities()) @@ -1585,7 +1596,8 @@ public class VirtualDeviceManagerServiceTest { mAssociationInfo, new Binder(), ownerUid, virtualDeviceId, mInputController, mSensorController, mCameraAccessController, /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId), - mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params); + mPendingTrampolineCallback, mActivityListener, mSoundEffectListener, + mRunningAppsChangedCallback, params); mVdms.addVirtualDevice(virtualDeviceImpl); return virtualDeviceImpl; } |