blob: 70c449fe147cbb56923ada3382718e80114a1d8e [file] [log] [blame]
/*
* Copyright (C) 2021 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 com.android.server.companion.virtual;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
import static android.companion.virtual.VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.camera.IVirtualCamera;
import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.WindowManager;
import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.VirtualAudioController;
import com.android.server.companion.virtual.camera.VirtualCameraController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
final class VirtualDeviceImpl extends IVirtualDevice.Stub
implements IBinder.DeathRecipient, RunningAppsChangedListener {
private static final String TAG = "VirtualDeviceImpl";
/**
* Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent
* with virtual displays created via {@link android.hardware.display.DisplayManager} and allow
* for the creation of private, auto-mirror, and fixed orientation displays since
* {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
*
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static final long MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER =
294837146L;
private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
| DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
| DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC =
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
| DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:";
/**
* Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
*/
private static final long PENDING_TRAMPOLINE_TIMEOUT_MS = 5000;
private final Object mVirtualDeviceLock = new Object();
private final int mBaseVirtualDisplayFlags;
private final Context mContext;
private final AssociationInfo mAssociationInfo;
private final VirtualDeviceManagerService mService;
private final PendingTrampolineCallback mPendingTrampolineCallback;
private final int mOwnerUid;
private final VirtualDeviceLog mVirtualDeviceLog;
private final String mOwnerPackageName;
private final int mDeviceId;
@Nullable
private final String mPersistentDeviceId;
// Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
// Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
// 1. After display is created the window manager calls into VDM during construction
// of display specific context to fetch device id corresponding to the display.
// mVirtualDeviceLock will be held while this is done.
// 2. InputController interactions result in calls to DisplayManager (to set IME,
// possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which
// creates lock inversion.
private final InputController mInputController;
private final SensorController mSensorController;
private final CameraAccessController mCameraAccessController;
@Nullable // Null if virtual camera flag is off.
private final VirtualCameraController mVirtualCameraController;
private VirtualAudioController mVirtualAudioController;
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;
@GuardedBy("mVirtualDeviceLock")
private final SparseIntArray mDevicePolicies;
@GuardedBy("mVirtualDeviceLock")
private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
private final IVirtualDeviceActivityListener mActivityListener;
private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
private final DisplayManagerGlobal mDisplayManager;
private final DisplayManagerInternal mDisplayManagerInternal;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
private final Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
// The default setting for showing the pointer on new displays.
@GuardedBy("mVirtualDeviceLock")
private boolean mDefaultShowPointerIcon = true;
@GuardedBy("mVirtualDeviceLock")
@Nullable
private LocaleList mLocaleList = null;
@NonNull
private final VirtualDevice mPublicVirtualDeviceObject;
@GuardedBy("mVirtualDeviceLock")
@NonNull
private final Set<ComponentName> mActivityPolicyExemptions;
private final ComponentName mPermissionDialogComponent;
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@Override
public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity) {
try {
mActivityListener.onTopActivityChanged(displayId, topActivity,
UserHandle.USER_NULL);
} catch (RemoteException e) {
Slog.w(TAG, "Unable to call mActivityListener", e);
}
}
@Override
public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
@UserIdInt int userId) {
try {
mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
} catch (RemoteException e) {
Slog.w(TAG, "Unable to call mActivityListener", e);
}
}
@Override
public void onDisplayEmpty(int displayId) {
try {
mActivityListener.onDisplayEmpty(displayId);
} catch (RemoteException e) {
Slog.w(TAG, "Unable to call mActivityListener", e);
}
}
};
}
VirtualDeviceImpl(
Context context,
AssociationInfo associationInfo,
VirtualDeviceManagerService service,
VirtualDeviceLog virtualDeviceLog,
IBinder token,
AttributionSource attributionSource,
int deviceId,
CameraAccessController cameraAccessController,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
VirtualDeviceParams params) {
this(
context,
associationInfo,
service,
virtualDeviceLog,
token,
attributionSource,
deviceId,
/* inputController= */ null,
cameraAccessController,
pendingTrampolineCallback,
activityListener,
soundEffectListener,
runningAppsChangedCallback,
params,
DisplayManagerGlobal.getInstance(),
Flags.virtualCamera() ? new VirtualCameraController(context) : null);
}
@VisibleForTesting
VirtualDeviceImpl(
Context context,
AssociationInfo associationInfo,
VirtualDeviceManagerService service,
VirtualDeviceLog virtualDeviceLog,
IBinder token,
AttributionSource attributionSource,
int deviceId,
InputController inputController,
CameraAccessController cameraAccessController,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
VirtualDeviceParams params,
DisplayManagerGlobal displayManager,
VirtualCameraController virtualCameraController) {
super(PermissionEnforcer.fromContext(context));
mVirtualDeviceLog = virtualDeviceLog;
mOwnerPackageName = attributionSource.getPackageName();
UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid());
mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
mPersistentDeviceId = PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationInfo.getId();
mService = service;
mPendingTrampolineCallback = pendingTrampolineCallback;
mActivityListener = activityListener;
mSoundEffectListener = soundEffectListener;
mRunningAppsChangedCallback = runningAppsChangedCallback;
mOwnerUid = attributionSource.getUid();
mDeviceId = deviceId;
mAppToken = token;
mParams = params;
mDevicePolicies = params.getDevicePolicies();
mDisplayManager = displayManager;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
if (inputController == null) {
mInputController = new InputController(
context.getMainThreadHandler(),
context.getSystemService(WindowManager.class));
} else {
mInputController = inputController;
}
mSensorController = new SensorController(this, mDeviceId,
mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs());
mCameraAccessController = cameraAccessController;
mCameraAccessController.startObservingIfNeeded();
if (!Flags.streamPermissions()) {
mPermissionDialogComponent = getPermissionDialogComponent();
} else {
mPermissionDialogComponent = null;
}
mVirtualCameraController = virtualCameraController;
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mVirtualDeviceLog.logCreated(deviceId, mOwnerUid);
if (Flags.vdmPublicApis()) {
mPublicVirtualDeviceObject = new VirtualDevice(
this, getDeviceId(), getPersistentDeviceId(), mParams.getName(),
getDisplayName());
} else {
mPublicVirtualDeviceObject = new VirtualDevice(
this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
}
if (Flags.dynamicPolicy()) {
mActivityPolicyExemptions = new ArraySet<>(
mParams.getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
? mParams.getBlockedActivities()
: mParams.getAllowedActivities());
} else {
mActivityPolicyExemptions =
mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED
? mParams.getBlockedActivities()
: mParams.getAllowedActivities();
}
int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
if (!CompatChanges.isChangeEnabled(
MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER, mOwnerUid)) {
flags |= DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC;
}
if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
}
mBaseVirtualDisplayFlags = flags;
}
@VisibleForTesting
SensorController getSensorControllerForTest() {
return mSensorController;
}
/**
* Returns the flags that should be added to any virtual displays created on this virtual
* device.
*/
int getBaseVirtualDisplayFlags() {
return mBaseVirtualDisplayFlags;
}
/** Returns the camera access controller of this device. */
CameraAccessController getCameraAccessController() {
return mCameraAccessController;
}
/** Returns the device display name. */
CharSequence getDisplayName() {
return mAssociationInfo.getDisplayName();
}
/** Returns the public representation of the device. */
VirtualDevice getPublicVirtualDeviceObject() {
return mPublicVirtualDeviceObject;
}
/** Returns the locale of the device. */
LocaleList getDeviceLocaleList() {
synchronized (mVirtualDeviceLock) {
return mLocaleList;
}
}
@Override // Binder call
public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
@VirtualDeviceParams.PolicyType int policyType) {
if (Flags.dynamicPolicy()) {
synchronized (mVirtualDeviceLock) {
return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
}
} else {
return mParams.getDevicePolicy(policyType);
}
}
/** Returns device-specific audio session id for playback. */
public int getAudioPlaybackSessionId() {
return mParams.getAudioPlaybackSessionId();
}
/** Returns device-specific audio session id for recording. */
public int getAudioRecordingSessionId() {
return mParams.getAudioRecordingSessionId();
}
/** Returns the unique device ID of this device. */
@Override // Binder call
public int getDeviceId() {
return mDeviceId;
}
/** Returns the unique device ID of this device. */
@Override // Binder call
public @Nullable String getPersistentDeviceId() {
return mPersistentDeviceId;
}
@Override // Binder call
public int getAssociationId() {
return mAssociationInfo.getId();
}
@Override // Binder call
public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
ResultReceiver resultReceiver) {
Objects.requireNonNull(pendingIntent);
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(displayId)) {
throw new SecurityException("Display ID " + displayId
+ " not found for this virtual device");
}
}
if (pendingIntent.isActivity()) {
try {
sendPendingIntent(displayId, pendingIntent);
resultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
} catch (PendingIntent.CanceledException e) {
Slog.w(TAG, "Pending intent canceled", e);
resultReceiver.send(
VirtualDeviceManager.LAUNCH_FAILURE_PENDING_INTENT_CANCELED, null);
}
} else {
PendingTrampoline pendingTrampoline = new PendingTrampoline(pendingIntent,
resultReceiver, displayId);
mPendingTrampolineCallback.startWaitingForPendingTrampoline(pendingTrampoline);
mContext.getMainThreadHandler().postDelayed(() -> {
pendingTrampoline.mResultReceiver.send(
VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
mPendingTrampolineCallback.stopWaitingForPendingTrampoline(pendingTrampoline);
}, PENDING_TRAMPOLINE_TIMEOUT_MS);
try {
sendPendingIntent(displayId, pendingIntent);
} catch (PendingIntent.CanceledException e) {
Slog.w(TAG, "Pending intent canceled", e);
resultReceiver.send(
VirtualDeviceManager.LAUNCH_FAILURE_PENDING_INTENT_CANCELED, null);
mPendingTrampolineCallback.stopWaitingForPendingTrampoline(pendingTrampoline);
}
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
super.addActivityPolicyExemption_enforcePermission();
synchronized (mVirtualDeviceLock) {
if (mActivityPolicyExemptions.add(componentName)) {
for (int i = 0; i < mVirtualDisplays.size(); i++) {
mVirtualDisplays.valueAt(i).getWindowPolicyController()
.addActivityPolicyExemption(componentName);
}
}
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
super.removeActivityPolicyExemption_enforcePermission();
synchronized (mVirtualDeviceLock) {
if (mActivityPolicyExemptions.remove(componentName)) {
for (int i = 0; i < mVirtualDisplays.size(); i++) {
mVirtualDisplays.valueAt(i).getWindowPolicyController()
.removeActivityPolicyExemption(componentName);
}
}
}
}
private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
throws PendingIntent.CanceledException {
final ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
options.setPendingIntentBackgroundActivityLaunchAllowed(true);
options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
pendingIntent.send(
mContext,
/* code= */ 0,
/* intent= */ null,
/* onFinished= */ null,
/* handler= */ null,
/* requiredPermission= */ null,
options.toBundle());
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
super.close_enforcePermission();
// Remove about-to-be-closed virtual device from the service before butchering it.
if (!mService.removeVirtualDevice(mDeviceId)) {
// Device is already closed.
return;
}
mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid);
final long ident = Binder.clearCallingIdentity();
try {
VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
synchronized (mVirtualDeviceLock) {
if (mVirtualAudioController != null) {
mVirtualAudioController.stopListening();
mVirtualAudioController = null;
}
mLocaleList = null;
virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()];
for (int i = 0; i < mVirtualDisplays.size(); i++) {
virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
}
mVirtualDisplays.clear();
}
// Destroy the display outside locked section.
for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken());
// The releaseVirtualDisplay call above won't trigger
// VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the
// virtual device from the service - we release the other display-tied resources
// here with the guarantee it will be done exactly once.
releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
}
mAppToken.unlinkToDeath(this, 0);
mCameraAccessController.stopObservingIfNeeded();
mInputController.close();
mSensorController.close();
} finally {
Binder.restoreCallingIdentity(ident);
}
if (mVirtualCameraController != null) {
mVirtualCameraController.close();
}
}
@Override
public void binderDied() {
close();
}
@Override
@RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
mCameraAccessController.blockCameraAccessIfNeeded(runningUids);
mRunningAppsChangedCallback.accept(runningUids);
}
@VisibleForTesting
VirtualAudioController getVirtualAudioControllerForTesting() {
return mVirtualAudioController;
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void onAudioSessionStarting(int displayId,
@NonNull IAudioRoutingCallback routingCallback,
@Nullable IAudioConfigChangedCallback configChangedCallback) {
super.onAudioSessionStarting_enforcePermission();
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(displayId)) {
throw new SecurityException(
"Cannot start audio session for a display not associated with this virtual "
+ "device");
}
if (mVirtualAudioController == null) {
mVirtualAudioController = new VirtualAudioController(mContext);
GenericWindowPolicyController gwpc = mVirtualDisplays.get(
displayId).getWindowPolicyController();
mVirtualAudioController.startListening(gwpc, routingCallback,
configChangedCallback);
}
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void onAudioSessionEnded() {
super.onAudioSessionEnded_enforcePermission();
synchronized (mVirtualDeviceLock) {
if (mVirtualAudioController != null) {
mVirtualAudioController.stopListening();
mVirtualAudioController = null;
}
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
super.setDevicePolicy_enforcePermission();
if (!Flags.dynamicPolicy()) {
return;
}
switch (policyType) {
case POLICY_TYPE_RECENTS:
synchronized (mVirtualDeviceLock) {
mDevicePolicies.put(policyType, devicePolicy);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
mVirtualDisplays.valueAt(i).getWindowPolicyController()
.setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
}
}
break;
case POLICY_TYPE_ACTIVITY:
synchronized (mVirtualDeviceLock) {
mDevicePolicies.put(policyType, devicePolicy);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
mVirtualDisplays.valueAt(i).getWindowPolicyController()
.setActivityLaunchDefaultAllowed(
devicePolicy == DEVICE_POLICY_DEFAULT);
}
}
break;
default:
throw new IllegalArgumentException("Device policy " + policyType
+ " cannot be changed at runtime. ");
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
super.createVirtualDpad_enforcePermission();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
final long ident = Binder.clearCallingIdentity();
try {
mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),
config.getProductId(), deviceToken, config.getAssociatedDisplayId());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
super.createVirtualKeyboard_enforcePermission();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
synchronized (mVirtualDeviceLock) {
mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag());
}
final long ident = Binder.clearCallingIdentity();
try {
mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),
config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
config.getLanguageTag(), config.getLayoutType());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
super.createVirtualMouse_enforcePermission();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
final long ident = Binder.clearCallingIdentity();
try {
mInputController.createMouse(config.getInputDeviceName(), config.getVendorId(),
config.getProductId(), deviceToken, config.getAssociatedDisplayId());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualTouchscreen(VirtualTouchscreenConfig config,
@NonNull IBinder deviceToken) {
super.createVirtualTouchscreen_enforcePermission();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
int screenHeight = config.getHeight();
int screenWidth = config.getWidth();
if (screenHeight <= 0 || screenWidth <= 0) {
throw new IllegalArgumentException(
"Cannot create a virtual touchscreen, screen dimensions must be positive. Got: "
+ "(" + screenWidth + ", " + screenHeight + ")");
}
final long ident = Binder.clearCallingIdentity();
try {
mInputController.createTouchscreen(config.getInputDeviceName(), config.getVendorId(),
config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
screenHeight, screenWidth);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
@NonNull IBinder deviceToken) {
super.createVirtualNavigationTouchpad_enforcePermission();
Objects.requireNonNull(config);
checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
int touchpadHeight = config.getHeight();
int touchpadWidth = config.getWidth();
if (touchpadHeight <= 0 || touchpadWidth <= 0) {
throw new IllegalArgumentException(
"Cannot create a virtual navigation touchpad, touchpad dimensions must be positive."
+ " Got: (" + touchpadHeight + ", " + touchpadWidth + ")");
}
final long ident = Binder.clearCallingIdentity();
try {
mInputController.createNavigationTouchpad(
config.getInputDeviceName(), config.getVendorId(),
config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
touchpadHeight, touchpadWidth);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterInputDevice(IBinder token) {
super.unregisterInputDevice_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
mInputController.unregisterInputDevice(token);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public int getInputDeviceId(IBinder token) {
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getInputDeviceId(token);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
super.sendDpadKeyEvent_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendDpadKeyEvent(token, event);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
super.sendKeyEvent_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendKeyEvent(token, event);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
super.sendButtonEvent_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendButtonEvent(token, event);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
super.sendTouchEvent_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendTouchEvent(token, event);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
super.sendRelativeEvent_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendRelativeEvent(token, event);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
super.sendScrollEvent_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendScrollEvent(token, event);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public PointF getCursorPosition(IBinder token) {
final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getCursorPosition(token);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setShowPointerIcon(boolean showPointerIcon) {
super.setShowPointerIcon_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
}
final int[] displayIds = getDisplayIds();
for (int i = 0; i < displayIds.length; ++i) {
mInputController.setShowPointerIcon(showPointerIcon, displayIds[i]);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
public List<VirtualSensor> getVirtualSensorList() {
super.getVirtualSensorList_enforcePermission();
return mSensorController.getSensorList();
}
@Nullable
VirtualSensor getVirtualSensorByHandle(int handle) {
return mSensorController.getSensorByHandle(handle);
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
super.sendSensorEvent_enforcePermission();
final long ident = Binder.clearCallingIdentity();
try {
return mSensorController.sendSensorEvent(token, event);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void registerIntentInterceptor(IVirtualDeviceIntentInterceptor intentInterceptor,
IntentFilter filter) {
super.registerIntentInterceptor_enforcePermission();
Objects.requireNonNull(intentInterceptor);
Objects.requireNonNull(filter);
synchronized (mVirtualDeviceLock) {
mIntentInterceptors.put(intentInterceptor.asBinder(), filter);
}
}
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void unregisterIntentInterceptor(
@NonNull IVirtualDeviceIntentInterceptor intentInterceptor) {
super.unregisterIntentInterceptor_enforcePermission();
Objects.requireNonNull(intentInterceptor);
synchronized (mVirtualDeviceLock) {
mIntentInterceptors.remove(intentInterceptor.asBinder());
}
}
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void registerVirtualCamera(@NonNull IVirtualCamera camera) {
super.registerVirtualCamera_enforcePermission();
if (mVirtualCameraController == null) {
return;
}
mVirtualCameraController.registerCamera(Objects.requireNonNull(camera));
}
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
String indent = " ";
fout.println(" VirtualDevice: ");
fout.println(indent + "mDeviceId: " + mDeviceId);
fout.println(indent + "mAssociationId: " + mAssociationInfo.getId());
fout.println(indent + "mOwnerPackageName: " + mOwnerPackageName);
fout.println(indent + "mParams: ");
mParams.dump(fout, indent + indent);
fout.println(indent + "mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
fout.println(" mDevicePolicies: " + mDevicePolicies);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
fout.println(indent + " " + mVirtualDisplays.keyAt(i));
}
fout.println(indent + "mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
mInputController.dump(fout);
mSensorController.dump(fout);
if (mVirtualCameraController != null) {
mVirtualCameraController.dump(fout, indent);
}
}
@GuardedBy("mVirtualDeviceLock")
private GenericWindowPolicyController createWindowPolicyControllerLocked(
@NonNull Set<String> displayCategories) {
final boolean activityLaunchAllowedByDefault =
Flags.dynamicPolicy()
? getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
: mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
final boolean crossTaskNavigationAllowedByDefault =
mParams.getDefaultNavigationPolicy() == NAVIGATION_POLICY_DEFAULT_ALLOWED;
final boolean showTasksInHostDeviceRecents =
getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
final ComponentName homeComponent =
Flags.vdmCustomHome() ? mParams.getHomeComponent() : null;
final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
getAllowedUserHandles(),
activityLaunchAllowedByDefault,
mActivityPolicyExemptions,
crossTaskNavigationAllowedByDefault,
/* crossTaskNavigationExemptions= */crossTaskNavigationAllowedByDefault
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
mPermissionDialogComponent,
createListenerAdapter(),
this::onEnteringPipBlocked,
this::onActivityBlocked,
this::onSecureWindowShown,
this::shouldInterceptIntent,
displayCategories,
showTasksInHostDeviceRecents,
homeComponent);
gwpc.registerRunningAppsChangedListener(/* listener= */ this);
return gwpc;
}
private ComponentName getPermissionDialogComponent() {
Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
PackageManager packageManager = mContext.getPackageManager();
intent.setPackage(packageManager.getPermissionControllerPackageName());
return intent.resolveActivity(packageManager);
}
int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@NonNull IVirtualDisplayCallback callback, String packageName) {
GenericWindowPolicyController gwpc;
synchronized (mVirtualDeviceLock) {
gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
}
int displayId;
displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
this, gwpc, packageName);
gwpc.setDisplayId(displayId, /* isMirrorDisplay= */ Flags.interactiveScreenMirror()
&& mDisplayManagerInternal.getDisplayIdToMirror(displayId)
!= Display.INVALID_DISPLAY);
boolean showPointer;
synchronized (mVirtualDeviceLock) {
if (mVirtualDisplays.contains(displayId)) {
gwpc.unregisterRunningAppsChangedListener(this);
throw new IllegalStateException(
"Virtual device already has a virtual display with ID " + displayId);
}
PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
showPointer = mDefaultShowPointerIcon;
}
final long token = Binder.clearCallingIdentity();
try {
mInputController.setShowPointerIcon(showPointer, displayId);
mInputController.setPointerAcceleration(1f, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
mInputController.setLocalIme(displayId);
} finally {
Binder.restoreCallingIdentity(token);
}
return displayId;
}
private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
final long token = Binder.clearCallingIdentity();
try {
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
TAG + ":" + displayId, displayId);
wakeLock.acquire();
return wakeLock;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
private void onActivityBlocked(int displayId, ActivityInfo activityInfo) {
Intent intent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
mContext.startActivityAsUser(
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
mContext.getUser());
}
private void onSecureWindowShown(int displayId, int uid) {
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(displayId)) {
return;
}
}
// If a virtual display isn't secure, the screen can't be captured. Show a warning toast
// if the secure window is shown on a non-secure virtual display.
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
Display display = displayManager.getDisplay(displayId);
if ((display.getFlags() & FLAG_SECURE) == 0) {
showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
Toast.LENGTH_LONG, mContext.getMainLooper());
}
}
private ArraySet<UserHandle> getAllowedUserHandles() {
ArraySet<UserHandle> result = new ArraySet<>();
final long token = Binder.clearCallingIdentity();
try {
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
UserManager userManager = mContext.getSystemService(UserManager.class);
for (UserHandle profile : userManager.getAllProfiles()) {
int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(
profile.getIdentifier());
if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
|| nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
result.add(profile);
} else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
result.add(profile);
}
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
return result;
}
void onVirtualDisplayRemoved(int displayId) {
/* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
* by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
* At this point, the display is already released, but we still need to release the
* corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
* WindowPolicyController.
*
* Note that when the display is destroyed during VirtualDeviceImpl.close() call,
* this callback won't be invoked because the display is removed from
* VirtualDeviceManagerService before any resources are released.
*/
VirtualDisplayWrapper virtualDisplayWrapper;
synchronized (mVirtualDeviceLock) {
virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId);
}
if (virtualDisplayWrapper == null) {
Slog.w(TAG, "Virtual device " + mDeviceId + " doesn't have a virtual display with ID "
+ displayId);
return;
}
final long ident = Binder.clearCallingIdentity();
try {
releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@SuppressWarnings("AndroidFrameworkRequiresPermission")
private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
if (mContext.checkCallingPermission(android.Manifest.permission.INJECT_EVENTS)
== PackageManager.PERMISSION_GRANTED) {
// The INJECT_EVENTS permission allows for injecting input to any window / display.
return;
}
synchronized (mVirtualDeviceLock) {
if (!mVirtualDisplays.contains(displayId)) {
throw new SecurityException(
"Cannot create a virtual input device for display " + displayId
+ " which not associated with this virtual device");
}
}
}
/**
* Release resources tied to virtual display owned by this VirtualDevice instance.
*
* Note that this method won't release the virtual display itself.
*
* @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for.
*/
private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
virtualDisplayWrapper.getWakeLock().release();
virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
this);
}
int getOwnerUid() {
return mOwnerUid;
}
@Override // Binder call
public int[] getDisplayIds() {
synchronized (mVirtualDeviceLock) {
final int size = mVirtualDisplays.size();
int[] displayIds = new int[size];
for (int i = 0; i < size; i++) {
displayIds[i] = mVirtualDisplays.keyAt(i);
}
return displayIds;
}
}
@VisibleForTesting
GenericWindowPolicyController getDisplayWindowPolicyControllerForTest(int displayId) {
VirtualDisplayWrapper virtualDisplayWrapper;
synchronized (mVirtualDeviceLock) {
virtualDisplayWrapper = mVirtualDisplays.get(displayId);
}
return virtualDisplayWrapper != null ? virtualDisplayWrapper.getWindowPolicyController()
: null;
}
/**
* Returns true if an app with the given {@code uid} is currently running on this virtual
* device.
*/
boolean isAppRunningOnVirtualDevice(int uid) {
synchronized (mVirtualDeviceLock) {
for (int i = 0; i < mVirtualDisplays.size(); i++) {
if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
return true;
}
}
}
return false;
}
/**
* Shows a toast on virtual displays owned by this device which have a given uid running.
*/
void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration,
Looper looper) {
showToastWhereUidIsRunning(uid, mContext.getString(resId), duration, looper);
}
/**
* Shows a toast on virtual displays owned by this device which have a given uid running.
*/
void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
Looper looper) {
IntArray displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
if (displayIdsForUid.size() == 0) {
return;
}
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
for (int i = 0; i < displayIdsForUid.size(); i++) {
Display display = displayManager.getDisplay(displayIdsForUid.get(i));
if (display != null && display.isValid()) {
Toast.makeText(mContext.createDisplayContext(display), looper, text,
duration).show();
}
}
}
private IntArray getDisplayIdsWhereUidIsRunning(int uid) {
IntArray displayIdsForUid = new IntArray();
synchronized (mVirtualDeviceLock) {
for (int i = 0; i < mVirtualDisplays.size(); i++) {
if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
displayIdsForUid.add(mVirtualDisplays.keyAt(i));
}
}
}
return displayIdsForUid;
}
boolean isDisplayOwnedByVirtualDevice(int displayId) {
synchronized (mVirtualDeviceLock) {
return mVirtualDisplays.contains(displayId);
}
}
void onEnteringPipBlocked(int uid) {
// Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not
// support PiP.
}
void playSoundEffect(int effectType) {
try {
mSoundEffectListener.onPlaySoundEffect(effectType);
} catch (RemoteException exception) {
Slog.w(TAG, "Unable to invoke sound effect listener", exception);
}
}
/**
* 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 PendingTrampolineCallback {
/**
* Called when the callback should start waiting for the given pending trampoline.
* Implementations should try to listen for activity starts associated with the given
* {@code pendingTrampoline}, and launch the activity on the display with
* {@link PendingTrampoline#mDisplayId}.
*/
void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline);
/**
* Called when the callback should stop waiting for the given pending trampoline. This can
* happen, for example, when the pending intent failed to send.
*/
void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline);
}
/**
* A data class storing a pending trampoline this device is expecting.
*/
static class PendingTrampoline {
/**
* The original pending intent sent, for which a trampoline activity launch is expected.
*/
final PendingIntent mPendingIntent;
/**
* The result receiver associated with this pending call. {@link Activity#RESULT_OK} will
* be sent to the receiver if the trampoline activity was captured successfully.
* {@link Activity#RESULT_CANCELED} is sent otherwise.
*/
final ResultReceiver mResultReceiver;
/**
* The display ID to send the captured trampoline activity launch to.
*/
final int mDisplayId;
private PendingTrampoline(PendingIntent pendingIntent, ResultReceiver resultReceiver,
int displayId) {
mPendingIntent = pendingIntent;
mResultReceiver = resultReceiver;
mDisplayId = displayId;
}
@Override
public String toString() {
return "PendingTrampoline{"
+ "pendingIntent=" + mPendingIntent
+ ", resultReceiver=" + mResultReceiver
+ ", displayId=" + mDisplayId + "}";
}
}
/** Data class wrapping resources tied to single virtual display. */
private static final class VirtualDisplayWrapper {
private final IVirtualDisplayCallback mToken;
private final GenericWindowPolicyController mWindowPolicyController;
private final PowerManager.WakeLock mWakeLock;
VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
@NonNull GenericWindowPolicyController windowPolicyController,
@NonNull PowerManager.WakeLock wakeLock) {
mToken = Objects.requireNonNull(token);
mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
mWakeLock = Objects.requireNonNull(wakeLock);
}
GenericWindowPolicyController getWindowPolicyController() {
return mWindowPolicyController;
}
PowerManager.WakeLock getWakeLock() {
return mWakeLock;
}
IVirtualDisplayCallback getToken() {
return mToken;
}
}
}