/*
 * Copyright (C) 2021 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;

import static android.annotation.RestrictedForEnvironment.ENVIRONMENT_SDK_RUNTIME;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.RestrictedForEnvironment;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.role.RoleManager;
import android.companion.AssociationInfo;
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.AutomatedPackageListener;
import android.companion.virtual.computercontrol.ComputerControlSession;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtual.computercontrol.IAutomatedPackageListener;
import android.companion.virtual.computercontrol.IComputerControlSessionCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtualdevice.flags.Flags;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.VirtualDpad;
import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyboard;
import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouse;
import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualNavigationTouchpad;
import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualRotaryEncoder;
import android.hardware.input.VirtualRotaryEncoderConfig;
import android.hardware.input.VirtualStylus;
import android.hardware.input.VirtualStylusConfig;
import android.hardware.input.VirtualTouchscreen;
import android.hardware.input.VirtualTouchscreenConfig;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Build;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;

import com.android.internal.annotations.GuardedBy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;

/**
 * System level service for creation and management of virtual devices.
 *
 * <p>VirtualDeviceManager enables interactive sharing of capabilities between the host Android
 * device and a remote device.
 *
 * <p class="note">Not to be confused with the Android Studio's Virtual Device Manager, which allows
 * for device emulation.
 */
