Merge "Add VirtualDevice.launchPendingIntent"

This commit is contained in:
Maurice Lam
2022-01-15 01:34:54 +00:00
committed by Android (Google) Code Review
6 changed files with 298 additions and 12 deletions

View File

@@ -16,12 +16,14 @@
package android.companion.virtual;
import android.app.PendingIntent;
import android.graphics.Point;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
import android.os.ResultReceiver;
/**
* Interface for a virtual device.
@@ -41,6 +43,7 @@ interface IVirtualDevice {
* Closes the virtual device and frees all associated resources.
*/
void close();
void createVirtualKeyboard(
int displayId,
String inputDeviceName,
@@ -66,4 +69,10 @@ interface IVirtualDevice {
boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
/**
* Launches a pending intent on the given display that is owned by this virtual device.
*/
void launchPendingIntent(
int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver);
}

View File

@@ -23,6 +23,8 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.Activity;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.content.Context;
import android.graphics.Point;
@@ -33,15 +35,19 @@ import android.hardware.input.VirtualKeyboard;
import android.hardware.input.VirtualMouse;
import android.hardware.input.VirtualTouchscreen;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.view.Surface;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Executor;
/**
* System level service for managing virtual devices.
@@ -128,6 +134,49 @@ public final class VirtualDeviceManager {
mVirtualDevice = virtualDevice;
}
/**
* Launches a given pending intent on the give display ID.
*
* @param displayId The display to launch the pending intent on. This display must be
* created from this virtual device.
* @param pendingIntent The pending intent to be launched. If the intent is an activity
* intent, the activity will be started on the virtual display using
* {@link android.app.ActivityOptions#setLaunchDisplayId}. If the intent is a service or
* broadcast intent, an attempt will be made to catch activities started as a result of
* sending the pending intent and move them to the given display.
* @param executor The executor to run {@code launchCallback} on.
* @param launchCallback Callback that is called when the pending intent launching is
* complete.
*
* @hide
*/
public void launchPendingIntent(
int displayId,
@NonNull PendingIntent pendingIntent,
@NonNull Executor executor,
@NonNull LaunchCallback launchCallback) {
try {
mVirtualDevice.launchPendingIntent(
displayId,
pendingIntent,
new ResultReceiver(new Handler(Looper.myLooper())) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
executor.execute(() -> {
if (resultCode == Activity.RESULT_OK) {
launchCallback.onLaunchSuccess();
} else {
launchCallback.onLaunchFailed();
}
});
}
});
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
* Creates a virtual display for this virtual device. All displays created on the same
* device belongs to the same display group.
@@ -299,4 +348,22 @@ public final class VirtualDeviceManager {
}
}
}
/**
* Callback for launching pending intents on the virtual device.
*
* @hide
*/
// TODO(b/194949534): Unhide this API
public interface LaunchCallback {
/**
* Called when the pending intent launched successfully.
*/
void onLaunchSuccess();
/**
* Called when the pending intent failed to launch.
*/
void onLaunchFailed();
}
}

View File

