diff options
8 files changed, 127 insertions, 3 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index f640ea25a22b..955858b9273f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9682,6 +9682,7 @@ package android.companion.virtual { method public int describeContents(); method public int getDeviceId(); method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds(); + method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName(); method @Nullable public String getName(); method @Nullable public String getPersistentDeviceId(); method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport(); diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index 4692f921beb2..ce883cddc952 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -44,6 +44,7 @@ public final class VirtualDevice implements Parcelable { private final int mId; private final @Nullable String mPersistentId; private final @Nullable String mName; + private final @Nullable CharSequence mDisplayName; /** * Creates a new instance of {@link VirtualDevice}. @@ -53,6 +54,18 @@ public final class VirtualDevice implements Parcelable { */ public VirtualDevice(@NonNull IVirtualDevice virtualDevice, int id, @Nullable String persistentId, @Nullable String name) { + this(virtualDevice, id, persistentId, name, null); + } + + /** + * Creates a new instance of {@link VirtualDevice}. Only to be used by the + * VirtualDeviceManagerService. + * + * @hide + */ + public VirtualDevice(@NonNull IVirtualDevice virtualDevice, int id, + @Nullable String persistentId, @Nullable String name, + @Nullable CharSequence displayName) { if (id <= Context.DEVICE_ID_DEFAULT) { throw new IllegalArgumentException("VirtualDevice ID must be greater than " + Context.DEVICE_ID_DEFAULT); @@ -61,6 +74,7 @@ public final class VirtualDevice implements Parcelable { mId = id; mPersistentId = persistentId; mName = name; + mDisplayName = displayName; } private VirtualDevice(@NonNull Parcel parcel) { @@ -68,6 +82,7 @@ public final class VirtualDevice implements Parcelable { mId = parcel.readInt(); mPersistentId = parcel.readString8(); mName = parcel.readString8(); + mDisplayName = parcel.readCharSequence(); } /** @@ -112,6 +127,15 @@ public final class VirtualDevice implements Parcelable { } /** + * Returns the human-readable name of the virtual device, if defined, which is suitable to be + * shown in UI. + */ + @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) + public @Nullable CharSequence getDisplayName() { + return mDisplayName; + } + + /** * Returns the IDs of all virtual displays that belong to this device, if any. * * <p>The actual {@link android.view.Display} objects can be obtained by passing the returned @@ -156,6 +180,7 @@ public final class VirtualDevice implements Parcelable { dest.writeInt(mId); dest.writeString8(mPersistentId); dest.writeString8(mName); + dest.writeCharSequence(mDisplayName); } @Override @@ -165,6 +190,7 @@ public final class VirtualDevice implements Parcelable { + " mId=" + mId + " mPersistentId=" + mPersistentId + " mName=" + mName + + " mDisplayName=" + mDisplayName + ")"; } diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index 3e96c96d8d17..d0e13cd977ef 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -34,3 +34,10 @@ flag { description: "Enable Virtual Camera" bug: "270352264" } + +flag { + name: "stream_permissions" + namespace: "virtual_devices" + description: "Enable streaming permission dialogs to Virtual Devices" + bug: "291737919" +} 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 a3ccb168aa4e..b56b47f9c727 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -113,6 +113,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController private final boolean mCrossTaskNavigationAllowedByDefault; @NonNull private final ArraySet<ComponentName> mCrossTaskNavigationExemptions; + @Nullable + private final ComponentName mPermissionDialogComponent; private final Object mGenericWindowPolicyControllerLock = new Object(); @Nullable private final ActivityBlockedCallback mActivityBlockedCallback; private int mDisplayId = Display.INVALID_DISPLAY; @@ -171,6 +173,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @NonNull Set<ComponentName> activityPolicyExemptions, boolean crossTaskNavigationAllowedByDefault, @NonNull Set<ComponentName> crossTaskNavigationExemptions, + @Nullable ComponentName permissionDialogComponent, @Nullable ActivityListener activityListener, @Nullable PipBlockedCallback pipBlockedCallback, @Nullable ActivityBlockedCallback activityBlockedCallback, @@ -185,6 +188,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController mActivityPolicyExemptions = activityPolicyExemptions; mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault; mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions); + mPermissionDialogComponent = permissionDialogComponent; mActivityBlockedCallback = activityBlockedCallback; setInterestedWindowFlags(windowFlags, systemWindowFlags); mActivityListener = activityListener; @@ -309,6 +313,13 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController return false; } + // mPermissionDialogComponent being null means we don't want to block permission Dialogs + // based on FLAG_STREAM_PERMISSIONS + if (mPermissionDialogComponent != null + && mPermissionDialogComponent.equals(activityComponent)) { + return false; + } + return true; } 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 203a152ccc73..a2e4d2cc6929 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -24,6 +24,7 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS; +import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; @@ -204,6 +205,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @GuardedBy("mVirtualDeviceLock") @NonNull private final Set<ComponentName> mActivityPolicyExemptions; + private final ComponentName mPermissionDialogComponent; private ActivityListener createListenerAdapter() { return new ActivityListener() { @@ -317,6 +319,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs()); mCameraAccessController = cameraAccessController; mCameraAccessController.startObservingIfNeeded(); + if (!Flags.streamPermissions()) { + mPermissionDialogComponent = getPermissionDialogComponent(); + } else { + mPermissionDialogComponent = null; + } try { token.linkToDeath(this, 0); } catch (RemoteException e) { @@ -324,8 +331,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } mVirtualDeviceLog.logCreated(deviceId, mOwnerUid); - mPublicVirtualDeviceObject = new VirtualDevice( - this, getDeviceId(), getPersistentDeviceId(), mParams.getName()); + if (Flags.vdmPublicApis()) { + mPublicVirtualDeviceObject = new VirtualDevice( + this, getDeviceId(), getPersistentDeviceId(), mParams.getName(), + getDisplayName()); + } else { + mPublicVirtualDeviceObject = new VirtualDevice( + this, getDeviceId(), getPersistentDeviceId(), mParams.getName()); + } if (Flags.dynamicPolicy()) { mActivityPolicyExemptions = new ArraySet<>( @@ -951,6 +964,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub /*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault ? mParams.getBlockedCrossTaskNavigations() : mParams.getAllowedCrossTaskNavigations(), + mPermissionDialogComponent, createListenerAdapter(), this::onEnteringPipBlocked, this::onActivityBlocked, @@ -963,6 +977,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return gwpc; } + private ComponentName getPermissionDialogComponent() { + Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); + PackageManager packageManager = mContext.getPackageManager(); + intent.setPackage(packageManager.getPermissionControllerPackageName()); + return intent.resolveActivity(packageManager); + } + int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig, @NonNull IVirtualDisplayCallback callback, String packageName) { GenericWindowPolicyController gwpc; diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 41af9e31dbdd..1e6306cb4071 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -57,6 +57,7 @@ import android.companion.virtual.IVirtualDeviceSoundEffectListener; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; +import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.IVirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorCallback; @@ -100,6 +101,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.WorkSource; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArraySet; @@ -211,6 +213,9 @@ public class VirtualDeviceManagerServiceTest { private static final String TEST_SITE = "http://test"; @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Rule public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( InstrumentationRegistry.getInstrumentation().getUiAutomation(), Manifest.permission.CREATE_VIRTUAL_DEVICE); @@ -328,6 +333,11 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS); + mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY); + mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS); + mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME); + doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); @@ -1450,6 +1460,50 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void openPermissionControllerOnVirtualDisplay_displayOnRemoteDevices_startsWhenFlagIsEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( + DISPLAY_ID_1); + doNothing().when(mContext).startActivityAsUser(any(), any(), any()); + + ActivityInfo activityInfo = getActivityInfo( + PERMISSION_CONTROLLER_PACKAGE_NAME, + PERMISSION_CONTROLLER_PACKAGE_NAME, + /* displayOnRemoveDevices */ true, + /* targetDisplayCategory */ null); + Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( + activityInfo, mAssociationInfo.getDisplayName()); + gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false); + + verify(mContext, never()).startActivityAsUser(argThat(intent -> + intent.filterEquals(blockedAppIntent)), any(), any()); + } + + @Test + public void openPermissionControllerOnVirtualDisplay_dontDisplayOnRemoteDevices_startsWhenFlagIsEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( + DISPLAY_ID_1); + doNothing().when(mContext).startActivityAsUser(any(), any(), any()); + + ActivityInfo activityInfo = getActivityInfo( + PERMISSION_CONTROLLER_PACKAGE_NAME, + PERMISSION_CONTROLLER_PACKAGE_NAME, + /* displayOnRemoveDevices */ false, + /* targetDisplayCategory */ null); + Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( + activityInfo, mAssociationInfo.getDisplayName()); + gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false); + + verify(mContext).startActivityAsUser(argThat(intent -> + intent.filterEquals(blockedAppIntent)), any(), any()); + } + + @Test public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() { addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java index c65452aa2fa1..90d9452fb9c3 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java @@ -49,6 +49,7 @@ public class VirtualDeviceTest { private static final int VIRTUAL_DEVICE_ID = 42; private static final String PERSISTENT_ID = "persistentId"; private static final String DEVICE_NAME = "VirtualDeviceName"; + private static final String DISPLAY_NAME = "DisplayName"; @Mock private IVirtualDevice mVirtualDevice; @@ -87,7 +88,8 @@ public class VirtualDeviceTest { @Test public void parcelable_shouldRecreateSuccessfully() { VirtualDevice originalDevice = - new VirtualDevice(mVirtualDevice, VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME); + new VirtualDevice(mVirtualDevice, VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME, + DISPLAY_NAME); Parcel parcel = Parcel.obtain(); originalDevice.writeToParcel(parcel, 0); parcel.setDataPosition(0); @@ -96,6 +98,7 @@ public class VirtualDeviceTest { assertThat(device.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID); assertThat(device.getPersistentDeviceId()).isEqualTo(PERSISTENT_ID); assertThat(device.getName()).isEqualTo(DEVICE_NAME); + assertThat(device.getDisplayName().toString()).isEqualTo(DISPLAY_NAME); } @RequiresFlagsEnabled(Flags.FLAG_VDM_PUBLIC_APIS) diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java index 1c48b8aa79f9..ef5270e8f003 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java @@ -82,6 +82,7 @@ public class VirtualAudioControllerTest { /* activityPolicyExemptions= */ new ArraySet<>(), /* crossTaskNavigationAllowedByDefault= */ true, /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent */ null, /* activityListener= */ null, /* pipBlockedCallback= */ null, /* activityBlockedCallback= */ null, |