Merge "Add VDM APIs to support permission streaming" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index f640ea2..955858b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9682,6 +9682,7 @@
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 4692f92..ce883cd 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -44,6 +44,7 @@
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 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 @@
mId = id;
mPersistentId = persistentId;
mName = name;
+ mDisplayName = displayName;
}
private VirtualDevice(@NonNull Parcel parcel) {
@@ -68,6 +82,7 @@
mId = parcel.readInt();
mPersistentId = parcel.readString8();
mName = parcel.readString8();
+ mDisplayName = parcel.readCharSequence();
}
/**
@@ -112,6 +127,15 @@
}
/**
+ * 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 @@
dest.writeInt(mId);
dest.writeString8(mPersistentId);
dest.writeString8(mName);
+ dest.writeCharSequence(mDisplayName);
}
@Override
@@ -165,6 +190,7 @@
+ " 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 3e96c96..d0e13cd 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -34,3 +34,10 @@
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 a3ccb16..b56b47f 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 @@
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 @@
@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 @@
mActivityPolicyExemptions = activityPolicyExemptions;
mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
+ mPermissionDialogComponent = permissionDialogComponent;
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
@@ -309,6 +313,13 @@
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 203a152..a2e4d2c 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.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 @@
@GuardedBy("mVirtualDeviceLock")
@NonNull
private final Set<ComponentName> mActivityPolicyExemptions;
+ private final ComponentName mPermissionDialogComponent;
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@@ -317,6 +319,11 @@
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 @@
}
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 @@
/*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
+ mPermissionDialogComponent,
createListenerAdapter(),
this::onEnteringPipBlocked,
this::onActivityBlocked,
@@ -963,6 +977,13 @@
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 41af9e3..1e6306c 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.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.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 @@
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 @@
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 @@
}
@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 c65452a..90d9452 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 @@
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 @@
@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 @@
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 1c48b8a..ef5270e 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 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
+ /* permissionDialogComponent */ null,
/* activityListener= */ null,
/* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,