diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8528c95101a75..0f0b68efa2f6b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2991,6 +2991,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(); @@ -3009,8 +3013,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 { diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index f17d18652e98d..22ea9f2094cb8 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -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); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceIntentInterceptor.aidl b/core/java/android/companion/virtual/IVirtualDeviceIntentInterceptor.aidl new file mode 100644 index 0000000000000..cd43bf746ecf5 --- /dev/null +++ b/core/java/android/companion/virtual/IVirtualDeviceIntentInterceptor.aidl @@ -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); +} diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index dba7c8e630c80..cc452e27a650c 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -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 mActivityListeners = new ArrayMap<>(); + private final ArrayMap 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); + } + } + } } diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java index e027934ccb515..535dc4e78c89d 100644 --- a/core/java/android/window/DisplayWindowPolicyController.java +++ b/core/java/android/window/DisplayWindowPolicyController.java @@ -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); 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 5e68d5231c6b7..6b2e893a55c9d 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -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 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 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; } 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 12ad9f1cf5805..29cde8ebd5138 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -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 mPerDisplayWakelocks = new ArrayMap<>(); private final IVirtualDeviceActivityListener mActivityListener; + @GuardedBy("mVirtualDeviceLock") + private final Map mIntentInterceptors = new ArrayMap<>(); @NonNull private Consumer> 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 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); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 2762c459fba45..6c49f47e68803 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -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; diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java index 72ed1083043b2..154fb0cecac32 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java +++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java @@ -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); } /** 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 31e53d56f5202..ad7550e47712e 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 @@ -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 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 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 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 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"), 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 aaa13512af251..c27043565fcca 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 @@ -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); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java index 2ce1d6055d86e..3c5bc17049543 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java @@ -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 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;