Addition of native handoff of an intercepted intent matched according to a provided IntentFilter.

API user can register and unregister an intent interceptor through the
VirtualDevice by passing in an IntentFilter to match an intent. When the passed
in intent matches, the intent will be intercepted and sent to the VirtualDevice
API user while the intended local activity will be aborted.

Bug: 237537362
Test: manual
Test: atest WmTests:DisplayWindowPolicyControllerTests
Test: atest VirtualDeviceManagerServiceTest
Test: atest CtsVirtualDevicesTestCases
Change-Id: Id841190045b68b9723e58ca02c365cf1c24df8c6
This commit is contained in:
Shaun Corkran
2022-11-27 01:22:55 +00:00
parent ef63ef4e87
commit bc3f6fa5c0
12 changed files with 346 additions and 10 deletions

View File

@@ -2988,6 +2988,10 @@ package android.companion.virtual {
method public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
}
public static interface VirtualDeviceManager.IntentInterceptorCallback {
method public void onIntentIntercepted(@NonNull android.content.Intent);
}
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 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
@@ -3006,8 +3010,10 @@ package android.companion.virtual {
method public int getDeviceId();
method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull java.util.concurrent.Executor, @NonNull android.content.IntentFilter, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @NonNull @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);
}
public final class VirtualDeviceParams implements android.os.Parcelable {

View File

@@ -17,11 +17,13 @@
package android.companion.virtual;
import android.app.PendingIntent;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.IntentFilter;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.input.VirtualDpadConfig;
@@ -125,4 +127,15 @@ interface IVirtualDevice {
/** Sets whether to show or hide the cursor while this virtual device is active. */
void setShowPointerIcon(boolean showPointerIcon);
/**
* Registers an intent interceptor that will intercept an intent attempting to launch
* when matching the provided IntentFilter and calls the callback with the intercepted
* intent.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void registerIntentInterceptor(
in IVirtualDeviceIntentInterceptor intentInterceptor, in IntentFilter filter);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.companion.virtual;
import android.content.Intent;
/**
* Interceptor interface to be called when an intent matches the IntentFilter passed into {@link
* VirtualDevice#registerIntentInterceptor}. When the interceptor is called after matching the
* IntentFilter, the intended activity launch will be aborted and alternatively replaced by
* the interceptor's receiver.
*
* @hide
*/
oneway interface IVirtualDeviceIntentInterceptor {
/**
* Called when an intent that matches the IntentFilter registered in {@link
* VirtualDevice#registerIntentInterceptor} is intercepted for the virtual device to
* handle.
*
* @param intent The intent that has been intercepted by the interceptor.
*/
void onIntentIntercepted(in Intent intent);
}

View File

@@ -35,6 +35,8 @@ import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Point;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.display.DisplayManager;
@@ -269,6 +271,9 @@ public final class VirtualDeviceManager {
private final IVirtualDevice mVirtualDevice;
private final ArrayMap<ActivityListener, ActivityListenerDelegate> mActivityListeners =
new ArrayMap<>();
private final ArrayMap<IntentInterceptorCallback,
VirtualIntentInterceptorDelegate> mIntentInterceptorListeners =
new ArrayMap<>();
private final IVirtualDeviceActivityListener mActivityListenerBinder =
new IVirtualDeviceActivityListener.Stub() {
@@ -857,6 +862,53 @@ public final class VirtualDeviceManager {
public void removeActivityListener(@NonNull ActivityListener listener) {
mActivityListeners.remove(listener);
}
/**
* Registers an intent interceptor that will intercept an intent attempting to launch
* when matching the provided IntentFilter and calls the callback with the intercepted
* intent.
*
* @param executor The executor where the interceptor is executed on.
* @param interceptorFilter The filter to match intents intended for interception.
* @param interceptorCallback The callback called when an intent matching interceptorFilter
* is intercepted.
* @see #unregisterIntentInterceptor(IntentInterceptorCallback)
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void registerIntentInterceptor(
@CallbackExecutor @NonNull Executor executor,
@NonNull IntentFilter interceptorFilter,
@NonNull IntentInterceptorCallback interceptorCallback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(interceptorFilter);
Objects.requireNonNull(interceptorCallback);
final VirtualIntentInterceptorDelegate delegate =
new VirtualIntentInterceptorDelegate(executor, interceptorCallback);
try {
mVirtualDevice.registerIntentInterceptor(delegate, interceptorFilter);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mIntentInterceptorListeners.put(interceptorCallback, delegate);
}
/**
* Unregisters the intent interceptorCallback previously registered with
* {@link #registerIntentInterceptor}.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterIntentInterceptor(
@NonNull IntentInterceptorCallback interceptorCallback) {
Objects.requireNonNull(interceptorCallback);
final VirtualIntentInterceptorDelegate delegate =
mIntentInterceptorListeners.get(interceptorCallback);
try {
mVirtualDevice.unregisterIntentInterceptor(delegate);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mIntentInterceptorListeners.remove(interceptorCallback);
}
}
/**
@@ -908,4 +960,51 @@ public final class VirtualDeviceManager {
mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId));
}
}
/**
* Interceptor interface to be called when an intent matches the IntentFilter passed into {@link
* VirtualDevice#registerIntentInterceptor}. When the interceptor is called after matching the
* IntentFilter, the intended activity launch will be aborted and alternatively replaced by
* the interceptor's receiver.
*
* @hide
*/
@SystemApi
public interface IntentInterceptorCallback {
/**
* Called when an intent that matches the IntentFilter registered in {@link
* VirtualDevice#registerIntentInterceptor} is intercepted for the virtual device to
* handle.
*
* @param intent The intent that has been intercepted by the interceptor.
*/
void onIntentIntercepted(@NonNull Intent intent);
}
/**
* A wrapper for {@link IntentInterceptorCallback} that executes callbacks on the
* the given executor.
*/
private static class VirtualIntentInterceptorDelegate
extends IVirtualDeviceIntentInterceptor.Stub {
@NonNull private final IntentInterceptorCallback mIntentInterceptorCallback;
@NonNull private final Executor mExecutor;
private VirtualIntentInterceptorDelegate(Executor executor,
IntentInterceptorCallback interceptorCallback) {
mExecutor = executor;
mIntentInterceptorCallback = interceptorCallback;
}
@Override
public void onIntentIntercepted(Intent intent) {
final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mIntentInterceptorCallback.onIntentIntercepted(intent));
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
}

View File

@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.util.ArraySet;
@@ -114,7 +115,7 @@ public abstract class DisplayWindowPolicyController {
/**
* Returns {@code true} if the given new task can be launched on this virtual display.
*/
public abstract boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
public abstract boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo, Intent intent,
@WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
boolean isNewTask);

View File

@@ -32,6 +32,7 @@ import android.companion.virtual.VirtualDeviceParams.RecentsPolicy;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.Handler;
@@ -93,6 +94,12 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
void onEnteringPipBlocked(int uid);
}
/** Interface to listen for interception of intents. */
public interface IntentListenerCallback {
/** Returns true when an intent should be intercepted */
boolean shouldInterceptIntent(Intent intent);
}
/**
* If required, allow the secure activity to display on remote device since
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
@@ -121,6 +128,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
final ArraySet<Integer> mRunningUids = new ArraySet<>();
@Nullable private final ActivityListener mActivityListener;
@Nullable private final PipBlockedCallback mPipBlockedCallback;
@Nullable private final IntentListenerCallback mIntentListenerCallback;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull
@GuardedBy("mGenericWindowPolicyControllerLock")
@@ -155,6 +163,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
* launching.
* @param secureWindowCallback Callback that is called when a secure window shows on the
* virtual display.
* @param intentListenerCallback Callback that is called to intercept intents when matching
* passed in filters.
* @param defaultRecentsPolicy a policy to indicate how to handle activities in recents.
*/
public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
@@ -168,6 +178,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
@NonNull PipBlockedCallback pipBlockedCallback,
@NonNull ActivityBlockedCallback activityBlockedCallback,
@NonNull SecureWindowCallback secureWindowCallback,
@NonNull IntentListenerCallback intentListenerCallback,
@NonNull List<String> displayCategories,
@RecentsPolicy int defaultRecentsPolicy) {
super();
@@ -182,6 +193,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
mActivityListener = activityListener;
mPipBlockedCallback = pipBlockedCallback;
mSecureWindowCallback = secureWindowCallback;
mIntentListenerCallback = intentListenerCallback;
mDisplayCategories = displayCategories;
mDefaultRecentsPolicy = defaultRecentsPolicy;
}
@@ -227,8 +239,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
@Override
public boolean canActivityBeLaunched(ActivityInfo activityInfo,
@WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
boolean isNewTask) {
Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
int launchingFromDisplayId, boolean isNewTask) {
if (!isWindowingModeSupported(windowingMode)) {
return false;
}
@@ -261,6 +273,12 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
return false;
}
if (mIntentListenerCallback != null && intent != null
&& mIntentListenerCallback.shouldInterceptIntent(intent)) {
Slog.d(TAG, "Virtual device has intercepted intent");
return false;
}
return true;
}

View File

@@ -33,6 +33,7 @@ import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.companion.virtual.VirtualDeviceParams;
@@ -43,6 +44,7 @@ import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
@@ -114,6 +116,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final VirtualDeviceParams mParams;
private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
private final IVirtualDeviceActivityListener mActivityListener;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
// The default setting for showing the pointer on new displays.
@@ -680,6 +684,31 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
@Override // Binder call
public void registerIntentInterceptor(IVirtualDeviceIntentInterceptor intentInterceptor,
IntentFilter filter) {
Objects.requireNonNull(intentInterceptor);
Objects.requireNonNull(filter);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to register intent interceptor");
synchronized (mVirtualDeviceLock) {
mIntentInterceptors.put(intentInterceptor.asBinder(), filter);
}
}
@Override // Binder call
public void unregisterIntentInterceptor(
@NonNull IVirtualDeviceIntentInterceptor intentInterceptor) {
Objects.requireNonNull(intentInterceptor);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to unregister intent interceptor");
synchronized (mVirtualDeviceLock) {
mIntentInterceptors.remove(intentInterceptor.asBinder());
}
}
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
fout.println(" VirtualDevice: ");
@@ -713,6 +742,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
this::onEnteringPipBlocked,
this::onActivityBlocked,
this::onSecureWindowShown,
this::shouldInterceptIntent,
displayCategories,
mParams.getDefaultRecentsPolicy());
gwpc.registerRunningAppsChangedListener(/* listener= */ this);
@@ -872,6 +902,34 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
Toast.LENGTH_LONG, mContext.getMainLooper());
}
/**
* Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true if
* the intent matches any filter notifying the DisplayPolicyController to abort the
* activity launch to be replaced by the interception.
*/
private boolean shouldInterceptIntent(Intent intent) {
synchronized (mVirtualDeviceLock) {
boolean hasInterceptedIntent = false;
for (Map.Entry<IBinder, IntentFilter> interceptor : mIntentInterceptors.entrySet()) {
if (interceptor.getValue().match(
intent.getAction(), intent.getType(), intent.getScheme(), intent.getData(),
intent.getCategories(), TAG) >= 0) {
try {
// For privacy reasons, only returning the intents action and data. Any
// other required field will require a review.
IVirtualDeviceIntentInterceptor.Stub.asInterface(interceptor.getKey())
.onIntentIntercepted(new Intent(intent.getAction(), intent.getData()));
hasInterceptedIntent = true;
} catch (RemoteException e) {
Slog.w(TAG, "Unable to call mVirtualDeviceIntentInterceptor", e);
}
}
}
return hasInterceptedIntent;
}
}
interface OnDeviceCloseListener {
void onClose(int deviceId);
}

