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

Commit ccb4fd07 authored by [D's avatar [D[1;5D Committed by Biswarup Pal
Browse files

Add wrappers around the binder CC interfaces.

Bug: 432679227
Test: presubmit & manual
Flag: android.companion.virtualdevice.flags.computer_control_access
Change-Id: I251ab9901c3b1c751533bdf34537bbe231a8e6c9
parent 4e2786de
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
import android.companion.virtual.camera.VirtualCamera;
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtual.computercontrol.ComputerControlSession;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtual.computercontrol.IComputerControlSession;
import android.companion.virtual.sensor.VirtualSensor;
@@ -212,12 +213,13 @@ public final class VirtualDeviceManager {
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_COMPUTER_CONTROL)
    @NonNull
    public IComputerControlSession createComputerControlSession(
    public ComputerControlSession createComputerControlSession(
            @NonNull ComputerControlSessionParams params) {
        Objects.requireNonNull(params, "params must not be null");
        try {
            return mService.createComputerControlSession(
            IComputerControlSession session = mService.createComputerControlSession(
                    new Binder(), mContext.getAttributionSource(), params);
            return new ComputerControlSession(session);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
+102 −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.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualTouchEvent;
import android.os.RemoteException;
import android.view.Surface;

import java.util.Objects;

/**
 * A session for automated control of applications.
 *
 * <p>A session is associated with a single trusted virtual display, capable of hosting activities,
 * along with the input devices that allow input injection.</p>
 *
 * @hide
 */
public final class ComputerControlSession implements AutoCloseable {

    private final IComputerControlSession mSession;

    /** @hide */
    public ComputerControlSession(@NonNull IComputerControlSession session) {
        mSession = Objects.requireNonNull(session);
    }

    /** Returns the ID of the single trusted virtual display for this session. */
    public int getVirtualDisplayId() {
        try {
            return mSession.getVirtualDisplayId();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /** Injects a key event into the trusted virtual display. */
    public void sendKeyEvent(@NonNull VirtualKeyEvent event) {
        try {
            mSession.sendKeyEvent(Objects.requireNonNull(event));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /** Injects a touch event into the trusted virtual display. */
    public void sendTouchEvent(@NonNull VirtualTouchEvent event) {
        try {
            mSession.sendTouchEvent(Objects.requireNonNull(event));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /** Creates an interactive virtual display, mirroring the trusted one. */
    @Nullable
    public InteractiveMirrorDisplay createInteractiveMirrorDisplay(
            @IntRange(from = 1) int width, @IntRange(from = 1) int height,
            @NonNull Surface surface) {
        Objects.requireNonNull(surface);
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("Display dimensions must be positive");
        }
        try {
            IInteractiveMirrorDisplay display =
                    mSession.createInteractiveMirrorDisplay(width, height, surface);
            if (display == null) {
                return null;
            }
            return new InteractiveMirrorDisplay(display);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @Override
    public void close() {
        try {
            mSession.close();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
+2 −27
Original line number Diff line number Diff line
@@ -16,30 +16,5 @@

package android.companion.virtual.computercontrol;

import android.view.Surface;

/**
 * Parameters for creating a computer control session.
 *
 * @hide
 */
parcelable ComputerControlSessionParams {

    /** The name of the session. Only used internally and not shown to users. */
    String name;

    /** The width of the display used for the session. */
    int displayWidthPx;

    /** The height of the display used for the session. */
    int displayHeightPx;

    /** The DPI of the display used for the session. */
    int displayDpi;

    /** The surface of the display used for the session. */
    Surface displaySurface;

    /** Whether the display used for the session should remain always unlocked. */
    boolean isDisplayAlwaysUnlocked;
}
/** @hide */
parcelable ComputerControlSessionParams ;
+253 −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.annotation.IntRange;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;

import java.util.Objects;

/**
 * Parameters for creating a {@link ComputerControlSession}.
 *
 * @hide
 */
public final class ComputerControlSessionParams implements Parcelable {

    private final String mName;
    private final int mDisplayWidthPx;
    private final int mDisplayHeightPx;
    private final int mDisplayDpi;
    private final Surface mDisplaySurface;
    private final boolean mIsDisplayAlwaysUnlocked;

    private ComputerControlSessionParams(
            @NonNull String name,
            int displayWidthPx,
            int displayHeightPx,
            int displayDpi,
            @NonNull Surface displaySurface,
            boolean isDisplayAlwaysUnlocked) {
        mName = name;
        mDisplayWidthPx = displayWidthPx;
        mDisplayHeightPx = displayHeightPx;
        mDisplayDpi = displayDpi;
        mDisplaySurface = displaySurface;
        mIsDisplayAlwaysUnlocked = isDisplayAlwaysUnlocked;
    }

    private ComputerControlSessionParams(Parcel parcel) {
        mName = parcel.readString8();
        mDisplayWidthPx = parcel.readInt();
        mDisplayHeightPx = parcel.readInt();
        mDisplayDpi = parcel.readInt();
        mDisplaySurface = parcel.readTypedObject(Surface.CREATOR);
        mIsDisplayAlwaysUnlocked = parcel.readBoolean();
    }

    /** Returns the name of this computer control session. */
    @NonNull
    public String getName() {
        return mName;
    }

    /** Returns the width of the display, in pixels. */
    public int getDisplayWidthPx() {
        return mDisplayWidthPx;
    }

    /** Returns the height of the display, in pixels. */
    public int getDisplayHeightPx() {
        return mDisplayHeightPx;
    }

    /** Returns the density of the display, in dpi. */
    public int getDisplayDpi() {
        return mDisplayDpi;
    }

    /** Returns the surface to which the display content should be rendered. */
    @NonNull
    public Surface getDisplaySurface() {
        return mDisplaySurface;
    }

    /** Returns true if the display should be always unlocked. */
    public boolean isDisplayAlwaysUnlocked() {
        return mIsDisplayAlwaysUnlocked;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString8(mName);
        dest.writeInt(mDisplayWidthPx);
        dest.writeInt(mDisplayHeightPx);
        dest.writeInt(mDisplayDpi);
        dest.writeTypedObject(mDisplaySurface, flags);
        dest.writeBoolean(mIsDisplayAlwaysUnlocked);
    }

    @NonNull
    public static final Creator<ComputerControlSessionParams> CREATOR = new Creator<>() {
        @Override
        @NonNull
        public ComputerControlSessionParams createFromParcel(@NonNull Parcel in) {
            return new ComputerControlSessionParams(in);
        }

        @Override
        @NonNull
        public ComputerControlSessionParams[] newArray(int size) {
            return new ComputerControlSessionParams[size];
        }
    };

    /** Builder for {@link ComputerControlSessionParams}. */
    public static final class Builder {
        private String mName;
        private int mDisplayWidthPx;
        private int mDisplayHeightPx;
        private int mDisplayDpi;
        private Surface mDisplaySurface;
        private boolean mIsDisplayAlwaysUnlocked;

        /**
         * Sets the name of this computer control session.
         *
         * @param name The name of the session.
         * @return This builder.
         */
        @NonNull
        public Builder setName(@NonNull String name) {
            if (name == null || name.isEmpty()) {
                throw new IllegalArgumentException("Name must not be empty");
            }
            mName = name;
            return this;
        }

        /**
         * Sets the width of the display, in pixels.
         *
         * @param displayWidthPx The width of the display.
         * @return This builder.
         */
        @NonNull
        public Builder setDisplayWidthPx(@IntRange(from = 1) int displayWidthPx) {
            if (displayWidthPx <= 0) {
                throw new IllegalArgumentException("Display width must be positive");
            }
            mDisplayWidthPx = displayWidthPx;
            return this;
        }

        /**
         * Sets the height of the display, in pixels.
         *
         * @param displayHeightPx The height of the display.
         * @return This builder.
         */
        @NonNull
        public Builder setDisplayHeightPx(@IntRange(from = 1) int displayHeightPx) {
            if (displayHeightPx <= 0) {
                throw new IllegalArgumentException("Display height must be positive");
            }
            mDisplayHeightPx = displayHeightPx;
            return this;
        }

        /**
         * Sets the density of the display, in dpi.
         *
         * @param displayDpi The density of the display.
         * @return This builder.
         */
        @NonNull
        public Builder setDisplayDpi(@IntRange(from = 1) int displayDpi) {
            if (displayDpi <= 0) {
                throw new IllegalArgumentException("Display DPI must be positive");
            }
            mDisplayDpi = displayDpi;
            return this;
        }

        /**
         * Sets the surface to which the display content should be rendered.
         *
         * @param displaySurface The surface for the display.
         * @return This builder.
         */
        @NonNull
        public Builder setDisplaySurface(@NonNull Surface displaySurface) {
            mDisplaySurface = Objects.requireNonNull(displaySurface);
            return this;
        }

        /**
         * Sets whether the display should be always unlocked.
         *
         * @param isDisplayAlwaysUnlocked true if the display should be always unlocked.
         * @return This builder.
         */
        @NonNull
        public Builder setDisplayAlwaysUnlocked(boolean isDisplayAlwaysUnlocked) {
            mIsDisplayAlwaysUnlocked = isDisplayAlwaysUnlocked;
            return this;
        }

        /**
         * Builds the {@link ComputerControlSessionParams} instance.
         *
         * @return The built {@link ComputerControlSessionParams}.
         * @throws IllegalArgumentException if the name or surface are not set, or if the display
         *     width, height, or dpi are not positive.
         */
        @NonNull
        public ComputerControlSessionParams build() {
            if (mName == null || mName.isEmpty()) {
                throw new IllegalArgumentException("Name must be set");
            }
            if (mDisplaySurface == null) {
                throw new IllegalArgumentException("Surface must be set");
            }
            if (mDisplayWidthPx <= 0) {
                throw new IllegalArgumentException("Display width must be positive");
            }
            if (mDisplayHeightPx <= 0) {
                throw new IllegalArgumentException("Display height must be positive");
            }
            if (mDisplayDpi <= 0) {
                throw new IllegalArgumentException("Display DPI must be positive");
            }
            return new ComputerControlSessionParams(
                    mName,
                    mDisplayWidthPx,
                    mDisplayHeightPx,
                    mDisplayDpi,
                    mDisplaySurface,
                    mIsDisplayAlwaysUnlocked);
        }
    }
}
+72 −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.annotation.IntRange;
import android.annotation.NonNull;
import android.hardware.input.VirtualTouchEvent;
import android.os.RemoteException;

import com.android.internal.annotations.VisibleForTesting;

import java.util.Objects;

/**
 * A display, mirroring a computer control session display, and its associated touchscreen.
 *
 * @hide
 */
public final class InteractiveMirrorDisplay implements AutoCloseable {

    private final IInteractiveMirrorDisplay mDisplay;

    /** @hide */
    @VisibleForTesting
    public InteractiveMirrorDisplay(@NonNull IInteractiveMirrorDisplay display) {
        mDisplay = Objects.requireNonNull(display);
    }

    /** Resizes the mirror display and updates the associated touchscreen. */
    public void resize(@IntRange(from = 1) int width, @IntRange(from = 1) int height) {
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("Display dimensions must be positive");
        }
        try {
            mDisplay.resize(width, height);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /** Injects a touch event into the mirror display. */
    public void sendTouchEvent(@NonNull VirtualTouchEvent event) {
        try {
            mDisplay.sendTouchEvent(Objects.requireNonNull(event));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @Override
    public void close() {
        try {
            mDisplay.close();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
Loading