Merge "Add listener to sound effects played on VirtualDevice."

This commit is contained in:
Ján Sebechlebský
2023-01-18 18:52:05 +00:00
committed by Android (Google) Code Review
7 changed files with 192 additions and 9 deletions

View File

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

View File

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

View File

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

View File

@@ -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)));
@@ -946,6 +974,35 @@ public final class VirtualDeviceManager {
mActivityListeners.remove(listener);
}
/**
* 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
@@ -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));
}
}
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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;
}