diff --git a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
index 2d6c77fdc41be..ca111a49235e4 100644
--- a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
+++ b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
@@ -36,6 +36,7 @@ public class BlockedAppStreamingActivity extends AlertActivity {
private static final String EXTRA_BLOCKED_ACTIVITY_INFO =
PACKAGE_NAME + ".extra.BLOCKED_ACTIVITY_INFO";
private static final String EXTRA_STREAMED_DEVICE = PACKAGE_NAME + ".extra.STREAMED_DEVICE";
+ private static final String BLOCKED_COMPONENT_SETTINGS = "com.android.settings";
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -56,13 +57,24 @@ public class BlockedAppStreamingActivity extends AlertActivity {
CharSequence streamedDeviceName = intent.getCharSequenceExtra(EXTRA_STREAMED_DEVICE);
if (!TextUtils.isEmpty(streamedDeviceName)) {
- mAlertParams.mTitle =
- TextUtils.equals(activityInfo.packageName,
- getPackageManager().getPermissionControllerPackageName())
- ? getString(R.string.app_streaming_blocked_title_for_permission_dialog)
- : getString(R.string.app_streaming_blocked_title, appLabel);
- mAlertParams.mMessage =
- getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ if (TextUtils.equals(activityInfo.packageName,
+ getPackageManager().getPermissionControllerPackageName())) {
+ mAlertParams.mTitle =
+ getString(R.string.app_streaming_blocked_title_for_permission_dialog);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message_for_permission_dialog,
+ streamedDeviceName);
+ } else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_SETTINGS)) {
+ mAlertParams.mTitle =
+ getString(R.string.app_streaming_blocked_title_for_settings_dialog);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ } else {
+ mAlertParams.mTitle =
+ getString(R.string.app_streaming_blocked_title, appLabel);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ }
} else {
mAlertParams.mTitle = getString(R.string.app_blocked_title);
mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3d73a02fca4ae..b5aa38b6729b8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5459,12 +5459,36 @@
%1$s unavailable
Permission needed
+
+ Camera unavailable
+
+ Continue on phone
+
+ Microphone unavailable
+
+ Android TV settings unavailable
+
+ Tablet settings unavailable
+
+ Phone settings unavailable
- This can’t be accessed on your %1$s at this time. Try on your Android TV device instead.
+ This can’t be accessed on your %1$s. Try on your Android TV device instead.
- This can’t be accessed on your %1$s at this time. Try on your tablet instead.
+ This can’t be accessed on your %1$s. Try on your tablet instead.
- This can’t be accessed on your %1$s at this time. Try on your phone instead.
+ This can’t be accessed on your %1$s. Try on your phone instead.
+
+ This can’t be accessed on your %1$s at this time. Try on your Android TV device instead.
+
+ This can’t be accessed on your %1$s at this time. Try on your tablet instead.
+
+ This can’t be accessed on your %1$s at this time. Try on your phone instead.
+
+ This app is requesting additional security. Try on your Android TV device instead.
+
+ This app is requesting additional security. Try on your tablet instead.
+
+ This app is requesting additional security. Try on your phone instead.
This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dd69fa0bebb20..f20db16d34973 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3284,7 +3284,13 @@
+
+
+
+
+
+
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 e4b839af8b2e8..235d28691fac9 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -283,6 +283,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mVirtualAudioController;
}
+ @VisibleForTesting
+ SparseArray getWindowPolicyControllersForTesting() {
+ return mWindowPolicyControllers;
+ }
+
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
public void onAudioSessionStarting(int displayId,
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 c96b4d66f293b..d1b015674b3a9 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
@@ -16,6 +16,8 @@
package com.android.server.companion.virtual;
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -26,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -36,14 +39,19 @@ import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import android.Manifest;
+import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManagerInternal;
@@ -65,11 +73,13 @@ import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.ArraySet;
import android.view.DisplayInfo;
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.server.LocalServices;
import org.junit.Before;
@@ -80,11 +90,21 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Arrays;
+
@Presubmit
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class VirtualDeviceManagerServiceTest {
+ private static final String NONBLOCKED_APP_PACKAGE_NAME = "com.someapp";
+ private static final String PERMISSION_CONTROLLER_PACKAGE_NAME =
+ "com.android.permissioncontroller";
+ private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
+ private static final String VENDING_PACKAGE_NAME = "com.android.vending";
+ private static final String GOOGLE_DIALER_PACKAGE_NAME = "com.google.android.dialer";
+ private static final String GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
private static final String DEVICE_NAME = "device name";
private static final int DISPLAY_ID = 2;
private static final int PRODUCT_ID = 10;
@@ -94,10 +114,12 @@ public class VirtualDeviceManagerServiceTest {
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
private static final Binder BINDER = new Binder("binder");
+ private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private Context mContext;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
+ private AssociationInfo mAssociationInfo;
@Mock
private InputController.NativeWrapper mNativeWrapperMock;
@Mock
@@ -119,6 +141,30 @@ public class VirtualDeviceManagerServiceTest {
private IAudioRoutingCallback mRoutingCallback;
@Mock
private IAudioConfigChangedCallback mConfigChangedCallback;
+ @Mock
+ private ApplicationInfo mApplicationInfoMock;
+
+ private ArraySet getBlockedActivities() {
+ ArraySet blockedActivities = new ArraySet<>();
+ blockedActivities.add(new ComponentName(SETTINGS_PACKAGE_NAME, SETTINGS_PACKAGE_NAME));
+ blockedActivities.add(new ComponentName(VENDING_PACKAGE_NAME, VENDING_PACKAGE_NAME));
+ blockedActivities.add(
+ new ComponentName(GOOGLE_DIALER_PACKAGE_NAME, GOOGLE_DIALER_PACKAGE_NAME));
+ blockedActivities.add(
+ new ComponentName(GOOGLE_MAPS_PACKAGE_NAME, GOOGLE_MAPS_PACKAGE_NAME));
+ return blockedActivities;
+ }
+
+ private ArrayList getActivityInfoList(
+ String packageName, String name, boolean displayOnRemoveDevices) {
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = packageName;
+ activityInfo.name = name;
+ activityInfo.flags = displayOnRemoveDevices
+ ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
+ activityInfo.applicationInfo = mApplicationInfoMock;
+ return new ArrayList<>(Arrays.asList(activityInfo));
+ }
@Before
public void setUp() {
@@ -151,13 +197,18 @@ public class VirtualDeviceManagerServiceTest {
when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
mInputController = new InputController(new Object(), mNativeWrapperMock);
- AssociationInfo associationInfo = new AssociationInfo(1, 0, null,
+ mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
+
+ VirtualDeviceParams params = new VirtualDeviceParams
+ .Builder()
+ .setBlockedActivities(getBlockedActivities())
+ .build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
- associationInfo, new Binder(), /* uid */ 0, mInputController,
+ mAssociationInfo, new Binder(), /* uid */ 0, mInputController,
(int associationId) -> {
}, mPendingTrampolineCallback, mActivityListener,
- new VirtualDeviceParams.Builder().build());
+ params);
}
@Test
@@ -590,4 +641,112 @@ public class VirtualDeviceManagerServiceTest {
mDeviceImpl.setShowPointerIcon(true);
verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(true), anyInt());
}
+
+ @Test
+ public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList activityInfos = getActivityInfoList(
+ NONBLOCKED_APP_PACKAGE_NAME,
+ NONBLOCKED_APP_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext, never()).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList activityInfos = getActivityInfoList(
+ PERMISSION_CONTROLLER_PACKAGE_NAME,
+ PERMISSION_CONTROLLER_PACKAGE_NAME, /* displayOnRemoveDevices */ false);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList activityInfos = getActivityInfoList(
+ SETTINGS_PACKAGE_NAME,
+ SETTINGS_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList activityInfos = getActivityInfoList(
+ VENDING_PACKAGE_NAME,
+ VENDING_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList activityInfos = getActivityInfoList(
+ GOOGLE_DIALER_PACKAGE_NAME,
+ GOOGLE_DIALER_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList activityInfos = getActivityInfoList(
+ GOOGLE_MAPS_PACKAGE_NAME,
+ GOOGLE_MAPS_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
}