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

Commit 6f262c58 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add interactive mirror display interface" into main

parents 383c8a28 7b0bd6b8
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));
    }
}