@RestrictedForEnvironment(
        environments = ENVIRONMENT_SDK_RUNTIME, from = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@SystemService(Context.VIRTUAL_DEVICE_SERVICE)
public final class VirtualDeviceManager {

    private static final String TAG = "VirtualDeviceManager";

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
            prefix = "LAUNCH_",
            value = {
                    LAUNCH_SUCCESS,
                    LAUNCH_FAILURE_PENDING_INTENT_CANCELED,
                    LAUNCH_FAILURE_NO_ACTIVITY})
    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
    public @interface PendingIntentLaunchStatus {}

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, prefix = "UI_MODE_", value = {
            Configuration.UI_MODE_TYPE_NORMAL,
            Configuration.UI_MODE_TYPE_DESK,
            Configuration.UI_MODE_TYPE_CAR,
            Configuration.UI_MODE_TYPE_TELEVISION,
            Configuration.UI_MODE_TYPE_APPLIANCE,
            Configuration.UI_MODE_TYPE_WATCH,
            Configuration.UI_MODE_TYPE_VR_HEADSET,
            Configuration.UI_MODE_NIGHT_NO,
            Configuration.UI_MODE_NIGHT_YES})
    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
    public @interface DisplayUiMode {}

    /**
     * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch was
     * successful.
     *
     * @hide
     */
    @SystemApi
    public static final int LAUNCH_SUCCESS = 0;

    /**
     * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch failed
     * because the pending intent was canceled.
     *
     * @hide
     */
    @SystemApi
    public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1;

    /**
     * Status for {@link VirtualDevice#launchPendingIntent}, indicating that the launch failed
     * because no activity starts were detected as a result of calling the pending intent.
     *
     * @hide
     */
    @SystemApi
    public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2;

    /**
     * Persistent device identifier corresponding to the default device.
     *
     * @see Context#DEVICE_ID_DEFAULT
     * @see VirtualDevice#getPersistentDeviceId()
     *
     * @hide
     */
    @SystemApi
    public static final String PERSISTENT_DEVICE_ID_DEFAULT =
            "default:" + Context.DEVICE_ID_DEFAULT;

    private final IVirtualDeviceManager mService;
    private final Context mContext;

    @GuardedBy("mVirtualDeviceListeners")
    private final List<VirtualDeviceListenerDelegate> mVirtualDeviceListeners = new ArrayList<>();

    @GuardedBy("mAutomatedPackageListeners")
    private final List<AutomatedPackageListenerDelegate> mAutomatedPackageListeners =
            new ArrayList<>();

    /** @hide */
    public VirtualDeviceManager(
            @Nullable IVirtualDeviceManager service, @NonNull Context context) {
        mService = service;
        mContext = context;
    }

    /**
     * Creates a virtual device where applications can launch and receive input events injected by
     * the creator.
     *
     * <p>The {@link android.Manifest.permission#CREATE_VIRTUAL_DEVICE} permission is required to
     * create virtual devices, which is only available to system apps holding specific roles.
     *
     * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
     *   Companion Device Manager. Virtual devices must have a corresponding association with CDM in
     *   order to be created.
     * @param params The parameters for creating virtual devices. See {@link VirtualDeviceParams}
     *   for the available options.
     * @return The created virtual device.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
    @NonNull
    public VirtualDevice createVirtualDevice(
            int associationId,
            @NonNull VirtualDeviceParams params) {
        Objects.requireNonNull(params, "params must not be null");
        try {
            return new VirtualDevice(mService, mContext, associationId, params);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Requests the creation of a new {@link ComputerControlSession}.
     *
     * @param params The configuration of the session.
     * @param executor An executor to run the callback on.
     * @param callback A callback to get notified about the result of this operation.
     *
     * @throws IllegalArgumentException when the given params contain invalid information.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_COMPUTER_CONTROL)
    public void requestComputerControlSession(
            @NonNull ComputerControlSessionParams params,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull ComputerControlSession.Callback callback) {
        if (mService == null) {
            Log.w(TAG, "Failed to request a new session; no virtual device manager service.");
            return;
        }
        Objects.requireNonNull(params, "params must not be null");
        Objects.requireNonNull(executor, "executor must not be null");
        Objects.requireNonNull(callback, "callback must not be null");
        try {
            IComputerControlSessionCallback callbackProxy =
                    new ComputerControlSession.CallbackProxy(executor, callback);
            mService.requestComputerControlSession(
                    mContext.getAttributionSource(), params, callbackProxy);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the details of all available virtual devices.
     *
     * <p>The returned objects are read-only representations that expose the properties of all
     * existing virtual devices.</p>
     *
     * <p>Note that if a virtual device is closed and becomes invalid, the returned objects will
     * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real
     * time updates of the availability  of virtual devices.</p>
     */
    @NonNull
    public List<android.companion.virtual.VirtualDevice> getVirtualDevices() {
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
            return new ArrayList<>();
        }
        try {
            return mService.getVirtualDevices();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the details of the virtual device with the given ID, if any.
     *
     * <p>The returned object is a read-only representation of the virtual device that expose its
     * properties.</p>
     *
     * <p>Note that if the virtual device is closed and becomes invalid, the returned object will
     * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real
     * time updates of the availability of virtual devices.</p>
     *
     * @return the virtual device with the requested ID, or {@code null} if no such device exists or
     *   it has already been closed.
     */
    @Nullable
    public android.companion.virtual.VirtualDevice getVirtualDevice(int deviceId) {
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
            return null;
        }
        if (deviceId == Context.DEVICE_ID_INVALID || deviceId == Context.DEVICE_ID_DEFAULT) {
            return null;  // Don't even bother making a Binder call.
        }
        try {
            return mService.getVirtualDevice(deviceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Registers a virtual device listener to receive notifications when virtual devices are created
     * or closed.
     *
     * @param executor The executor where the listener is executed on.
     * @param listener The listener to add.
     * @see #unregisterVirtualDeviceListener
     */
    public void registerVirtualDeviceListener(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull VirtualDeviceListener listener) {
        if (mService == null) {
            Log.w(TAG, "Failed to register listener; no virtual device manager service.");
            return;
        }
        final VirtualDeviceListenerDelegate delegate =
                new VirtualDeviceListenerDelegate(Objects.requireNonNull(executor),
                        Objects.requireNonNull(listener));
        synchronized (mVirtualDeviceListeners) {
            try {
                mService.registerVirtualDeviceListener(delegate);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            mVirtualDeviceListeners.add(delegate);
        }
    }

    /**
     * Unregisters a virtual device listener previously registered with
     * {@link #registerVirtualDeviceListener}.
     *
     * @param listener The listener to unregister.
     * @see #registerVirtualDeviceListener
     */
    public void unregisterVirtualDeviceListener(@NonNull VirtualDeviceListener listener) {
        if (mService == null) {
            Log.w(TAG, "Failed to unregister listener; no virtual device manager service.");
            return;
        }
        Objects.requireNonNull(listener);
        synchronized (mVirtualDeviceListeners) {
            final Iterator<VirtualDeviceListenerDelegate> it = mVirtualDeviceListeners.iterator();
            while (it.hasNext()) {
                final VirtualDeviceListenerDelegate delegate = it.next();
                if (delegate.mListener == listener) {
                    try {
                        mService.unregisterVirtualDeviceListener(delegate);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                    it.remove();
                }
            }
        }
    }

    /**
     * Registers a listener to receive notifications when the set of automated apps changes.
     *
     * @param executor The executor where the listener is executed on.
     * @param listener The listener to add.
     * @throws SecurityException if the caller does not hold the {@link RoleManager#ROLE_HOME} role.
     * @see #unregisterAutomatedPackageListener
     * @hide
     */
    public void registerAutomatedPackageListener(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull AutomatedPackageListener listener) {
        if (mService == null) {
            Log.w(TAG, "Failed to register listener; no virtual device manager service.");
            return;
        }
        final AutomatedPackageListenerDelegate delegate =
                new AutomatedPackageListenerDelegate(Objects.requireNonNull(executor),
                        Objects.requireNonNull(listener));
        synchronized (mAutomatedPackageListeners) {
            try {
                mService.registerAutomatedPackageListener(delegate);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            mAutomatedPackageListeners.add(delegate);
        }
    }

    /**
     * Unregisters a listener previously registered with {@link #registerAutomatedPackageListener}.
     *
     * @param listener The listener to unregister.
     * @throws SecurityException if the caller does not hold the {@link RoleManager#ROLE_HOME} role.
     * @see #registerAutomatedPackageListener
     * @hide
     */
    public void unregisterAutomatedPackageListener(@NonNull AutomatedPackageListener listener) {
        if (mService == null) {
            Log.w(TAG, "Failed to unregister listener; no virtual device manager service.");
            return;
        }
        Objects.requireNonNull(listener);
        synchronized (mAutomatedPackageListeners) {
            final Iterator<AutomatedPackageListenerDelegate> it =
                    mAutomatedPackageListeners.iterator();
            while (it.hasNext()) {
                final AutomatedPackageListenerDelegate delegate = it.next();
                if (delegate.mListener == listener) {
                    try {
                        mService.unregisterAutomatedPackageListener(delegate);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                    it.remove();
                }
            }
        }
    }

    /**
     * Returns the device policy for the given virtual device and policy type.
     *
     * <p>In case the virtual device identifier is not valid,
     * {@link VirtualDeviceParams#DEVICE_POLICY_INVALID} is returned. If there's no explicitly
     * specified policy for that device and policy type, then
     * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
     *
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
            int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
        if (deviceId == Context.DEVICE_ID_DEFAULT) {
            // Avoid unnecessary binder call, for default device, policy will be always default.
            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
        }
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service.");
            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
        }
        try {
            return mService.getDevicePolicy(deviceId, policyType);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the device policy for the display with the given ID and the given policy type.
     *
     * <p>In case the display does not exist or is not owned by a virtual device,
     * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
     *
     * @hide
     */
    public @VirtualDeviceParams.DevicePolicy int getDevicePolicyForDisplayId(
            int displayId, @VirtualDeviceParams.PolicyType int policyType) {
        if (displayId == Context.DEVICE_ID_DEFAULT) {
            // Avoid unnecessary binder call, for default display, policy will be always default.
            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
        }
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service.");
            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
        }
        try {
            return mService.getDevicePolicyForDisplayId(displayId, policyType);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the ID of the device which owns the display with the given ID.
     *
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    public int getDeviceIdForDisplayId(int displayId) {
        if (displayId == Display.DEFAULT_DISPLAY || displayId == Display.INVALID_DISPLAY) {
            // Avoid unnecessary binder call for default / invalid display id.
            return Context.DEVICE_ID_DEFAULT;
        }
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
            return Context.DEVICE_ID_DEFAULT;
        }
        try {
            return mService.getDeviceIdForDisplayId(displayId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get the display name for a given persistent device ID.
     *
     * <p>This will work even if currently there is no valid virtual device with the given
     * persistent ID, as long as such a device has been created or can be created.</p>
     *
     * @return the display name associated with the given persistent device ID, or {@code null} if
     *     the persistent ID is invalid or does not correspond to a virtual device.
     *
     * @see VirtualDevice#getPersistentDeviceId()
     * @hide
     */
    @SystemApi
    @Nullable
    public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String persistentDeviceId) {
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
            return null;
        }
        try {
            return mService.getDisplayNameForPersistentDeviceId(
                    Objects.requireNonNull(persistentDeviceId));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns all current persistent device IDs, including the ones for which no virtual device
     * exists, as long as one may have existed or can be created.
     *
     * @see VirtualDevice#getPersistentDeviceId()
     * @hide
     */
    @SystemApi
    @NonNull
    public Set<String> getAllPersistentDeviceIds() {
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve persistent ids; no virtual device manager service.");
            return Collections.emptySet();
        }
        try {
            return new ArraySet<>(mService.getAllPersistentDeviceIds());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
     * {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
     * device which is not a virtual device. {@code deviceId} must correspond to a virtual device
     * created by {@link VirtualDeviceManager#createVirtualDevice(int, VirtualDeviceParams)}.
     *
     * @hide
     */
    public boolean isValidVirtualDeviceId(int deviceId) {
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
            return false;
        }
        try {
            return mService.isValidVirtualDeviceId(deviceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns device-specific audio session id for audio playback.
     *
     * @param deviceId - id of the virtual audio device
     * @return Device specific session id to be used for audio playback (see
     *   {@link AudioManager#generateAudioSessionId}) if virtual device has
     *   {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to
     *   {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device
     *   is configured in context-aware mode. Otherwise
     *   {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned.
     *
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    public int getAudioPlaybackSessionId(int deviceId) {
        if (mService == null) {
            return AUDIO_SESSION_ID_GENERATE;
        }
        try {
            return mService.getAudioPlaybackSessionId(deviceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns device-specific audio session id for audio recording.
     *
     * @param deviceId - id of the virtual audio device
     * @return Device specific session id to be used for audio recording (see
     *   {@link AudioManager#generateAudioSessionId}) if virtual device has
     *   {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to
     *   {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device
     *   is configured in context-aware mode. Otherwise
     *   {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned.
     *
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    public int getAudioRecordingSessionId(int deviceId) {
        if (mService == null) {
            return AUDIO_SESSION_ID_GENERATE;
        }
        try {
            return mService.getAudioRecordingSessionId(deviceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Requests sound effect to be played on virtual device.
     *
     * @see AudioManager#playSoundEffect(int)
     *
     * @param deviceId - id of the virtual audio device
     * @param effectType the type of sound effect
     *
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
        if (mService == null) {
            Log.w(TAG, "Failed to dispatch sound effect; no virtual device manager service.");
            return;
        }
        try {
            mService.playSoundEffect(deviceId, effectType);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns whether the given display is an auto-mirror display owned by a virtual device.
     *
     * @hide
     */
    @TestApi
    public boolean isVirtualDeviceOwnedMirrorDisplay(int displayId) {
        if (mService == null) {
            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
            return false;
        }
        try {
            return mService.isVirtualDeviceOwnedMirrorDisplay(displayId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * A representation of a virtual device.
     *
     * <p>A virtual device can have its own virtual displays, audio input/output, sensors, etc.
     * The creator of a virtual device can take the output from the virtual display and stream it
     * over to another device, and inject input and sensor events that are received from the remote
     * device.
     *
     * <p>This object is only used by the virtual device creator and allows them to manage the
     * device's behavior, peripherals, and the user interaction with that device.
     *
     * <p class="note">Not to be confused with {@link android.companion.virtual.VirtualDevice},
     * which is a read-only representation exposing the properties of an existing virtual device.
     *
     * @hide
     */
    @SystemApi
    public static class VirtualDevice implements AutoCloseable {

        private final VirtualDeviceInternal mVirtualDeviceInternal;

        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
        private VirtualDevice(
                IVirtualDeviceManager service,
                Context context,
                int associationId,
                VirtualDeviceParams params) throws RemoteException {
            mVirtualDeviceInternal =
                    new VirtualDeviceInternal(service, context, associationId, params);
        }

        /** @hide */
        public VirtualDevice(Context context, IVirtualDevice virtualDevice) {
            mVirtualDeviceInternal = new VirtualDeviceInternal(context, virtualDevice);
        }

        /**
         * Returns the unique ID of this virtual device.
         */
        public int getDeviceId() {
            return mVirtualDeviceInternal.getDeviceId();
        }

        /**
         * Returns the persistent ID of this virtual device.
         */
        public @Nullable String getPersistentDeviceId() {
            return mVirtualDeviceInternal.getPersistentDeviceId();
        }

        /**
         * Returns a new context bound to this device.
         *
         * <p>This is a convenience method equivalent to calling
         * {@link Context#createDeviceContext(int)} with the id of this device.
         */
        public @NonNull Context createContext() {
            return mVirtualDeviceInternal.createContext();
        }

        /**
         * Returns this device's sensors.
         *
         * @see VirtualDeviceParams.Builder#addVirtualSensorConfig
         *
         * @return A list of all sensors for this device, or an empty list if no sensors exist.
         */
        @NonNull
        public List<VirtualSensor> getVirtualSensorList() {
            return mVirtualDeviceInternal.getVirtualSensorList();
        }

        /**
         * Forces all trusted non-mirror displays of the virtual device to turn off.
         *
         * <p>After this action, if all displays across all devices, including the default one, are
         * off, then the physical device will be put to sleep. If the displays of this virtual
         * device are already off, then nothing will happen.</p>
         *
         * <p>Overrides all the wake locks that are held. This is equivalent to pressing a "virtual
         * power key" to turn off the screen.</p>
         *
         * @see #wakeUp()
         * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
         * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
         */
        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
        public void goToSleep() {
            mVirtualDeviceInternal.goToSleep();
        }

        /**
         * Forces all trusted non-mirror displays of the virtual device to turn on.
         *
         * <p>If the displays of this virtual device are turned off, then they will be turned on.
         * Additionally, if the device is asleep it will be awoken. If the displays of this virtual
         * device are already on, then nothing will happen.</p>
         *
         * <p>This is equivalent to pressing a "virtual power key" to turn on the screen.</p>
         *
         * @see #goToSleep()
         * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
         * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
         */
        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
        public void wakeUp() {
            mVirtualDeviceInternal.wakeUp();
        }

        /**
         * 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. When it completes,
         *   {@code listener} will be called with the status of whether the launch attempt is
         *   successful or not.
         * @param executor The executor to run {@code launchCallback} on.
         * @param listener Listener that is called when the pending intent launching is complete.
         *   The argument is {@link #LAUNCH_SUCCESS} if the launch successfully started an activity
         *   on the virtual display, or one of the {@code LAUNCH_FAILED} status explaining why it
         *   failed.
         */
        public void launchPendingIntent(
                int displayId,
                @NonNull PendingIntent pendingIntent,
                @NonNull Executor executor,
                @NonNull IntConsumer listener) {
            Objects.requireNonNull(pendingIntent, "pendingIntent must not be null");
            Objects.requireNonNull(executor, "executor must not be null");
            Objects.requireNonNull(listener, "listener must not be null");
            mVirtualDeviceInternal.launchPendingIntent(
                    displayId, pendingIntent, executor, listener);
        }

        /**
         * Creates a virtual display for this virtual device. All displays created on the same
         * device belongs to the same display group.
         *
         * @param width The width of the virtual display in pixels, must be greater than 0.
         * @param height The height of the virtual display in pixels, must be greater than 0.
         * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
         * @param surface The surface to which the content of the virtual display should
         *   be rendered, or null if there is none initially. The surface can also be set later
         *   using {@link VirtualDisplay#setSurface(Surface)}.
         * @param flags A combination of virtual display flags accepted by
         *   {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
         *   automatically set for all virtual devices:
         *   {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC} and
         *   {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
         * @param executor The executor on which {@code callback} will be invoked. This is ignored
         *   if {@code callback} is {@code null}. If {@code callback} is specified, this executor
         *   must not be null.
         * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
         * @return The newly created virtual display, or {@code null} if the application could
         *   not create the virtual display.
         *
         * @see DisplayManager#createVirtualDisplay
         *
         * @deprecated use {@link #createVirtualDisplay(VirtualDisplayConfig, Executor,
         * VirtualDisplay.Callback)}
         */
        @Deprecated
        @Nullable
        public VirtualDisplay createVirtualDisplay(
                @IntRange(from = 1) int width,
                @IntRange(from = 1) int height,
                @IntRange(from = 1) int densityDpi,
                @Nullable Surface surface,
                @VirtualDisplayFlag int flags,
                @Nullable @CallbackExecutor Executor executor,
                @Nullable VirtualDisplay.Callback callback) {
            // Currently this just uses the device ID, which means all of the virtual displays
            // created using the same virtual device will have the same name if they use this
            // deprecated API. The name should only be used for informational purposes, and not for
            // identifying the display in code.
            String virtualDisplayName =  "VirtualDevice_" + getDeviceId();
            VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
                    virtualDisplayName, width, height, densityDpi)
                    .setFlags(flags);
            if (surface != null) {
                builder.setSurface(surface);
            }
            return mVirtualDeviceInternal.createVirtualDisplay(builder.build(), executor, callback);
        }

        /**
         * Creates a virtual display for this virtual device. All displays created on the same
         * device belongs to the same display group.
         *
         * @param config The configuration of the display.
         * @param executor The executor on which {@code callback} will be invoked. This is ignored
         *   if {@code callback} is {@code null}. If {@code callback} is specified, this executor
         *   must not be null.
         * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
         * @return The newly created virtual display, or {@code null} if the application could
         *   not create the virtual display.
         *
         * @see DisplayManager#createVirtualDisplay
         */
        @Nullable
        public VirtualDisplay createVirtualDisplay(
                @NonNull VirtualDisplayConfig config,
                @Nullable @CallbackExecutor Executor executor,
                @Nullable VirtualDisplay.Callback callback) {
            Objects.requireNonNull(config, "config must not be null");
            return mVirtualDeviceInternal.createVirtualDisplay(config, executor, callback);
        }

        /**
         * Closes the virtual device, stopping and tearing down any virtual displays, associated
         * virtual audio device, and event injection that's currently in progress.
         */
        public void close() {
            mVirtualDeviceInternal.close();
        }

        /**
         * Specifies a policy for this virtual device.
         *
         * <p>Policies define the system behavior that may be specific for this virtual device. The
         * given policy must be able to be changed dynamically during the lifetime of the device.
         *
         * @param policyType the type of policy, i.e. which behavior to specify a policy for.
         * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
         * @throws IllegalArgumentException if the policy cannot be changed at runtime.
         *
         * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
         * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
         */
        public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
                @VirtualDeviceParams.DevicePolicy int devicePolicy) {
            mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
        }

        /**
         * Specifies a component name to be exempt from the current activity launch policy.
         *
         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
         * then the specified component will be blocked from launching.
         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
         * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
         * specified component will be allowed to launch.</p>
         *
         * <p>Note that changing the activity launch policy will clear current set of exempt
         * components.</p>
         *
         * @see #removeActivityPolicyExemption(ComponentName)
         * @see #setDevicePolicy
         */
        public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
            addActivityPolicyExemption(new ActivityPolicyExemption.Builder()
                    .setComponentName(componentName)
                    .build());
        }

        /**
         * Makes the specified component name to adhere to the default activity launch policy.
         *
         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
         * then the specified component will be allowed to launch.
         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
         * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
         * specified component will be blocked from launching.</p>
         *
         * <p>Note that changing the activity launch policy will clear current set of exempt
         * components.</p>
         *
         * @see #addActivityPolicyExemption(ComponentName)
         * @see #setDevicePolicy
         */
        public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
            removeActivityPolicyExemption(new ActivityPolicyExemption.Builder()
                    .setComponentName(componentName)
                    .build());
        }

        /**
         * Specifies an exemption from the current activity launch policy.
         *
         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
         * then all exempt activities be blocked from launching.
         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
         * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then all
         * exempt activities will be allowed to launch.</p>
         *
         * <p>Note that changing the activity launch policy will clear current set of exempt
         * packages.</p>
         * <p>Any change to the exemptions will only be applied for new activity launches.</p>
         *
         * @see #removeActivityPolicyExemption(ActivityPolicyExemption)
         * @see #setDevicePolicy
         */
        @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
        public void addActivityPolicyExemption(@NonNull ActivityPolicyExemption exemption) {
            mVirtualDeviceInternal.addActivityPolicyExemption(Objects.requireNonNull(exemption));
        }

        /**
         * Removes an exemption from the current activity launch policy.
         *
         * <p>Note that changing the activity launch policy will clear current set of exempt
         * packages.</p>
         * <p>Any change to the exemptions will only be applied for new activity launches.</p>
         *
         * @see #addActivityPolicyExemption(ActivityPolicyExemption)
         * @see #setDevicePolicy
         */
        @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
        public void removeActivityPolicyExemption(@NonNull ActivityPolicyExemption exemption) {
            mVirtualDeviceInternal.removeActivityPolicyExemption(Objects.requireNonNull(exemption));
        }

        /**
         * Specifies a policy for this virtual device to be applied on the given virtual display.
         * <p>
         * Any policy specified for a particular display takes precedence over the policy specified
         * for the device itself.
         * </p>
         *
         * @param policyType the type of policy, i.e. which behavior to specify a policy for.
         * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
         * @param displayId the ID of the display, for which to apply the policy.
         * @throws IllegalArgumentException if the specified policy cannot be changed per
         *   display or if the specified display does not belong to the virtual device.
         *
         * @see #setDevicePolicy(int, int)
         * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
         * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
         */
        @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
        public void setDevicePolicy(
                @VirtualDeviceParams.DynamicDisplayPolicyType int policyType,
                @VirtualDeviceParams.DevicePolicy int devicePolicy,
                int displayId) {
            mVirtualDeviceInternal.setDevicePolicyForDisplay(displayId, policyType, devicePolicy);
        }

        /**
         * Creates a virtual dpad.
         *
         * @param config the configurations of the virtual dpad.
         */
        @NonNull
        public VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
            Objects.requireNonNull(config, "config must not be null");
            return mVirtualDeviceInternal.createVirtualDpad(config);
        }

        /**
         * Creates a virtual keyboard.
         *
         * @param config the configurations of the virtual keyboard.
         */
        @NonNull
        public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
            Objects.requireNonNull(config, "config must not be null");
            return mVirtualDeviceInternal.createVirtualKeyboard(config);
        }

        /**
         * Creates a virtual keyboard.
         *
         * @param display the display that the events inputted through this device should target.
         * @param inputDeviceName the name of this keyboard device.
         * @param vendorId the PCI vendor id.
         * @param productId the product id, as defined by the vendor.
         * @see #createVirtualKeyboard(VirtualKeyboardConfig config)
         * @deprecated Use {@link #createVirtualKeyboard(VirtualKeyboardConfig config)} instead
         */
        @Deprecated
        @NonNull
        public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualDisplay display,
                @NonNull String inputDeviceName, int vendorId, int productId) {
            VirtualKeyboardConfig keyboardConfig =
                    new VirtualKeyboardConfig.Builder()
                            .setVendorId(vendorId)
                            .setProductId(productId)
                            .setInputDeviceName(inputDeviceName)
                            .setAssociatedDisplayId(display.getDisplay().getDisplayId())
                            .build();
            return mVirtualDeviceInternal.createVirtualKeyboard(keyboardConfig);
        }

        /**
         * Creates a virtual mouse.
         *
         * @param config the configurations of the virtual mouse.
         */
        @NonNull
        public VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
            Objects.requireNonNull(config, "config must not be null");
            return mVirtualDeviceInternal.createVirtualMouse(config);
        }

        /**
         * Creates a virtual mouse.
         *
         * @param display the display that the events inputted through this device should target.
         * @param inputDeviceName the name of this mouse.
         * @param vendorId the PCI vendor id.
         * @param productId the product id, as defined by the vendor.
         * @see #createVirtualMouse(VirtualMouseConfig config)
         * @deprecated Use {@link #createVirtualMouse(VirtualMouseConfig config)} instead
         */
        @Deprecated
        @NonNull
        public VirtualMouse createVirtualMouse(@NonNull VirtualDisplay display,
                @NonNull String inputDeviceName, int vendorId, int productId) {
            VirtualMouseConfig mouseConfig =
                    new VirtualMouseConfig.Builder()
                            .setVendorId(vendorId)
                            .setProductId(productId)
                            .setInputDeviceName(inputDeviceName)
                            .setAssociatedDisplayId(display.getDisplay().getDisplayId())
                            .build();
            return mVirtualDeviceInternal.createVirtualMouse(mouseConfig);
        }

        /**
         * Creates a virtual touchscreen.
         *
         * @param config the configurations of the virtual touchscreen.
         */
        @NonNull
        public VirtualTouchscreen createVirtualTouchscreen(
                @NonNull VirtualTouchscreenConfig config) {
            Objects.requireNonNull(config, "config must not be null");
            return mVirtualDeviceInternal.createVirtualTouchscreen(config);
        }

        /**
         * Creates a virtual touchscreen.
         *
         * @param display the display that the events inputted through this device should target.
         * @param inputDeviceName the name of this touchscreen device.
         * @param vendorId the PCI vendor id.
         * @param productId the product id, as defined by the vendor.
         * @see #createVirtualTouchscreen(VirtualTouchscreenConfig config)
         * @deprecated Use {@link #createVirtualTouchscreen(VirtualTouchscreenConfig config)}
         * instead
         */
        @Deprecated
        @NonNull
        public VirtualTouchscreen createVirtualTouchscreen(@NonNull VirtualDisplay display,
                @NonNull String inputDeviceName, int vendorId, int productId) {
            final Point size = new Point();
            display.getDisplay().getSize(size);
            VirtualTouchscreenConfig touchscreenConfig =
                    new VirtualTouchscreenConfig.Builder(size.x, size.y)
                            .setVendorId(vendorId)
                            .setProductId(productId)
                            .setInputDeviceName(inputDeviceName)
                            .setAssociatedDisplayId(display.getDisplay().getDisplayId())
                            .build();
            return mVirtualDeviceInternal.createVirtualTouchscreen(touchscreenConfig);
        }

        /**
         * Creates a virtual touchpad in navigation mode.
         *
         * <p>A touchpad in navigation mode means that its events are interpreted as navigation
         * events (up, down, etc) instead of using them to update a cursor's absolute position. If
         * the events are not consumed they are converted to DPAD events and delivered to the target
         * again.
         *
         * @param config the configurations of the virtual navigation touchpad.
         * @see android.view.InputDevice#SOURCE_TOUCH_NAVIGATION
         */
        @NonNull
        public VirtualNavigationTouchpad createVirtualNavigationTouchpad(
                @NonNull VirtualNavigationTouchpadConfig config) {
            return mVirtualDeviceInternal.createVirtualNavigationTouchpad(config);
        }

        /**
         * Creates a virtual stylus.
         *
         * @param config the touchscreen configurations for the virtual stylus.
         */
        @NonNull
        public VirtualStylus createVirtualStylus(@NonNull VirtualStylusConfig config) {
            return mVirtualDeviceInternal.createVirtualStylus(config);
        }

        /**
         * Creates a virtual rotary encoder.
         *
         * @param config the configuration for the virtual rotary encoder.
         * @see android.view.InputDevice#SOURCE_ROTARY_ENCODER
         */
        @NonNull
        @FlaggedApi(Flags.FLAG_VIRTUAL_ROTARY)
        public VirtualRotaryEncoder createVirtualRotaryEncoder(
                @NonNull VirtualRotaryEncoderConfig config) {
            if (!Flags.virtualRotary()) {
                throw new UnsupportedOperationException("Virtual rotary support not enabled");
            }
            return mVirtualDeviceInternal.createVirtualRotaryEncoder(config);
        }

        /**
         * Creates a VirtualAudioDevice, capable of recording audio emanating from this device,
         * or injecting audio from another device.
         *
         * <p>Note: One {@link VirtualDevice} can only create one {@link VirtualAudioDevice}, so
         * calling this method multiple times will return the same instance. When
         * {@link VirtualDevice#close()} is called, the associated {@link VirtualAudioDevice} will
         * also be closed automatically.
         *
         * @param display The target virtual display to capture from and inject into.
         * @param executor The {@link Executor} object for the thread on which to execute
         *   the callback. If <code>null</code>, the {@link Executor} associated with the main
         *   {@link Looper} will be used.
         * @param callback Interface to be notified when playback or recording configuration of
         *   applications running on virtual display is changed.
         * @return A {@link VirtualAudioDevice} instance.
         */
        @NonNull
        public VirtualAudioDevice createVirtualAudioDevice(
                @NonNull VirtualDisplay display,
                @Nullable Executor executor,
                @Nullable AudioConfigurationChangeCallback callback) {
            Objects.requireNonNull(display, "display must not be null");
            return mVirtualDeviceInternal.createVirtualAudioDevice(display, executor, callback);
        }

        /**
         * Creates a new virtual camera with the given {@link VirtualCameraConfig}.
         *
         * <p>A virtual device with {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} for its
         * {@link VirtualDeviceParams#POLICY_TYPE_CAMERA} can create virtual cameras
         * with any {@link CameraCharacteristics#LENS_FACING}, though at most one of each
         * {@link CameraMetadata#LENS_FACING_FRONT} and {@link CameraMetadata#LENS_FACING_BACK}.
         * Multiple {@link CameraMetadata#LENS_FACING_EXTERNAL} virtual cameras are allowed.
         * The virtual cameras (including the external ones) are guarded by a separate
         * {@link Manifest.permission#CAMERA} permission relevant only to the virtual device.
         *
         * <p>A virtual device with {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} for its
         * {@link VirtualDeviceParams#POLICY_TYPE_CAMERA} can create <b>only</b> virtual cameras
         * with {@link CameraMetadata#LENS_FACING_EXTERNAL}. The created virtual external cameras
         * are visible from {@link CameraManager} created with a default device context.
         * {@link Context#DEVICE_ID_DEFAULT}.
         * In this case the virtual external cameras are guarded by the default's device
         * {@link Manifest.permission#CAMERA} permission.
         *
         * @param config camera configuration.
         * @return newly created camera.
         * @throws UnsupportedOperationException if virtual camera isn't supported on this device.
         * @see VirtualDeviceParams#POLICY_TYPE_CAMERA
         */
        @NonNull
        public VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) {
            return mVirtualDeviceInternal.createVirtualCamera(Objects.requireNonNull(config));
        }

        /**
         * Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
         *
         * <p>Only applicable to trusted displays.</p>
         *
         * @param showPointerIcon True if the pointer should be shown; false otherwise. The default
         *   visibility is true.
         * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
         */
        public void setShowPointerIcon(boolean showPointerIcon) {
            mVirtualDeviceInternal.setShowPointerIcon(showPointerIcon);
        }

        /**
         * Specifies the IME behavior on the given display. By default, all displays created by
         * virtual devices have {@link WindowManager#DISPLAY_IME_POLICY_LOCAL}.
         *
         * @param displayId the ID of the display to change the IME policy for. It must be owned by
         *                  this virtual device.
         * @param policy the IME policy to use on that display
         * @throws SecurityException if the display is not owned by this device or is not
         *                           {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted}
         */
        public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
            mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
        }

        /**
         * Specifies the UI mode on the given display.
         *
         * <p>By default, all displays created by virtual devices have
         * {@link Configuration#UI_MODE_TYPE_UNDEFINED} and
         * {@link Configuration#UI_MODE_NIGHT_UNDEFINED}, meaning that they follow the global UI
         * mode type and night mode. These constants can also be used to unset a previously set
         * UI mode.</p>
         *
         * @param displayId the ID of the display to change the UI mode for. It must be a trusted
         *   non-mirror display, owned by this virtual device.
         * @param uiMode the UI mode to use on that display, a combination of the UI mode type
         *   given by the {@link Configuration#UI_MODE_TYPE_MASK} bits, and the night mode given by
         *   the {@link Configuration#UI_MODE_NIGHT_MASK} bits.
         * @throws SecurityException if the display is not owned by this device, is not
         *   {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted}, or is a
         *   {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR mirror} display.
         * @see Configuration#uiMode
         */
        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_UI_MODE)
        public void setDisplayUiMode(int displayId, @DisplayUiMode int uiMode) {
            if (!Flags.deviceAwareUiMode()) {
                throw new UnsupportedOperationException("Required flag is not enabled");
            }
            mVirtualDeviceInternal.setDisplayUiMode(displayId, uiMode);
        }

        /**
         * Adds an activity listener to listen for events such as top activity change or virtual
         * display task stack became empty.
         *
         * @param executor The executor where the listener is executed on.
         * @param listener The listener to add.
         * @see #removeActivityListener(ActivityListener)
         */
        public void addActivityListener(
                @CallbackExecutor @NonNull Executor executor, @NonNull ActivityListener listener) {
            mVirtualDeviceInternal.addActivityListener(executor, listener);
        }

        /**
         * Removes an activity listener previously added with {@link #addActivityListener}.
         *
         * @param listener The listener to remove.
         * @see #addActivityListener(Executor, ActivityListener)
         */
        public void removeActivityListener(@NonNull ActivityListener listener) {
            mVirtualDeviceInternal.removeActivityListener(listener);
        }

        /**
         * Adds a sound effect listener.
         *
         * @param executor The executor where the listener is executed on.
         * @param soundEffectListener The listener to add.
         * @see #removeSoundEffectListener(SoundEffectListener)
         */
        public void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor,
                @NonNull SoundEffectListener soundEffectListener) {
            mVirtualDeviceInternal.addSoundEffectListener(executor, soundEffectListener);
        }

        /**
         * Removes a sound effect listener previously added with {@link #addSoundEffectListener}.
         *
         * @param soundEffectListener The listener to remove.
         * @see #addSoundEffectListener(Executor, SoundEffectListener)
         */
        public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) {
            mVirtualDeviceInternal.removeSoundEffectListener(soundEffectListener);
        }

        /**
         * Registers an intent interceptor that will intercept an intent attempting to launch
         * when matching the provided IntentFilter and calls the callback with the intercepted
         * intent.
         *
         * @param interceptorFilter The filter to match intents intended for interception.
         * @param executor The executor where the interceptor is executed on.
         * @param interceptorCallback The callback called when an intent matching interceptorFilter
         * is intercepted.
         * @see #unregisterIntentInterceptor(IntentInterceptorCallback)
         */
        public void registerIntentInterceptor(
                @NonNull IntentFilter interceptorFilter,
                @CallbackExecutor @NonNull Executor executor,
                @NonNull IntentInterceptorCallback interceptorCallback) {
            mVirtualDeviceInternal.registerIntentInterceptor(
                    interceptorFilter, executor, interceptorCallback);
        }

        /**
         * Unregisters the intent interceptor previously registered with
         * {@link #registerIntentInterceptor}.
         */
        public void unregisterIntentInterceptor(
                    @NonNull IntentInterceptorCallback interceptorCallback) {
            mVirtualDeviceInternal.unregisterIntentInterceptor(interceptorCallback);
        }
    }

    /**
     * Listener for activity changes and other activity events on a virtual device.
     *
     * @hide
     */
    @SystemApi
    public interface ActivityListener {

        /**
         * Called when the top activity is changed.
         *
         * <p>Note: When there are no activities running on the virtual display, the
         * {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
         * should be cleared when {@link #onDisplayEmpty(int)} is called.
         *
         * @param displayId The display ID on which the activity change happened.
         * @param topActivity The component name of the top activity.
         * @deprecated Use {@link #onTopActivityChanged(int, ComponentName, int)} instead
         */
        void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity);

        /**
         * Called when the top activity is changed.
         *
         * <p>Note: When there are no activities running on the virtual display, the
         * {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
         * should be cleared when {@link #onDisplayEmpty(int)} is called.
         *
         * @param displayId The display ID on which the activity change happened.
         * @param topActivity The component name of the top activity.
         * @param userId The user ID associated with the top activity.
         */
        default void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
                @UserIdInt int userId) {}

        /**
         * Called when the display becomes empty (e.g. if the user hits back on the last
         * activity of the root task).
         *
         * @param displayId The display ID that became empty.
         */
        void onDisplayEmpty(int displayId);

        /**
         * Called when an activity launch was blocked due to a policy violation.
         *
         * @param displayId The display ID on which the activity tried to launch.
         * @param componentName The component name of the blocked activity.
         * @param user The user associated with the blocked activity.
         * @param intentSender The original sender of the intent. May be {@code null} if the sender
         *   expects an activity result to be reported. In that case
         *   {@link android.app.Activity#RESULT_CANCELED} was already reported back because the
         *   launch was blocked. This {@link IntentSender} can be used to relaunch the blocked
         *   activity to a different display.
         *
         * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
         * @see VirtualDevice#addActivityPolicyExemption(ActivityPolicyExemption)
         */
        @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
        default void onActivityLaunchBlocked(int displayId, @NonNull ComponentName componentName,
                @NonNull UserHandle user, @Nullable IntentSender intentSender) {}

        /**
         * Called when a window with a secure surface is shown on the device.
         *
         * <p>Note that this is called only when the window is associated with an activity.</p>
         *
         * @param displayId The display ID on which the window was shown.
         * @param componentName The component name of the activity that showed the window.
         * @param user The user associated with the activity.
         *
         * @see Display#FLAG_SECURE
         * @see WindowManager.LayoutParams#FLAG_SECURE
         */
        @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
        default void onSecureWindowShown(int displayId, @NonNull ComponentName componentName,
                @NonNull UserHandle user) {}

        /**
         * Called when there is no longer any window with a secure surface shown on the device.
         *
         * <p>This is only called once there are no more secure windows shown on the device. If
         * there are multiple secure windows shown on the device, this callback will be called only
         * once all of them are hidden.</p>
         *
         * @param displayId The display ID on which the window was shown before.
         *
         * @see Display#FLAG_SECURE
         * @see WindowManager.LayoutParams#FLAG_SECURE
         */
        @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
        default void onSecureWindowHidden(int displayId) {}
    }

    /**
     * Interceptor interface to be called when an intent matches the IntentFilter passed into {@link
     * VirtualDevice#registerIntentInterceptor}. When the interceptor is called after matching the
     * IntentFilter, the intended activity launch will be aborted and alternatively replaced by
     * the interceptor's receiver.
     *
     * @hide
     */
    @SystemApi
    public interface IntentInterceptorCallback {

        /**
         * Called when an intent that matches the IntentFilter registered in {@link
         * VirtualDevice#registerIntentInterceptor} is intercepted for the virtual device to
         * handle.
         *
         * @param intent The intent that has been intercepted by the interceptor.
         */
        void onIntentIntercepted(@NonNull Intent intent);
    }

    /**
     * Listener for system sound effect playback on virtual device.
     *
     * @hide
     */
    @SystemApi
    public interface SoundEffectListener {

        /**
         * Called when there's a system sound effect to be played on virtual device.
         *
         * @param effectType - system sound effect type
         * @see android.media.AudioManager.SystemSoundEffect
         */
        void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType);
    }

    /**
     * Listener for changes in the available virtual devices.
     *
     * @see #registerVirtualDeviceListener
     */
    public interface VirtualDeviceListener {
        /**
         * Called whenever a new virtual device has been added to the system.
         * Use {@link VirtualDeviceManager#getVirtualDevice(int)} to get more information about
         * the device.
         *
         * @param deviceId The id of the virtual device that was added.
         */
        default void onVirtualDeviceCreated(int deviceId) {}

        /**
         * Called whenever a virtual device has been removed from the system.
         *
         * @param deviceId The id of the virtual device that was removed.
         */
        default void onVirtualDeviceClosed(int deviceId) {}
    }

    /**
     * A wrapper for {@link VirtualDeviceListener} that executes callbacks on the given executor.
     */
    private static class VirtualDeviceListenerDelegate extends IVirtualDeviceListener.Stub {
        private final VirtualDeviceListener mListener;
        private final Executor mExecutor;

        private VirtualDeviceListenerDelegate(Executor executor, VirtualDeviceListener listener) {
            mExecutor = executor;
            mListener = listener;
        }

        @Override
        public void onVirtualDeviceCreated(int deviceId) {
            final long token = Binder.clearCallingIdentity();
            try {
                mExecutor.execute(() -> mListener.onVirtualDeviceCreated(deviceId));
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

        @Override
        public void onVirtualDeviceClosed(int deviceId) {
            final long token = Binder.clearCallingIdentity();
            try {
                mExecutor.execute(() -> mListener.onVirtualDeviceClosed(deviceId));
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }

    /**
     * A wrapper for {@link AutomatedPackageListener} that executes callbacks on the given executor.
     */
    private static class AutomatedPackageListenerDelegate extends IAutomatedPackageListener.Stub {
        private final AutomatedPackageListener mListener;
        private final Executor mExecutor;

        private AutomatedPackageListenerDelegate(
                Executor executor, AutomatedPackageListener listener) {
            mExecutor = executor;
            mListener = listener;
        }

        @Override
        public void onAutomatedPackagesChanged(
                @NonNull String automatingPackage,
                @NonNull List<String> automatedPackages,
                @NonNull UserHandle user) {
            Binder.withCleanCallingIdentity(() ->
                    mExecutor.execute(() -> mListener.onAutomatedPackagesChanged(
                            automatingPackage, automatedPackages, user)));
        }
    }
}
