Loading core/java/android/companion/virtual/IVirtualDevice.aidl +9 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -41,6 +43,7 @@ interface IVirtualDevice { * Closes the virtual device and frees all associated resources. */ void close(); void createVirtualKeyboard( int displayId, String inputDeviceName, Loading @@ -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); } core/java/android/companion/virtual/VirtualDeviceManager.java +67 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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(); } } services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +109 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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; Loading Loading @@ -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()); Loading Loading @@ -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) { Loading Loading @@ -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 + "}"; } } } services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +101 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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") Loading Loading @@ -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( Loading Loading @@ -164,7 +205,8 @@ public class VirtualDeviceManagerService extends SystemService { mVirtualDevices.remove(associationId); } } }, params); }, this, params); mVirtualDevices.put(associationInfo.getId(), virtualDevice); return virtualDevice; } Loading @@ -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; } Loading @@ -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); } } Loading @@ -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: "); Loading @@ -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); } } private final class LocalService extends VirtualDeviceManagerInternal { @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) { Loading Loading @@ -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; } } } services/core/java/com/android/server/wm/ActivityInterceptorCallback.java +8 −1 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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. Loading Loading
core/java/android/companion/virtual/IVirtualDevice.aidl +9 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -41,6 +43,7 @@ interface IVirtualDevice { * Closes the virtual device and frees all associated resources. */ void close(); void createVirtualKeyboard( int displayId, String inputDeviceName, Loading @@ -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); }
core/java/android/companion/virtual/VirtualDeviceManager.java +67 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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. Loading Loading @@ -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(); } }
services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +109 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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; Loading Loading @@ -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()); Loading Loading @@ -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) { Loading Loading @@ -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 + "}"; } } }
services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +101 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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") Loading Loading @@ -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( Loading Loading @@ -164,7 +205,8 @@ public class VirtualDeviceManagerService extends SystemService { mVirtualDevices.remove(associationId); } } }, params); }, this, params); mVirtualDevices.put(associationInfo.getId(), virtualDevice); return virtualDevice; } Loading @@ -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; } Loading @@ -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); } } Loading @@ -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: "); Loading @@ -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); } } private final class LocalService extends VirtualDeviceManagerInternal { @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) { Loading Loading @@ -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; } } }
services/core/java/com/android/server/wm/ActivityInterceptorCallback.java +8 −1 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -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. Loading