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:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user