@@ -23,6 +23,9 @@ import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
@@ -38,9 +41,11 @@ import android.hardware.input.VirtualTouchEvent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.window.DisplayWindowPolicyController;
@@ -55,10 +60,12 @@ import java.util.List;
final class VirtualDeviceImpl extends IVirtualDevice.Stub
implements IBinder.DeathRecipient {
private static final String TAG = "VirtualDeviceImpl";
private final Object mVirtualDeviceLock = new Object();
private final Context mContext;
private final AssociationInfo mAssociationInfo;
private final PendingTrampolineCallback mPendingTrampolineCallback;
private final int mOwnerUid;
private final InputController mInputController;
@VisibleForTesting
@@ -76,17 +83,18 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
IBinder token, int ownerUid, OnDeviceCloseListener listener,
VirtualDeviceParams params) {
PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
params);
pendingTrampolineCallback, params);
}
@VisibleForTesting
VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
int ownerUid, InputController inputController, OnDeviceCloseListener listener,
VirtualDeviceParams params) {
PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
mContext = context;
mAssociationInfo = associationInfo;
mPendingTrampolineCallback = pendingTrampolineCallback;
mOwnerUid = ownerUid;
mAppToken = token;
mParams = params;
@@ -120,6 +128,49 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mAssociationInfo.getId();
}
@Override // Binder call
public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
ResultReceiver resultReceiver) {
if (!mVirtualDisplayIds.contains(displayId)) {
throw new SecurityException("Display ID " + displayId
+ " not found for this virtual device");
}
if (pendingIntent.isActivity()) {
try {
sendPendingIntent(displayId, pendingIntent);
resultReceiver.send(Activity.RESULT_OK, null);
} catch (PendingIntent.CanceledException e) {
Slog.w(TAG, "Pending intent canceled", e);
resultReceiver.send(Activity.RESULT_CANCELED, null);
}
} else {
PendingTrampoline pendingTrampoline = new PendingTrampoline(pendingIntent,
resultReceiver, displayId);
mPendingTrampolineCallback.startWaitingForPendingTrampoline(pendingTrampoline);
try {
sendPendingIntent(displayId, pendingIntent);
} catch (PendingIntent.CanceledException e) {
Slog.w(TAG, "Pending intent canceled", e);
resultReceiver.send(Activity.RESULT_CANCELED, null);
mPendingTrampolineCallback.stopWaitingForPendingTrampoline(pendingTrampoline);
}
}
}
private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
throws PendingIntent.CanceledException {
pendingIntent.send(
mContext,
/* code= */ 0,
/* intent= */ null,
/* onFinished= */ null,
/* handler= */ null,
/* requiredPermission= */ null,
ActivityOptions.makeBasic()
.setLaunchDisplayId(displayId)
.toBundle());
}
@Override // Binder call
public void close() {
mListener.onClose(mAssociationInfo.getId());
@@ -276,6 +327,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
fout.println(" VirtualDevice: ");
fout.println(" mAssociationId: " + mAssociationInfo.getId());
fout.println(" mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
for (int id : mVirtualDisplayIds) {
@@ -346,4 +398,58 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
interface OnDeviceCloseListener {
void onClose(int associationId);
}
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 + "}";
}
}
}

View File

