summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt6
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceManager.aidl14
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl34
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java101
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java14
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java14
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;
}