Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f487abc9 authored by Maurice Lam's avatar Maurice Lam Committed by Android (Google) Code Review
Browse files

Merge "Add VirtualDevice.launchPendingIntent"

parents 1439429d f03d832c
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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);
}
+67 −0
Original line number Diff line number Diff line
@@ -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();
    }
}
+109 −3
Original line number Diff line number Diff line
@@ -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 + "}";
        }
    }
}
+101 −7
Original line number Diff line number Diff line
@@ -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);
            }
        }

    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) {
@@ -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;
        }
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -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.
Loading