@@ -16,9 +16,13 @@
package com.android.server.companion.virtual;
import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityOptions;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
@@ -26,7 +30,9 @@ import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.ExceptionUtils;
@@ -37,6 +43,9 @@ import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceImpl.PendingTrampoline;
import com.android.server.wm.ActivityInterceptorCallback;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -48,10 +57,12 @@ import java.util.concurrent.ConcurrentHashMap;
public class VirtualDeviceManagerService extends SystemService {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "VirtualDeviceManagerService";
private static final String TAG = "VirtualDeviceManagerService";
private final Object mVirtualDeviceManagerLock = new Object();
private final VirtualDeviceManagerImpl mImpl;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
/**
* Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
@@ -80,10 +91,39 @@ public class VirtualDeviceManagerService extends SystemService {
mImpl = new VirtualDeviceManagerImpl();
}
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@Override
public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
if (info.callingPackage == null) {
return null;
}
PendingTrampoline pt = mPendingTrampolines.remove(info.callingPackage);
if (pt == null) {
return null;
}
pt.mResultReceiver.send(Activity.RESULT_OK, null);
ActivityOptions options = info.checkedOptions;
if (options == null) {
options = ActivityOptions.makeBasic();
}
return new ActivityInterceptResult(
info.intent, options.setLaunchDisplayId(pt.mDisplayId));
}
};
@Override
public void onStart() {
publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService(
ActivityTaskManagerInternal.class);
activityTaskManagerInternal.registerActivityStartInterceptor(
VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
mActivityInterceptorCallback);
}
@GuardedBy("mVirtualDeviceManagerLock")
@@ -128,7 +168,8 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
VirtualDeviceImpl.PendingTrampolineCallback {
@Override // Binder call
public IVirtualDevice createVirtualDevice(
@@ -164,7 +205,8 @@ public class VirtualDeviceManagerService extends SystemService {
mVirtualDevices.remove(associationId);
}
}
}, params);
},
this, params);
mVirtualDevices.put(associationInfo.getId(), virtualDevice);
return virtualDevice;
}
@@ -185,7 +227,7 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
} else {
Slog.w(LOG_TAG, "No associations for user " + callingUserId);
Slog.w(TAG, "No associations for user " + callingUserId);
}
return null;
}
@@ -196,7 +238,7 @@ public class VirtualDeviceManagerService extends SystemService {
try {
return super.onTransact(code, data, reply, flags);
} catch (Throwable e) {
Slog.e(LOG_TAG, "Error during IPC", e);
Slog.e(TAG, "Error during IPC", e);
throw ExceptionUtils.propagate(e, RemoteException.class);
}
}
@@ -205,7 +247,7 @@ public class VirtualDeviceManagerService extends SystemService {
public void dump(@NonNull FileDescriptor fd,
@NonNull PrintWriter fout,
@Nullable String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, fout)) {
return;
}
fout.println("Created virtual devices: ");
@@ -215,10 +257,24 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
}
@Override
public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
PendingTrampoline existing = mPendingTrampolines.put(
pendingTrampoline.mPendingIntent.getCreatorPackage(),
pendingTrampoline);
if (existing != null) {
existing.mResultReceiver.send(Activity.RESULT_CANCELED, null);
}
}
@Override
public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
}
}
private final class LocalService extends VirtualDeviceManagerInternal {
@Override
public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) {
synchronized (mVirtualDeviceManagerLock) {
@@ -272,4 +328,42 @@ public class VirtualDeviceManagerService extends SystemService {
return false;
}
}
private static final class PendingTrampolineMap {
/**
* The maximum duration, in milliseconds, to wait for a trampoline activity launch after
* invoking a pending intent.
*/
private static final int TRAMPOLINE_WAIT_MS = 5000;
private final ConcurrentHashMap<String, PendingTrampoline> mMap = new ConcurrentHashMap<>();
private final Handler mHandler;
PendingTrampolineMap(Handler handler) {
mHandler = handler;
}
PendingTrampoline put(
@NonNull String packageName, @NonNull PendingTrampoline pendingTrampoline) {
PendingTrampoline existing = mMap.put(packageName, pendingTrampoline);
mHandler.removeCallbacksAndMessages(existing);
mHandler.postDelayed(
() -> {
final String creatorPackage =
pendingTrampoline.mPendingIntent.getCreatorPackage();
if (creatorPackage != null) {
remove(creatorPackage);
}
},
pendingTrampoline,
TRAMPOLINE_WAIT_MS);
return existing;
}
PendingTrampoline remove(@NonNull String packageName) {
PendingTrampoline pendingTrampoline = mMap.remove(packageName);
mHandler.removeCallbacksAndMessages(pendingTrampoline);
return pendingTrampoline;
}
}
}

View File

@@ -55,6 +55,7 @@ public abstract class ActivityInterceptorCallback {
FIRST_ORDERED_ID,
COMMUNAL_MODE_ORDERED_ID,
PERMISSION_POLICY_ORDERED_ID,
VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
LAST_ORDERED_ID // Update this when adding new ids
})
@Retention(RetentionPolicy.SOURCE)
@@ -75,11 +76,17 @@ public abstract class ActivityInterceptorCallback {
*/
public static final int PERMISSION_POLICY_ORDERED_ID = 2;
/**
* The identifier for {@link com.android.server.companion.virtual.VirtualDeviceManagerService}
* interceptor.
*/
public static final int VIRTUAL_DEVICE_SERVICE_ORDERED_ID = 3;
/**
* The final id, used by the framework to determine the valid range of ids. Update this when
* adding new ids.
*/
static final int LAST_ORDERED_ID = PERMISSION_POLICY_ORDERED_ID;
static final int LAST_ORDERED_ID = VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
/**
* Data class for storing the various arguments needed for activity interception.

View File

@@ -71,6 +71,8 @@ public class VirtualDeviceManagerServiceTest {
private InputController.NativeWrapper mNativeWrapperMock;
@Mock
private DisplayManagerInternal mDisplayManagerInternalMock;
@Mock
private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
@Before
public void setUp() {
@@ -85,7 +87,8 @@ public class VirtualDeviceManagerServiceTest {
mInputController = new InputController(new Object(), mNativeWrapperMock);
mDeviceImpl = new VirtualDeviceImpl(mContext,
/* association info */ null, new Binder(), /* uid */ 0, mInputController,
(int associationId) -> {}, new VirtualDeviceParams.Builder().build());
(int associationId) -> {}, mPendingTrampolineCallback,
new VirtualDeviceParams.Builder().build());
}
@Test