View File

@@ -1829,8 +1829,8 @@ class ActivityStarter {
final int launchingFromDisplayId =
mSourceRecord != null ? mSourceRecord.getDisplayId() : DEFAULT_DISPLAY;
if (!displayContent.mDwpcHelper
.canActivityBeLaunched(r.info, targetWindowingMode, launchingFromDisplayId,
newTask)) {
.canActivityBeLaunched(r.info, r.intent, targetWindowingMode,
launchingFromDisplayId, newTask)) {
Slog.w(TAG, "Abort to launch " + r.info.getComponentName()
+ " on display area " + mPreferredTaskDisplayArea);
return START_ABORTED;

View File

@@ -19,6 +19,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -91,8 +92,8 @@ class DisplayWindowPolicyControllerHelper {
* @see DisplayWindowPolicyController#canActivityBeLaunched(ActivityInfo, int, int, boolean)
*/
public boolean canActivityBeLaunched(ActivityInfo activityInfo,
@WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
boolean isNewTask) {
Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
int launchingFromDisplayId, boolean isNewTask) {
if (mDisplayWindowPolicyController == null) {
if (activityInfo.requiredDisplayCategory != null) {
Slog.e(TAG,
@@ -104,8 +105,8 @@ class DisplayWindowPolicyControllerHelper {
}
return true;
}
return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, windowingMode,
launchingFromDisplayId, isNewTask);
return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, intent,
windowingMode, launchingFromDisplayId, isNewTask);
}
/**

View File

@@ -21,6 +21,7 @@ import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.Intent.ACTION_VIEW;
import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static com.google.common.truth.Truth.assertThat;
@@ -37,6 +38,7 @@ import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -49,6 +51,7 @@ import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -57,6 +60,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.hardware.Sensor;
@@ -73,6 +77,7 @@ import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.net.MacAddress;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -184,6 +189,7 @@ public class VirtualDeviceManagerServiceTest {
.setInputDeviceName(DEVICE_NAME)
.setAssociatedDisplayId(DISPLAY_ID)
.build();
private static final String TEST_SITE = "http://test";
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
@@ -1339,6 +1345,99 @@ public class VirtualDeviceManagerServiceTest {
assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
}
@Test
public void canActivityBeLaunched_activityCanLaunch() {
Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
NONBLOCKED_APP_PACKAGE_NAME,
/* displayOnRemoveDevices */ true,
/* targetDisplayCategory */ null);
assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
.isTrue();
}
@Test
public void canActivityBeLaunched_intentInterceptedWhenRegistered_activityNoLaunch()
throws RemoteException {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(TEST_SITE));
IVirtualDeviceIntentInterceptor.Stub interceptor =
mock(IVirtualDeviceIntentInterceptor.Stub.class);
doNothing().when(interceptor).onIntentIntercepted(any());
doReturn(interceptor).when(interceptor).asBinder();
doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
NONBLOCKED_APP_PACKAGE_NAME,
/* displayOnRemoveDevices */ true,
/* targetDisplayCategory */ null);
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
intentFilter.addDataScheme(IntentFilter.SCHEME_HTTP);
intentFilter.addDataScheme(IntentFilter.SCHEME_HTTPS);
// register interceptor and intercept intent
mDeviceImpl.registerIntentInterceptor(interceptor, intentFilter);
assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
.isFalse();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(interceptor).onIntentIntercepted(intentCaptor.capture());
Intent cIntent = intentCaptor.getValue();
assertThat(cIntent).isNotNull();
assertThat(cIntent.getAction()).isEqualTo(Intent.ACTION_VIEW);
assertThat(cIntent.getData().toString()).isEqualTo(TEST_SITE);
// unregister interceptor and launch activity
mDeviceImpl.unregisterIntentInterceptor(interceptor);
assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
.isTrue();
}
@Test
public void canActivityBeLaunched_noMatchIntentFilter_activityLaunches()
throws RemoteException {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("testing"));
IVirtualDeviceIntentInterceptor.Stub interceptor =
mock(IVirtualDeviceIntentInterceptor.Stub.class);
doNothing().when(interceptor).onIntentIntercepted(any());
doReturn(interceptor).when(interceptor).asBinder();
doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
NONBLOCKED_APP_PACKAGE_NAME,
/* displayOnRemoveDevices */ true,
/* targetDisplayCategory */ null);
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
intentFilter.addDataScheme("mailto");
// register interceptor with different filter
mDeviceImpl.registerIntentInterceptor(interceptor, intentFilter);
assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
.isTrue();
}
@Test
public void nonRestrictedActivityOnRestrictedVirtualDisplay_startBlockedAlertActivity() {
Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"),

View File

@@ -85,6 +85,7 @@ public class VirtualAudioControllerTest {
/* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ null,
/* displayCategories= */ new ArrayList<>(),
/* recentsPolicy= */
VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS);

View File

@@ -31,6 +31,7 @@ import static org.mockito.Mockito.mock;
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -227,7 +228,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase {
ArraySet<Integer> mRunningUids = new ArraySet<>();
@Override
public boolean canActivityBeLaunched(@NonNull ActivityInfo activity,
public boolean canActivityBeLaunched(@NonNull ActivityInfo activity, Intent intent,
@WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
boolean isNewTask) {
return false;