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

Commit 7b0bd6b8 authored by Vladimir Komsiyski's avatar Vladimir Komsiyski
Browse files

Add interactive mirror display interface

Bug: 432679227
Test: presubmit
Flag: android.companion.virtualdevice.flags.computer_control_access
Change-Id: I66a425d04370f8b521a4c0e19d11f2ef03d2b8a0
parent 3587ff71
Loading
Loading
Loading
Loading
+5 −9
Original line number Diff line number Diff line
@@ -16,12 +16,10 @@

package android.companion.virtual.computercontrol;

import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.IVirtualInputDevice;
import android.companion.virtual.computercontrol.IInteractiveMirrorDisplay;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.view.Surface;

/**
 * Interface for computer control session management.
@@ -39,11 +37,9 @@ interface IComputerControlSession {
    /** Injects a touch event into the trusted virtual display. */
    void sendTouchEvent(in VirtualTouchEvent event);

    /** Creates a virtual display mirroring the trusted one and returns the display ID. */
    int createMirrorDisplay(in VirtualDisplayConfig config, in IVirtualDisplayCallback callback);

    /** Creates a touchscreen associated with a mirror display. */
    IVirtualInputDevice createMirrorDisplayTouchscreen(in VirtualTouchscreenConfig config);
    /** Creates an interactive virtual display, mirroring the trusted one. */
    IInteractiveMirrorDisplay createInteractiveMirrorDisplay(
            int width, int height, in Surface surface);

    /** Closes this session. */
    void close();
+36 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.companion.virtual.computercontrol;

import android.hardware.input.VirtualTouchEvent;

/**
 * A display, mirroring a computer control session display, and its associated touchscreen.
 *
 * @hide
 */
oneway interface IInteractiveMirrorDisplay {

    /** Resize the mirror display and updates the associated touchscreen. */
    void resize(int width, int height);

    /** Injects a touch event into the mirror display. */
    void sendTouchEvent(in VirtualTouchEvent event);

    /** Closes this mirror display and the associated touchscreen. */
    void close();
}
+32 −34
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtual.computercontrol.IComputerControlSession;
import android.companion.virtual.computercontrol.IInteractiveMirrorDisplay;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.IntentSender;
@@ -43,8 +44,12 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A computer control session that encapsulates a {@link IVirtualDevice}. The device is created and
@@ -53,18 +58,21 @@ import java.util.Objects;
final class ComputerControlSessionImpl extends IComputerControlSession.Stub {

    private final IBinder mAppToken;
    private final ComputerControlSessionParams mParams;
    private final IVirtualDevice mVirtualDevice;
    private final int mVirtualDisplayId;
    private final IVirtualInputDevice mVirtualTouchscreen;
    private final IVirtualInputDevice mVirtualDpad;
    private final IVirtualInputDevice mVirtualKeyboard;
    private final AtomicInteger mMirrorDisplayCounter = new AtomicInteger(0);

