diff options
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. * |