/* * 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.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; 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.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.VirtualTouchscreen; import android.hardware.input.VirtualTouchscreenConfig; import android.media.AudioManager; import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.view.Surface; 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.List; import java.util.Objects; 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; private final IVirtualDeviceManager mService; private final Context mContext; /** @hide */ public VirtualDeviceManager( @Nullable IVirtualDeviceManager service, @NonNull Context context) { mService = service; mContext = context; } /** * Creates a virtual device where applications can launch and receive input events injected by * the creator. * *

The {@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. */ @NonNull public List getVirtualDevices() { if (mService == null) { Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service."); return new ArrayList<>(); } try { return mService.getVirtualDevices(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns the device policy for the given virtual device and policy type. * *

In 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 */ public @VirtualDeviceParams.DevicePolicy int getDevicePolicy( int deviceId, @VirtualDeviceParams.PolicyType int policyType) { 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 */ public int getDeviceIdForDisplayId(int displayId) { 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(); } } /** * Checks whether the passed {@code deviceId} is a valid virtual device ID or not. * {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default * device which is not a virtual device. {@code deviceId} must correspond to a virtual device * created by {@link VirtualDeviceManager#createVirtualDevice(int, VirtualDeviceParams)}. * * @hide */ public boolean isValidVirtualDeviceId(int deviceId) { if (mService == null) { Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service."); return false; } try { return mService.isValidVirtualDeviceId(deviceId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns device-specific audio session id for audio playback. * * @param deviceId - id of the virtual audio device * @return Device specific session id to be used for audio playback (see * {@link AudioManager#generateAudioSessionId}) if virtual device has * {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to * {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device * is configured in context-aware mode. Otherwise * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned. * * @hide */ public int getAudioPlaybackSessionId(int deviceId) { if (mService == null) { return AUDIO_SESSION_ID_GENERATE; } try { return mService.getAudioPlaybackSessionId(deviceId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns device-specific audio session id for audio recording. * * @param deviceId - id of the virtual audio device * @return Device specific session id to be used for audio recording (see * {@link AudioManager#generateAudioSessionId}) if virtual device has * {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to * {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device * is configured in context-aware mode. Otherwise * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned. * * @hide */ public int getAudioRecordingSessionId(int deviceId) { if (mService == null) { return AUDIO_SESSION_ID_GENERATE; } try { return mService.getAudioRecordingSessionId(deviceId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Requests sound effect to be played on virtual device. * * @see AudioManager#playSoundEffect(int) * * @param deviceId - id of the virtual audio device * @param effectType the type of sound effect * * @hide */ public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) { 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(); } } /** * A representation of a virtual device. * *

A 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 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 getVirtualSensorList() { return mVirtualDeviceInternal.getVirtualSensorList(); } /** * Launches a given pending intent on the give display ID. * * @param displayId The display to launch the pending intent on. This display must be * created from this virtual device. * @param pendingIntent The pending intent to be launched. If the intent is an activity * intent, the activity will be started on the virtual display using * {@link android.app.ActivityOptions#setLaunchDisplayId}. If the intent is a service or * broadcast intent, an attempt will be made to catch activities started as a result of * sending the pending intent and move them to the given display. When it completes, * {@code listener} will be called with the status of whether the launch attempt is * successful or not. * @param executor The executor to run {@code launchCallback} on. * @param listener Listener that is called when the pending intent launching is complete. * The argument is {@link #LAUNCH_SUCCESS} if the launch successfully started an activity * on the virtual display, or one of the {@code LAUNCH_FAILED} status explaining why it * failed. */ public void launchPendingIntent( int displayId, @NonNull PendingIntent pendingIntent, @NonNull Executor executor, @NonNull IntConsumer listener) { Objects.requireNonNull(pendingIntent, "pendingIntent must not be null"); Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(listener, "listener must not be null"); mVirtualDeviceInternal.launchPendingIntent( displayId, pendingIntent, executor, listener); } /** * Creates a virtual display for this virtual device. All displays created on the same * device belongs to the same display group. * * @param width The width of the virtual display in pixels, must be greater than 0. * @param height The height of the virtual display in pixels, must be greater than 0. * @param densityDpi The density of the virtual display in dpi, must be greater than 0. * @param surface The surface to which the content of the virtual display should * be rendered, or null if there is none initially. The surface can also be set later * using {@link VirtualDisplay#setSurface(Surface)}. * @param flags A combination of virtual display flags accepted by * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are * automatically set for all virtual devices: * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC} and * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}. * @param executor The executor on which {@code callback} will be invoked. This is ignored * if {@code callback} is {@code null}. If {@code callback} is specified, this executor * must not be null. * @param callback Callback to call when the state of the {@link VirtualDisplay} changes * @return The newly created virtual display, or {@code null} if the application could * not create the virtual display. * * @see DisplayManager#createVirtualDisplay * * @deprecated use {@link #createVirtualDisplay(VirtualDisplayConfig, Executor, * VirtualDisplay.Callback)} */ @Deprecated @Nullable public VirtualDisplay createVirtualDisplay( @IntRange(from = 1) int width, @IntRange(from = 1) int height, @IntRange(from = 1) int densityDpi, @Nullable Surface surface, @VirtualDisplayFlag int flags, @Nullable @CallbackExecutor Executor executor, @Nullable VirtualDisplay.Callback callback) { // Currently this just uses the device ID, which means all of the virtual displays // created using the same virtual device will have the same name if they use this // deprecated API. The name should only be used for informational purposes, and not for // identifying the display in code. String virtualDisplayName = "VirtualDevice_" + getDeviceId(); VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( virtualDisplayName, width, height, densityDpi) .setFlags(flags); if (surface != null) { builder.setSurface(surface); } return mVirtualDeviceInternal.createVirtualDisplay(builder.build(), executor, callback); } /** * Creates a virtual display for this virtual device. All displays created on the same * device belongs to the same display group. * * @param config The configuration of the display. * @param executor The executor on which {@code callback} will be invoked. This is ignored * if {@code callback} is {@code null}. If {@code callback} is specified, this executor * must not be null. * @param callback Callback to call when the state of the {@link VirtualDisplay} changes * @return The newly created virtual display, or {@code null} if the application could * not create the virtual display. * * @see DisplayManager#createVirtualDisplay */ @Nullable public VirtualDisplay createVirtualDisplay( @NonNull VirtualDisplayConfig config, @Nullable @CallbackExecutor Executor executor, @Nullable VirtualDisplay.Callback callback) { Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualDisplay(config, executor, callback); } /** * Closes the virtual device, stopping and tearing down any virtual displays, associated * virtual audio device, and event injection that's currently in progress. */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { mVirtualDeviceInternal.close(); } /** * Creates a virtual dpad. * * @param config the configurations of the virtual dpad. */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) { Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualDpad(config); } /** * Creates a virtual keyboard. * * @param config the configurations of the virtual keyboard. */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) { Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualKeyboard(config); } /** * Creates a virtual keyboard. * * @param display the display that the events inputted through this device should target. * @param inputDeviceName the name of this keyboard device. * @param vendorId the PCI vendor id. * @param productId the product id, as defined by the vendor. * @see #createVirtualKeyboard(VirtualKeyboardConfig config) * @deprecated Use {@link #createVirtualKeyboard(VirtualKeyboardConfig config)} instead */ @Deprecated @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualDisplay display, @NonNull String inputDeviceName, int vendorId, int productId) { VirtualKeyboardConfig keyboardConfig = new VirtualKeyboardConfig.Builder() .setVendorId(vendorId) .setProductId(productId) .setInputDeviceName(inputDeviceName) .setAssociatedDisplayId(display.getDisplay().getDisplayId()) .build(); return mVirtualDeviceInternal.createVirtualKeyboard(keyboardConfig); } /** * Creates a virtual mouse. * * @param config the configurations of the virtual mouse. */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) { Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualMouse(config); } /** * Creates a virtual mouse. * * @param display the display that the events inputted through this device should target. * @param inputDeviceName the name of this mouse. * @param vendorId the PCI vendor id. * @param productId the product id, as defined by the vendor. * @see #createVirtualMouse(VirtualMouseConfig config) * @deprecated Use {@link #createVirtualMouse(VirtualMouseConfig config)} instead */ @Deprecated @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualMouse createVirtualMouse(@NonNull VirtualDisplay display, @NonNull String inputDeviceName, int vendorId, int productId) { VirtualMouseConfig mouseConfig = new VirtualMouseConfig.Builder() .setVendorId(vendorId) .setProductId(productId) .setInputDeviceName(inputDeviceName) .setAssociatedDisplayId(display.getDisplay().getDisplayId()) .build(); return mVirtualDeviceInternal.createVirtualMouse(mouseConfig); } /** * Creates a virtual touchscreen. * * @param config the configurations of the virtual touchscreen. */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualTouchscreen createVirtualTouchscreen( @NonNull VirtualTouchscreenConfig config) { Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualTouchscreen(config); } /** * Creates a virtual touchscreen. * * @param display the display that the events inputted through this device should target. * @param inputDeviceName the name of this touchscreen device. * @param vendorId the PCI vendor id. * @param productId the product id, as defined by the vendor. * @see #createVirtualTouchscreen(VirtualTouchscreenConfig config) * @deprecated Use {@link #createVirtualTouchscreen(VirtualTouchscreenConfig config)} * instead */ @Deprecated @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualTouchscreen createVirtualTouchscreen(@NonNull VirtualDisplay display, @NonNull String inputDeviceName, int vendorId, int productId) { final Point size = new Point(); display.getDisplay().getSize(size); VirtualTouchscreenConfig touchscreenConfig = new VirtualTouchscreenConfig.Builder(size.x, size.y) .setVendorId(vendorId) .setProductId(productId) .setInputDeviceName(inputDeviceName) .setAssociatedDisplayId(display.getDisplay().getDisplayId()) .build(); return mVirtualDeviceInternal.createVirtualTouchscreen(touchscreenConfig); } /** * Creates a virtual touchpad in navigation mode. * *

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 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 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); } /** * 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) @NonNull public void setShowPointerIcon(boolean showPointerIcon) { mVirtualDeviceInternal.setShowPointerIcon(showPointerIcon); } /** * 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 #removeActivityListener(ActivityListener) */ 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. * *

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