    ComputerControlSessionImpl(IBinder appToken, ComputerControlSessionParams params,
            AttributionSource attributionSource, PackageManager packageManager,
            ComputerControlSessionProcessor.VirtualDeviceFactory virtualDeviceFactory) {
        mAppToken = appToken;
        mParams = params;
        VirtualDeviceParams virtualDeviceParams = new VirtualDeviceParams.Builder()
                .setName(params.name)
                .setName(mParams.name)
                .setDevicePolicy(VirtualDeviceParams.POLICY_TYPE_RECENTS,
                        VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
                .build();
@@ -76,7 +84,7 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub {

        int displayFlags = DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
                | DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED;
        if (params.isDisplayAlwaysUnlocked) {
        if (mParams.isDisplayAlwaysUnlocked) {
            displayFlags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
        }

@@ -90,9 +98,9 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub {
                new DisplayManagerGlobal.VirtualDisplayCallback(null, null);

        VirtualDisplayConfig virtualDisplayConfig = new VirtualDisplayConfig.Builder(
                params.name + "-display", params.displayWidthPx, params.displayHeightPx,
                params.displayDpi)
                .setSurface(params.displaySurface)
                mParams.name + "-display", mParams.displayWidthPx, mParams.displayHeightPx,
                mParams.displayDpi)
                .setSurface(mParams.displaySurface)
                .setFlags(displayFlags)
                .build();

@@ -106,7 +114,7 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub {
                    mVirtualDevice.createVirtualDisplay(
                            virtualDisplayConfig, virtualDisplayCallback));

            String dpadName = params.name + "-dpad";
            String dpadName = mParams.name + "-dpad";
            VirtualDpadConfig virtualDpadConfig =
                    new VirtualDpadConfig.Builder()
                            .setAssociatedDisplayId(mVirtualDisplayId)
@@ -115,7 +123,7 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub {
            mVirtualDpad = mVirtualDevice.createVirtualDpad(
                    virtualDpadConfig, new Binder(dpadName));

            String keyboardName = params.name  + "-keyboard";
            String keyboardName = mParams.name  + "-keyboard";
            VirtualKeyboardConfig virtualKeyboardConfig =
                    new VirtualKeyboardConfig.Builder()
                            .setAssociatedDisplayId(mVirtualDisplayId)
@@ -124,10 +132,10 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub {
            mVirtualKeyboard = mVirtualDevice.createVirtualKeyboard(
                    virtualKeyboardConfig, new Binder(keyboardName));

            String touchscreenName = params.name + "-touchscreen";
            String touchscreenName = mParams.name + "-touchscreen";
            VirtualTouchscreenConfig virtualTouchscreenConfig =
                    new VirtualTouchscreenConfig.Builder(
                            params.displayWidthPx, params.displayHeightPx)
                            mParams.displayWidthPx, mParams.displayHeightPx)
                            .setAssociatedDisplayId(mVirtualDisplayId)
                            .setInputDeviceName(touchscreenName)
                            .build();
@@ -159,31 +167,21 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub {
    }

    @Override
    public int createMirrorDisplay(
            @NonNull VirtualDisplayConfig config, @NonNull IVirtualDisplayCallback callback)
            throws RemoteException {
        Objects.requireNonNull(config);
        Objects.requireNonNull(callback);
        // The config in the app process is only used for the name and the surface, so we can
        // replace it here to ensure it only has the allowed properties and we don't leak
        // capabilities by creating the display with a clean identity (which we need for the mirror
        // display creation permission).
        VirtualDisplayConfig internalConfig = new VirtualDisplayConfig.Builder(
                config.getName(), config.getWidth(), config.getHeight(), config.getDensityDpi())
                .setSurface(config.getSurface())
    @NonNull
    public IInteractiveMirrorDisplay createInteractiveMirrorDisplay(
            int width, int height, @NonNull Surface surface) throws RemoteException {
        Objects.requireNonNull(surface);
        Display display = DisplayManagerGlobal.getInstance().getRealDisplay(mVirtualDisplayId);
        DisplayInfo displayInfo = new DisplayInfo();
        display.getDisplayInfo(displayInfo);
        String name = mParams.name + "-display-mirror-" + mMirrorDisplayCounter.getAndIncrement();
        VirtualDisplayConfig virtualDisplayConfig =
                new VirtualDisplayConfig.Builder(name, width, height, displayInfo.logicalDensityDpi)
                        .setSurface(surface)
                        .setDisplayIdToMirror(mVirtualDisplayId)
                        .setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
                        .build();
        return Binder.withCleanCallingIdentity(() ->
                mVirtualDevice.createVirtualDisplay(internalConfig, callback));

    }

    @Override
    public IVirtualInputDevice createMirrorDisplayTouchscreen(
            @NonNull VirtualTouchscreenConfig config) throws RemoteException {
        return mVirtualDevice.createVirtualTouchscreen(
                config, new Binder(config.getInputDeviceName()));
        return new InteractiveMirrorDisplayImpl(virtualDisplayConfig, mVirtualDevice);
    }

    @Override
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.computercontrol;

import android.annotation.NonNull;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.computercontrol.IInteractiveMirrorDisplay;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.IVirtualInputDevice;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
import android.os.RemoteException;
import android.view.Display;

import java.util.Objects;

/**
 * A wrapper around a mirror virtual display and the associated touchscreen.
 */
final class InteractiveMirrorDisplayImpl extends IInteractiveMirrorDisplay.Stub {

    private final VirtualDisplayConfig mVirtualDisplayConfig;
    private final IVirtualDevice mVirtualDevice;
    private final VirtualDisplay mVirtualDisplay;
    private IVirtualInputDevice mVirtualTouchscreen;

    InteractiveMirrorDisplayImpl(VirtualDisplayConfig virtualDisplayConfig,
            IVirtualDevice virtualDevice) throws RemoteException {
        mVirtualDisplayConfig = virtualDisplayConfig;
        mVirtualDevice = virtualDevice;

        // This is used as a death detection token to release the display upon app death. We're in
        // the system process, so this won't happen, but this is OK because we already do death
        // detection in the virtual device based on the app token and closing it will also release
        // the display.
        // The same applies to the input devices. We can't reuse the app token there because it's
        // used as a map key for the virtual input devices.
        IVirtualDisplayCallback virtualDisplayCallback =
                new DisplayManagerGlobal.VirtualDisplayCallback(null, null);
        int displayId = Binder.withCleanCallingIdentity(() ->
                mVirtualDevice.createVirtualDisplay(virtualDisplayConfig, virtualDisplayCallback));
        DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
        mVirtualDisplay = displayManager.createVirtualDisplayWrapper(
                virtualDisplayConfig, virtualDisplayCallback, displayId);

        createTouchscreen();
    }

    @Override
    public void resize(int width, int height) throws RemoteException {
        mVirtualDisplay.resize(width, height, mVirtualDisplayConfig.getDensityDpi());

        // Since there is no way to resize a touchscreen, just recreate it.
        mVirtualTouchscreen.close();
        createTouchscreen();
    }

    @Override
    public void sendTouchEvent(@NonNull VirtualTouchEvent event) throws RemoteException {
        mVirtualTouchscreen.sendTouchEvent(Objects.requireNonNull(event));
    }

    @Override
    public void close() throws RemoteException {
        mVirtualDisplay.release();
        mVirtualTouchscreen.close();
    }

    private void createTouchscreen() throws RemoteException {
        Display display = mVirtualDisplay.getDisplay();
        String touchscreenName = display.getName() + "-touchscreen";
        VirtualTouchscreenConfig virtualTouchscreenConfig =
                new VirtualTouchscreenConfig.Builder(display.getWidth(), display.getHeight())
                        .setAssociatedDisplayId(display.getDisplayId())
                        .setInputDeviceName(touchscreenName)
                        .build();
        mVirtualTouchscreen = mVirtualDevice.createVirtualTouchscreen(
                virtualTouchscreenConfig, new Binder(touchscreenName));
    }
}