/* * Copyright (C) 2021 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; import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.companion.AssociationInfo; import android.companion.virtual.audio.VirtualAudioDevice; import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; import android.companion.virtual.camera.VirtualCamera; import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Point; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.VirtualDisplayFlag; import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplayConfig; import android.hardware.input.VirtualDpad; import android.hardware.input.VirtualDpadConfig; import android.hardware.input.VirtualKeyboard; import android.hardware.input.VirtualKeyboardConfig; import android.hardware.input.VirtualMouse; import android.hardware.input.VirtualMouseConfig; import android.hardware.input.VirtualNavigationTouchpad; import android.hardware.input.VirtualNavigationTouchpadConfig; import android.hardware.input.VirtualStylus; import android.hardware.input.VirtualStylusConfig; import android.hardware.input.VirtualTouchscreen; import android.hardware.input.VirtualTouchscreenConfig; import android.media.AudioManager; import android.os.Binder; import android.os.Looper; import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; import android.view.Display; import android.view.Surface; import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.IntConsumer; /** * System level service for creation and management of virtual devices. * *
VirtualDeviceManager enables interactive sharing of capabilities between the host Android * device and a remote device. * *
Not to be confused with the Android Studio's Virtual Device Manager, which allows * for device emulation. */ @SystemService(Context.VIRTUAL_DEVICE_SERVICE) public final class VirtualDeviceManager { private static final String TAG = "VirtualDeviceManager"; /** * Broadcast Action: A Virtual Device was removed. * *
This is a protected intent that can only be sent by the system.
* * @hide */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_VIRTUAL_DEVICE_REMOVED = "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED"; /** * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}. * Contains the identifier of the virtual device, which was removed. * * @hide */ public static final String EXTRA_VIRTUAL_DEVICE_ID = "android.companion.virtual.extra.VIRTUAL_DEVICE_ID"; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = "LAUNCH_", value = { LAUNCH_SUCCESS, LAUNCH_FAILURE_PENDING_INTENT_CANCELED, LAUNCH_FAILURE_NO_ACTIVITY}) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface PendingIntentLaunchStatus {} /** * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch was * successful. * * @hide */ @SystemApi public static final int LAUNCH_SUCCESS = 0; /** * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch failed * because the pending intent was canceled. * * @hide */ @SystemApi public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; /** * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch failed * because no activity starts were detected as a result of calling the pending intent. * * @hide */ @SystemApi public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; /** * Persistent device identifier corresponding to the default device. * * @see Context#DEVICE_ID_DEFAULT * @see VirtualDevice#getPersistentDeviceId() * * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) public static final String PERSISTENT_DEVICE_ID_DEFAULT = "default:" + Context.DEVICE_ID_DEFAULT; private final IVirtualDeviceManager mService; private final Context mContext; @GuardedBy("mVirtualDeviceListeners") private final ListThe {@link android.Manifest.permission#CREATE_VIRTUAL_DEVICE} permission is required to * create virtual devices, which is only available to system apps holding specific roles. * * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from * Companion Device Manager. Virtual devices must have a corresponding association with CDM in * order to be created. * @param params The parameters for creating virtual devices. See {@link VirtualDeviceParams} * for the available options. * @return The created virtual device. * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualDevice createVirtualDevice( int associationId, @NonNull VirtualDeviceParams params) { Objects.requireNonNull(params, "params must not be null"); try { return new VirtualDevice(mService, mContext, associationId, params); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns the details of all available virtual devices. * *
The returned objects are read-only representations that expose the properties of all * existing virtual devices.
* *Note that if a virtual device is closed and becomes invalid, the returned objects will * not be updated and may contain stale values.
*/ // TODO(b/310912420): Add "Use a VirtualDeviceListener for real time updates of the // availability of virtual devices." in the note paragraph above with a link annotation. @NonNull public ListThe returned object is a read-only representation of the virtual device that expose its * properties.
* *Note that if the virtual device is closed and becomes invalid, the returned object will * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real * time updates of the availability of virtual devices.
* * @return the virtual device with the requested ID, or {@code null} if no such device exists or * it has already been closed. */ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) @Nullable public android.companion.virtual.VirtualDevice getVirtualDevice(int deviceId) { if (mService == null) { Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service."); return null; } if (deviceId == Context.DEVICE_ID_INVALID || deviceId == Context.DEVICE_ID_DEFAULT) { return null; // Don't even bother making a Binder call. } try { return mService.getVirtualDevice(deviceId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Registers a virtual device listener to receive notifications when virtual devices are created * or closed. * * @param executor The executor where the listener is executed on. * @param listener The listener to add. * @see #unregisterVirtualDeviceListener */ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) public void registerVirtualDeviceListener( @NonNull @CallbackExecutor Executor executor, @NonNull VirtualDeviceListener listener) { if (mService == null) { Log.w(TAG, "Failed to register listener; no virtual device manager service."); return; } final VirtualDeviceListenerDelegate delegate = new VirtualDeviceListenerDelegate(Objects.requireNonNull(executor), Objects.requireNonNull(listener)); synchronized (mVirtualDeviceListeners) { try { mService.registerVirtualDeviceListener(delegate); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mVirtualDeviceListeners.add(delegate); } } /** * Unregisters a virtual device listener previously registered with * {@link #registerVirtualDeviceListener}. * * @param listener The listener to unregister. * @see #registerVirtualDeviceListener */ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) public void unregisterVirtualDeviceListener(@NonNull VirtualDeviceListener listener) { if (mService == null) { Log.w(TAG, "Failed to unregister listener; no virtual device manager service."); return; } Objects.requireNonNull(listener); synchronized (mVirtualDeviceListeners) { final IteratorIn case the virtual device identifier is not valid, or there's no explicitly specified * policy for that device and policy type, then * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned. * * @hide */ @SuppressLint("UnflaggedApi") // @TestApi without associated feature. @TestApi public @VirtualDeviceParams.DevicePolicy int getDevicePolicy( int deviceId, @VirtualDeviceParams.PolicyType int policyType) { if (deviceId == Context.DEVICE_ID_DEFAULT) { // Avoid unnecessary binder call, for default device, policy will be always default. return VirtualDeviceParams.DEVICE_POLICY_DEFAULT; } if (mService == null) { Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service."); return VirtualDeviceParams.DEVICE_POLICY_DEFAULT; } try { return mService.getDevicePolicy(deviceId, policyType); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns the ID of the device which owns the display with the given ID. * * @hide */ @SuppressLint("UnflaggedApi") // @TestApi without associated feature. @TestApi public int getDeviceIdForDisplayId(int displayId) { if (displayId == Display.DEFAULT_DISPLAY || displayId == Display.INVALID_DISPLAY) { // Avoid unnecessary binder call for default / invalid display id. return Context.DEVICE_ID_DEFAULT; } if (mService == null) { Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service."); return Context.DEVICE_ID_DEFAULT; } try { return mService.getDeviceIdForDisplayId(displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Get the display name for a given persistent device ID. * *
This will work even if currently there is no valid virtual device with the given * persistent ID, as long as such a device has been created or can be created.
* * @return the display name associated with the given persistent device ID, or {@code null} if * the persistent ID is invalid or does not correspond to a virtual device. * * @hide */ // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId() @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) @SystemApi @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String persistentDeviceId) { if (mService == null) { Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service."); return null; } try { return mService.getDisplayNameForPersistentDeviceId( Objects.requireNonNull(persistentDeviceId)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns all current persistent device IDs, including the ones for which no virtual device * exists, as long as one may have existed or can be created. * * @hide */ // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId() @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) @SystemApi @NonNull public SetA virtual device can have its own virtual displays, audio input/output, sensors, etc. * The creator of a virtual device can take the output from the virtual display and stream it * over to another device, and inject input and sensor events that are received from the remote * device. * *
This object is only used by the virtual device creator and allows them to manage the * device's behavior, peripherals, and the user interaction with that device. * *
Not to be confused with {@link android.companion.virtual.VirtualDevice}, * which is a read-only representation exposing the properties of an existing virtual device. * * @hide */ @SystemApi public static class VirtualDevice implements AutoCloseable { private final VirtualDeviceInternal mVirtualDeviceInternal; @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) private VirtualDevice( IVirtualDeviceManager service, Context context, int associationId, VirtualDeviceParams params) throws RemoteException { mVirtualDeviceInternal = new VirtualDeviceInternal(service, context, associationId, params); } /** * Returns the unique ID of this virtual device. */ public int getDeviceId() { return mVirtualDeviceInternal.getDeviceId(); } /** * Returns the persistent ID of this virtual device. */ @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) public @Nullable String getPersistentDeviceId() { return mVirtualDeviceInternal.getPersistentDeviceId(); } /** * Returns a new context bound to this device. * *
This is a convenience method equivalent to calling
* {@link Context#createDeviceContext(int)} with the id of this device.
*/
public @NonNull Context createContext() {
return mVirtualDeviceInternal.createContext();
}
/**
* Returns this device's sensors.
*
* @see VirtualDeviceParams.Builder#addVirtualSensorConfig
*
* @return A list of all sensors for this device, or an empty list if no sensors exist.
*/
@NonNull
public List Policies define the system behavior that may be specific for this virtual device. The
* given policy must be able to be changed dynamically during the lifetime of the device.
*
* @param policyType the type of policy, i.e. which behavior to specify a policy for.
* @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
*
* @see VirtualDeviceParams#POLICY_TYPE_RECENTS
* @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
*/
@FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
}
/**
* Specifies a component name to be exempt from the current activity launch policy.
*
* If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
* launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
* then the specified component will be blocked from launching.
* If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
* by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
* specified component will be allowed to launch. Note that changing the activity launch policy will clear current set of exempt
* components. If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
* launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
* then the specified component will be allowed to launch.
* If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
* by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
* specified component will be blocked from launching. Note that changing the activity launch policy will clear current set of exempt
* components. A touchpad in navigation mode means that its events are interpreted as navigation
* events (up, down, etc) instead of using them to update a cursor's absolute position. If
* the events are not consumed they are converted to DPAD events and delivered to the target
* again.
*
* @param config the configurations of the virtual navigation touchpad.
* @see android.view.InputDevice#SOURCE_TOUCH_NAVIGATION
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualNavigationTouchpad createVirtualNavigationTouchpad(
@NonNull VirtualNavigationTouchpadConfig config) {
return mVirtualDeviceInternal.createVirtualNavigationTouchpad(config);
}
/**
* Creates a virtual stylus.
*
* @param config the touchscreen configurations for the virtual stylus.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
public VirtualStylus createVirtualStylus(
@NonNull VirtualStylusConfig config) {
return mVirtualDeviceInternal.createVirtualStylus(config);
}
/**
* Creates a VirtualAudioDevice, capable of recording audio emanating from this device,
* or injecting audio from another device.
*
* Note: One {@link VirtualDevice} can only create one {@link VirtualAudioDevice}, so
* calling this method multiple times will return the same instance. When
* {@link VirtualDevice#close()} is called, the associated {@link VirtualAudioDevice} will
* also be closed automatically.
*
* @param display The target virtual display to capture from and inject into.
* @param executor The {@link Executor} object for the thread on which to execute
* the callback. If Note: When there are no activities running on the virtual display, the
* {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
* should be cleared when {@link #onDisplayEmpty(int)} is called.
*
* @param displayId The display ID on which the activity change happened.
* @param topActivity The component name of the top activity.
* @deprecated Use {@link #onTopActivityChanged(int, ComponentName, int)} instead
*/
void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity);
/**
* Called when the top activity is changed.
*
* Note: When there are no activities running on the virtual display, the
* {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
* should be cleared when {@link #onDisplayEmpty(int)} is called.
*
* @param displayId The display ID on which the activity change happened.
* @param topActivity The component name of the top activity.
* @param userId The user ID associated with the top activity.
*/
default void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
@UserIdInt int userId) {}
/**
* Called when the display becomes empty (e.g. if the user hits back on the last
* activity of the root task).
*
* @param displayId The display ID that became empty.
*/
void onDisplayEmpty(int displayId);
}
/**
* Interceptor interface to be called when an intent matches the IntentFilter passed into {@link
* VirtualDevice#registerIntentInterceptor}. When the interceptor is called after matching the
* IntentFilter, the intended activity launch will be aborted and alternatively replaced by
* the interceptor's receiver.
*
* @hide
*/
@SystemApi
public interface IntentInterceptorCallback {
/**
* Called when an intent that matches the IntentFilter registered in {@link
* VirtualDevice#registerIntentInterceptor} is intercepted for the virtual device to
* handle.
*
* @param intent The intent that has been intercepted by the interceptor.
*/
void onIntentIntercepted(@NonNull Intent intent);
}
/**
* 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 android.media.AudioManager.SystemSoundEffect
*/
void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType);
}
/**
* Listener for changes in the available virtual devices.
*
* @see #registerVirtualDeviceListener
*/
@FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS)
public interface VirtualDeviceListener {
/**
* Called whenever a new virtual device has been added to the system.
* Use {@link VirtualDeviceManager#getVirtualDevice(int)} to get more information about
* the device.
*
* @param deviceId The id of the virtual device that was added.
*/
default void onVirtualDeviceCreated(int deviceId) {}
/**
* Called whenever a virtual device has been removed from the system.
*
* @param deviceId The id of the virtual device that was removed.
*/
default void onVirtualDeviceClosed(int deviceId) {}
}
/**
* A wrapper for {@link VirtualDeviceListener} that executes callbacks on the given executor.
*/
private static class VirtualDeviceListenerDelegate extends IVirtualDeviceListener.Stub {
private final VirtualDeviceListener mListener;
private final Executor mExecutor;
private VirtualDeviceListenerDelegate(Executor executor, VirtualDeviceListener listener) {
mExecutor = executor;
mListener = listener;
}
@Override
public void onVirtualDeviceCreated(int deviceId) {
final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mListener.onVirtualDeviceCreated(deviceId));
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void onVirtualDeviceClosed(int deviceId) {
final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mListener.onVirtualDeviceClosed(deviceId));
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
}
null
, the {@link Executor} associated with the main
* {@link Looper} will be used.
* @param callback Interface to be notified when playback or recording configuration of
* applications running on virtual display is changed.
* @return A {@link VirtualAudioDevice} instance.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualAudioDevice createVirtualAudioDevice(
@NonNull VirtualDisplay display,
@Nullable Executor executor,
@Nullable AudioConfigurationChangeCallback callback) {
Objects.requireNonNull(display, "display must not be null");
return mVirtualDeviceInternal.createVirtualAudioDevice(display, executor, callback);
}
/**
* Creates a new virtual camera with the given {@link VirtualCameraConfig}. A virtual device
* can create a virtual camera only if it has
* {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} as its
* {@link VirtualDeviceParams#POLICY_TYPE_CAMERA}.
*
* @param config camera configuration.
* @return newly created camera.
* @throws UnsupportedOperationException if virtual camera isn't supported on this device.
* @see VirtualDeviceParams#POLICY_TYPE_CAMERA
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) {
if (!Flags.virtualCamera()) {
throw new UnsupportedOperationException(
"Flag is not enabled: %s".formatted(Flags.FLAG_VIRTUAL_CAMERA));
}
return mVirtualDeviceInternal.createVirtualCamera(Objects.requireNonNull(config));
}
/**
* Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
*
* @param showPointerIcon True if the pointer should be shown; false otherwise. The default
* visibility is true.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setShowPointerIcon(boolean showPointerIcon) {
mVirtualDeviceInternal.setShowPointerIcon(showPointerIcon);
}
/**
* Specifies the IME behavior on the given display. By default, all displays created by
* virtual devices have {@link WindowManager#DISPLAY_IME_POLICY_LOCAL}.
*
* @param displayId the ID of the display to change the IME policy for. It must be owned by
* this virtual device.
* @param policy the IME policy to use on that display
* @throws SecurityException if the display is not owned by this device or is not
* {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted}
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
if (Flags.vdmCustomIme()) {
mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
}
}
/**
* Adds an activity listener to listen for events such as top activity change or virtual
* display task stack became empty.
*
* @param executor The executor where the listener is executed on.
* @param listener The listener to add.
* @see #removeActivityListener(ActivityListener)
*/
public void addActivityListener(
@CallbackExecutor @NonNull Executor executor, @NonNull ActivityListener listener) {
mVirtualDeviceInternal.addActivityListener(executor, listener);
}
/**
* Removes an activity listener previously added with {@link #addActivityListener}.
*
* @param listener The listener to remove.
* @see #addActivityListener(Executor, ActivityListener)
*/
public void removeActivityListener(@NonNull ActivityListener listener) {
mVirtualDeviceInternal.removeActivityListener(listener);
}
/**
* Adds a sound effect listener.
*
* @param executor The executor where the listener is executed on.
* @param soundEffectListener The listener to add.
* @see #removeSoundEffectListener(SoundEffectListener)
*/
public void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor,
@NonNull SoundEffectListener soundEffectListener) {
mVirtualDeviceInternal.addSoundEffectListener(executor, soundEffectListener);
}
/**
* Removes a sound effect listener previously added with {@link #addSoundEffectListener}.
*
* @param soundEffectListener The listener to remove.
* @see #addSoundEffectListener(Executor, SoundEffectListener)
*/
public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) {
mVirtualDeviceInternal.removeSoundEffectListener(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.
*
* @param interceptorFilter The filter to match intents intended for interception.
* @param executor The executor where the interceptor is executed on.
* @param interceptorCallback The callback called when an intent matching interceptorFilter
* is intercepted.
* @see #unregisterIntentInterceptor(IntentInterceptorCallback)
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void registerIntentInterceptor(
@NonNull IntentFilter interceptorFilter,
@CallbackExecutor @NonNull Executor executor,
@NonNull IntentInterceptorCallback interceptorCallback) {
mVirtualDeviceInternal.registerIntentInterceptor(
interceptorFilter, executor, interceptorCallback);
}
/**
* Unregisters the intent interceptor previously registered with
* {@link #registerIntentInterceptor}.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterIntentInterceptor(
@NonNull IntentInterceptorCallback interceptorCallback) {
mVirtualDeviceInternal.unregisterIntentInterceptor(interceptorCallback);
}
}
/**
* Listener for activity changes in this virtual device.
*
* @hide
*/
@SystemApi
public interface ActivityListener {
/**
* Called when the top activity is changed.
*
*