Merge "Addition of native handoff of an intercepted intent matched according to a provided IntentFilter."
This commit is contained in:
committed by
Android (Google) Code Review
commit
cceab4de84
@@ -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 {
|
||||
|
||||
@@ -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