summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt3
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl18
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceInternal.java50
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java76
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceParams.java17
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java4
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java89
7 files changed, 239 insertions, 18 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fd0262ef5771..bed1b43fdbe7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3465,6 +3465,7 @@ package android.companion.virtual {
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName, int);
method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
@@ -3489,8 +3490,10 @@ package android.companion.virtual {
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName, int);
method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int, int);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDisplayImePolicy(int, int);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 065383938e95..a56bc02b4bc2 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -112,6 +112,24 @@ interface IVirtualDevice {
void removeActivityPolicyExemption(in ComponentName exemption);
/**
+ * Specifies a policy for this virtual device on the given display.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void setDevicePolicyForDisplay(int displayId, int policyType, int devicePolicy);
+
+ /**
+ * Adds an exemption to the default activity launch policy on the given display.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void addActivityPolicyExemptionForDisplay(int displayId, in ComponentName exemption);
+
+ /**
+ * Removes an exemption to the default activity launch policy on the given display.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void removeActivityPolicyExemptionForDisplay(int displayId, in ComponentName exemption);
+
+ /**
* Notifies that an audio session being started.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d3fcfc6f0ecc..d8899b24eda3 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -17,7 +17,11 @@
package android.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -282,6 +286,16 @@ public class VirtualDeviceInternal {
void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ switch (policyType) {
+ case POLICY_TYPE_RECENTS:
+ case POLICY_TYPE_CLIPBOARD:
+ case POLICY_TYPE_ACTIVITY:
+ case POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR:
+ break;
+ default:
+ throw new IllegalArgumentException("Device policy " + policyType
+ + " cannot be changed at runtime. ");
+ }
try {
mVirtualDevice.setDevicePolicy(policyType, devicePolicy);
} catch (RemoteException e) {
@@ -305,6 +319,42 @@ public class VirtualDeviceInternal {
}
}
+ void setDevicePolicyForDisplay(int displayId,
+ @VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ switch (policyType) {
+ case POLICY_TYPE_RECENTS:
+ case POLICY_TYPE_ACTIVITY:
+ break;
+ default:
+ throw new IllegalArgumentException("Device policy " + policyType
+ + " cannot be changed for a specific display. ");
+ }
+
+ try {
+ mVirtualDevice.setDevicePolicyForDisplay(displayId, policyType, devicePolicy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void addActivityPolicyExemptionForDisplay(int displayId, @NonNull ComponentName componentName) {
+ try {
+ mVirtualDevice.addActivityPolicyExemptionForDisplay(displayId, componentName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void removeActivityPolicyExemptionForDisplay(int displayId,
+ @NonNull ComponentName componentName) {
+ try {
+ mVirtualDevice.removeActivityPolicyExemptionForDisplay(displayId, componentName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 296ca33cdaec..42da7e9e1815 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -739,6 +739,7 @@ public final class VirtualDeviceManager {
*
* @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.
+ * @throws IllegalArgumentException if the policy cannot be changed at runtime.
*
* @see VirtualDeviceParams#POLICY_TYPE_RECENTS
* @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
@@ -797,6 +798,81 @@ public final class VirtualDeviceManager {
}
/**
+ * Specifies a policy for this virtual device to be applied on the given virtual display.
+ * <p>
+ * Any policy specified for a particular display takes precedence over the policy specified
+ * for the device itself.
+ * </p>
+ *
+ * @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.
+ * @param displayId the ID of the display, for which to apply the policy.
+ * @throws IllegalArgumentException if the specified policy cannot be changed per
+ * display or if the specified display does not belong to the virtual device.
+ *
+ * @see #setDevicePolicy(int, int)
+ * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
+ * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setDevicePolicy(
+ @VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy,
+ int displayId) {
+ mVirtualDeviceInternal.setDevicePolicyForDisplay(displayId, policyType, devicePolicy);
+ }
+
+ /**
+ * Specifies a component name to be exempt from the given display's activity launch policy.
+ *
+ * <p>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.</p>
+ *
+ * <p>Note that changing the activity launch policy will clear current set of exempt
+ * components.</p>
+ * <p>Any change to the exemptions will only be applied for new activity launches.</p>
+ *
+ * @see #removeActivityPolicyExemption
+ * @see #setDevicePolicy
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void addActivityPolicyExemption(
+ @NonNull ComponentName componentName, int displayId) {
+ mVirtualDeviceInternal.addActivityPolicyExemptionForDisplay(
+ displayId, Objects.requireNonNull(componentName));
+ }
+
+ /**
+ * Makes the specified component name adhere to the given display's activity launch policy.
+ *
+ * <p>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.</p>
+ *
+ * <p>Note that changing the activity launch policy will clear current set of exempt
+ * components.</p>
+ *
+ * @see #addActivityPolicyExemption
+ * @see #setDevicePolicy
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void removeActivityPolicyExemption(
+ @NonNull ComponentName componentName, int displayId) {
+ mVirtualDeviceInternal.removeActivityPolicyExemptionForDisplay(
+ displayId, Objects.requireNonNull(componentName));
+ }
+
+ /**
* Creates a virtual dpad.
*
* @param config the configurations of the virtual dpad.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index f7f842f58511..c1fc51d3ebd4 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -159,7 +159,8 @@ public final class VirtualDeviceParams implements Parcelable {
* @hide
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
- POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA})
+ POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA,
+ POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -177,6 +178,17 @@ public final class VirtualDeviceParams implements Parcelable {
public @interface DynamicPolicyType {}
/**
+ * Policy types that can be dynamically changed for a specific display.
+ *
+ * @see VirtualDeviceManager.VirtualDevice#setDevicePolicyForDisplay
+ * @hide
+ */
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface DynamicDisplayPolicyType {}
+
+ /**
* Tells the sensor framework how to handle sensor requests from contexts associated with this
* virtual device, namely the sensors returned by
* {@link android.hardware.SensorManager#getSensorList}:
@@ -229,6 +241,8 @@ public final class VirtualDeviceParams implements Parcelable {
* @see VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption
* @see VirtualDeviceManager.VirtualDevice#removeActivityPolicyExemption
*/
+ // TODO(b/333443509): Update the documentation of custom policy and link to the new policy
+ // POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR
@FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
public static final int POLICY_TYPE_ACTIVITY = 3;
@@ -276,6 +290,7 @@ public final class VirtualDeviceParams implements Parcelable {
* experience on the virtual device.
* </ul>
*/
+ // TODO(b/333443509): Link to POLICY_TYPE_ACTIVITY
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
public static final int POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR = 6;
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 988a2132455b..0bf931ce2633 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -108,7 +108,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
private boolean mActivityLaunchAllowedByDefault;
@NonNull
@GuardedBy("mGenericWindowPolicyControllerLock")
- private final Set<ComponentName> mActivityPolicyExemptions;
+ private final ArraySet<ComponentName> mActivityPolicyExemptions;
private final boolean mCrossTaskNavigationAllowedByDefault;
@NonNull
private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
@@ -190,7 +190,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
mAttributionSource = attributionSource;
mAllowedUsers = allowedUsers;
mActivityLaunchAllowedByDefault = activityLaunchAllowedByDefault;
- mActivityPolicyExemptions = activityPolicyExemptions;
+ mActivityPolicyExemptions = new ArraySet<>(activityPolicyExemptions);
mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
mPermissionDialogComponent = permissionDialogComponent;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index ee7d0aef2189..29ddf64486bb 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -558,6 +558,36 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void addActivityPolicyExemptionForDisplay(
+ int displayId, @NonNull ComponentName componentName) {
+ super.addActivityPolicyExemptionForDisplay_enforcePermission();
+ if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ return;
+ }
+ synchronized (mVirtualDeviceLock) {
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
+ mVirtualDisplays.get(displayId).getWindowPolicyController()
+ .addActivityPolicyExemption(componentName);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void removeActivityPolicyExemptionForDisplay(
+ int displayId, @NonNull ComponentName componentName) {
+ super.removeActivityPolicyExemptionForDisplay_enforcePermission();
+ if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ return;
+ }
+ synchronized (mVirtualDeviceLock) {
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
+ mVirtualDisplays.get(displayId).getWindowPolicyController()
+ .removeActivityPolicyExemption(componentName);
+ }
+ }
+
private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
throws PendingIntent.CanceledException {
final ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
@@ -657,12 +687,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Nullable IAudioConfigChangedCallback configChangedCallback) {
super.onAudioSessionStarting_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplays.contains(displayId)) {
- throw new SecurityException(
- "Cannot start audio session for a display not associated with this virtual "
- + "device");
- }
-
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
if (mVirtualAudioController == null) {
mVirtualAudioController = new VirtualAudioController(mContext, mAttributionSource);
GenericWindowPolicyController gwpc = mVirtualDisplays.get(
@@ -706,6 +731,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
break;
case POLICY_TYPE_ACTIVITY:
synchronized (mVirtualDeviceLock) {
+ if (getDevicePolicy(policyType) != devicePolicy) {
+ mActivityPolicyExemptions.clear();
+ }
mDevicePolicies.put(policyType, devicePolicy);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
mVirtualDisplays.valueAt(i).getWindowPolicyController()
@@ -736,6 +764,33 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setDevicePolicyForDisplay(int displayId,
+ @VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ super.setDevicePolicyForDisplay_enforcePermission();
+ if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ return;
+ }
+ synchronized (mVirtualDeviceLock) {
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
+ switch (policyType) {
+ case POLICY_TYPE_RECENTS:
+ mVirtualDisplays.get(displayId).getWindowPolicyController()
+ .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ break;
+ case POLICY_TYPE_ACTIVITY:
+ mVirtualDisplays.get(displayId).getWindowPolicyController()
+ .setActivityLaunchDefaultAllowed(devicePolicy == DEVICE_POLICY_DEFAULT);
+ break;
+ default:
+ throw new IllegalArgumentException("Device policy " + policyType
+ + " cannot be changed for a specific display. ");
+ }
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
super.createVirtualDpad_enforcePermission();
Objects.requireNonNull(config);
@@ -1441,20 +1496,24 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@SuppressWarnings("AndroidFrameworkRequiresPermission")
private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
+ // The INJECT_EVENTS permission allows for injecting input to any window / display.
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INJECT_EVENTS)
- == PackageManager.PERMISSION_GRANTED) {
- // The INJECT_EVENTS permission allows for injecting input to any window / display.
- return;
- }
- synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplays.contains(displayId)) {
- throw new SecurityException(
- "Cannot create a virtual input device for display " + displayId
- + " which not associated with this virtual device");
+ != PackageManager.PERMISSION_GRANTED) {
+ synchronized (mVirtualDeviceLock) {
+ checkDisplayOwnedByVirtualDeviceLocked(displayId);
}
}
}
+ @GuardedBy("mVirtualDeviceLock")
+ private void checkDisplayOwnedByVirtualDeviceLocked(int displayId) {
+ if (!mVirtualDisplays.contains(displayId)) {
+ throw new SecurityException(
+ "Invalid displayId: Display " + displayId
+ + " is not associated with this virtual device");
+ }
+ }
+
/**
* Release resources tied to virtual display owned by this VirtualDevice instance.
*