/* * 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 List mVirtualDeviceListeners = new ArrayList<>(); /** @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.

* *

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 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 details of the virtual device with the given ID, if any. * *

The 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 Iterator it = mVirtualDeviceListeners.iterator(); while (it.hasNext()) { final VirtualDeviceListenerDelegate delegate = it.next(); if (delegate.mListener == listener) { try { mService.unregisterVirtualDeviceListener(delegate); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } it.remove(); } } } } /** * 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 */ @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 Set getAllPersistentDeviceIds() { if (mService == null) { Log.w(TAG, "Failed to retrieve persistent ids; no virtual device manager service."); return Collections.emptySet(); } try { return new ArraySet<>(mService.getAllPersistentDeviceIds()); } 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 */ @SuppressLint("UnflaggedApi") // @TestApi without associated feature. @TestApi 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 */ @SuppressLint("UnflaggedApi") // @TestApi without associated feature. @TestApi 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 */ @SuppressLint("UnflaggedApi") // @TestApi without associated feature. @TestApi 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(); } } /** * Returns whether the given display is an auto-mirror display owned by a virtual device. * * @hide */ @FlaggedApi(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR) @TestApi public boolean isVirtualDeviceOwnedMirrorDisplay(int displayId) { if (mService == null) { Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service."); return false; } try { return mService.isVirtualDeviceOwnedMirrorDisplay(displayId); } 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 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 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(); } /** * Specifies a policy for this virtual device. * *

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.

* * @see #removeActivityPolicyExemption * @see #setDevicePolicy */ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull ComponentName componentName) { mVirtualDeviceInternal.addActivityPolicyExemption( Objects.requireNonNull(componentName)); } /** * Makes the specified component name to adhere to the default 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 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.

* * @see #addActivityPolicyExemption * @see #setDevicePolicy */ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull ComponentName componentName) { mVirtualDeviceInternal.removeActivityPolicyExemption( Objects.requireNonNull(componentName)); } /** * 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 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 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. * *

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