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 Original line Diff line number Diff line
@@ -16,12 +16,10 @@


package android.companion.virtual.computercontrol;
package android.companion.virtual.computercontrol;


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


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


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

            int width, int height, in Surface surface);
    /** Creates a touchscreen associated with a mirror display. */
    IVirtualInputDevice createMirrorDisplayTouchscreen(in VirtualTouchscreenConfig config);


    /** Closes this session. */
    /** Closes this session. */
    void close();
    void close();
+36 −0
Original line number Original line 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 Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtual.computercontrol.IComputerControlSession;
import android.companion.virtual.computercontrol.IComputerControlSession;
import android.companion.virtual.computercontrol.IInteractiveMirrorDisplay;
import android.content.AttributionSource;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.IntentSender;
import android.content.IntentSender;
@@ -43,8 +44,12 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;


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


/**
/**
 * A computer control session that encapsulates a {@link IVirtualDevice}. The device is created and
 * 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 {
final class ComputerControlSessionImpl extends IComputerControlSession.Stub {


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


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


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


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


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


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


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


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


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


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

    }

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


    @Override
    @Override
+98 −0
Original line number Original line 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));
    }
}