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

Commit b151cfde authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Move implementation details out of VDM" into udc-dev

parents d01cc57a eda545b0
Loading
Loading
Loading
Loading
+458 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.sensor.VirtualSensor;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IVirtualDisplayCallback;
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.VirtualTouchscreen;
import android.hardware.input.VirtualTouchscreenConfig;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.ArrayMap;

import com.android.internal.annotations.GuardedBy;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;

/**
 * An internal representation of a virtual device.
 *
 * @hide
 */
public class VirtualDeviceInternal {

    private final Context mContext;
    private final IVirtualDeviceManager mService;
    private final IVirtualDevice mVirtualDevice;
    private final Object mActivityListenersLock = new Object();
    @GuardedBy("mActivityListenersLock")
    private final ArrayMap<VirtualDeviceManager.ActivityListener, ActivityListenerDelegate>
            mActivityListeners =
            new ArrayMap<>();
    private final Object mIntentInterceptorListenersLock = new Object();
    @GuardedBy("mIntentInterceptorListenersLock")
    private final ArrayMap<VirtualDeviceManager.IntentInterceptorCallback,
            IntentInterceptorDelegate> mIntentInterceptorListeners =
            new ArrayMap<>();
    private final Object mSoundEffectListenersLock = new Object();
    @GuardedBy("mSoundEffectListenersLock")
    private final ArrayMap<VirtualDeviceManager.SoundEffectListener, SoundEffectListenerDelegate>
            mSoundEffectListeners = new ArrayMap<>();
    private final IVirtualDeviceActivityListener mActivityListenerBinder =
            new IVirtualDeviceActivityListener.Stub() {

                @Override
                public void onTopActivityChanged(int displayId, ComponentName topActivity,
                        @UserIdInt int userId) {
                    final long token = Binder.clearCallingIdentity();
                    try {
                        synchronized (mActivityListenersLock) {
                            for (int i = 0; i < mActivityListeners.size(); i++) {
                                mActivityListeners.valueAt(i)
                                        .onTopActivityChanged(displayId, topActivity);
                                mActivityListeners.valueAt(i)
                                        .onTopActivityChanged(displayId, topActivity, userId);
                            }
                        }
                    } finally {
                        Binder.restoreCallingIdentity(token);
                    }
                }

                @Override
                public void onDisplayEmpty(int displayId) {
                    final long token = Binder.clearCallingIdentity();
                    try {
                        synchronized (mActivityListenersLock) {
                            for (int i = 0; i < mActivityListeners.size(); i++) {
                                mActivityListeners.valueAt(i).onDisplayEmpty(displayId);
                            }
                        }
                    } finally {
                        Binder.restoreCallingIdentity(token);
                    }
                }
            };
    private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
            new IVirtualDeviceSoundEffectListener.Stub() {
                @Override
                public void onPlaySoundEffect(int soundEffect) {
                    final long token = Binder.clearCallingIdentity();
                    try {
                        synchronized (mSoundEffectListenersLock) {
                            for (int i = 0; i < mSoundEffectListeners.size(); i++) {
                                mSoundEffectListeners.valueAt(i).onPlaySoundEffect(soundEffect);
                            }
                        }
                    } finally {
                        Binder.restoreCallingIdentity(token);
                    }
                }
            };
    @Nullable
    private VirtualAudioDevice mVirtualAudioDevice;

    VirtualDeviceInternal(
            IVirtualDeviceManager service,
            Context context,
            int associationId,
            VirtualDeviceParams params) throws RemoteException {
        mService = service;
        mContext = context.getApplicationContext();
        mVirtualDevice = service.createVirtualDevice(
                new Binder(),
                mContext.getPackageName(),
                associationId,
                params,
                mActivityListenerBinder,
                mSoundEffectListener);
    }

    int getDeviceId() {
        try {
            return mVirtualDevice.getDeviceId();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @NonNull Context createContext() {
        try {
            return mContext.createDeviceContext(mVirtualDevice.getDeviceId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @NonNull
    List<VirtualSensor> getVirtualSensorList() {
        try {
            return mVirtualDevice.getVirtualSensorList();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    void launchPendingIntent(
            int displayId,
            @NonNull PendingIntent pendingIntent,
            @NonNull Executor executor,
            @NonNull IntConsumer listener) {
        try {
            mVirtualDevice.launchPendingIntent(
                    displayId,
                    pendingIntent,
                    new ResultReceiver(new Handler(Looper.getMainLooper())) {
                        @Override
                        protected void onReceiveResult(int resultCode, Bundle resultData) {
                            super.onReceiveResult(resultCode, resultData);
                            executor.execute(() -> listener.accept(resultCode));
                        }
                    });
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    @Nullable
    VirtualDisplay createVirtualDisplay(
            @NonNull VirtualDisplayConfig config,
            @Nullable @CallbackExecutor Executor executor,
            @Nullable VirtualDisplay.Callback callback) {
        IVirtualDisplayCallback callbackWrapper =
                new DisplayManagerGlobal.VirtualDisplayCallback(callback, executor);
        final int displayId;
        try {
            displayId = mService.createVirtualDisplay(config, callbackWrapper, mVirtualDevice,
                    mContext.getPackageName());
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
        return displayManager.createVirtualDisplayWrapper(config, callbackWrapper,
                displayId);
    }

    void close() {
        try {
            // This also takes care of unregistering all virtual sensors.
            mVirtualDevice.close();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        if (mVirtualAudioDevice != null) {
            mVirtualAudioDevice.close();
            mVirtualAudioDevice = null;
        }
    }

    @NonNull
    VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
        try {
            final IBinder token = new Binder(
                    "android.hardware.input.VirtualDpad:" + config.getInputDeviceName());
            mVirtualDevice.createVirtualDpad(config, token);
            return new VirtualDpad(mVirtualDevice, token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @NonNull
    VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
        try {
            final IBinder token = new Binder(
                    "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
            mVirtualDevice.createVirtualKeyboard(config, token);
            return new VirtualKeyboard(mVirtualDevice, token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @NonNull
    VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
        try {
            final IBinder token = new Binder(
                    "android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
            mVirtualDevice.createVirtualMouse(config, token);
            return new VirtualMouse(mVirtualDevice, token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @NonNull
    VirtualTouchscreen createVirtualTouchscreen(
            @NonNull VirtualTouchscreenConfig config) {
        try {
            final IBinder token = new Binder(
                    "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
            mVirtualDevice.createVirtualTouchscreen(config, token);
            return new VirtualTouchscreen(mVirtualDevice, token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @NonNull
    VirtualNavigationTouchpad createVirtualNavigationTouchpad(
            @NonNull VirtualNavigationTouchpadConfig config) {
        try {
            final IBinder token = new Binder(
                    "android.hardware.input.VirtualNavigationTouchpad:"
                            + config.getInputDeviceName());
            mVirtualDevice.createVirtualNavigationTouchpad(config, token);
            return new VirtualNavigationTouchpad(mVirtualDevice, token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @NonNull
    VirtualAudioDevice createVirtualAudioDevice(
            @NonNull VirtualDisplay display,
            @Nullable Executor executor,
            @Nullable VirtualAudioDevice.AudioConfigurationChangeCallback callback) {
        if (mVirtualAudioDevice == null) {
            mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display,
                    executor, callback, () -> mVirtualAudioDevice = null);
        }
        return mVirtualAudioDevice;
    }

    @NonNull
    void setShowPointerIcon(boolean showPointerIcon) {
        try {
            mVirtualDevice.setShowPointerIcon(showPointerIcon);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    void addActivityListener(
            @CallbackExecutor @NonNull Executor executor,
            @NonNull VirtualDeviceManager.ActivityListener listener) {
        final ActivityListenerDelegate delegate = new ActivityListenerDelegate(
                Objects.requireNonNull(listener), Objects.requireNonNull(executor));
        synchronized (mActivityListenersLock) {
            mActivityListeners.put(listener, delegate);
        }
    }

    void removeActivityListener(@NonNull VirtualDeviceManager.ActivityListener listener) {
        synchronized (mActivityListenersLock) {
            mActivityListeners.remove(Objects.requireNonNull(listener));
        }
    }

    void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor,
            @NonNull VirtualDeviceManager.SoundEffectListener soundEffectListener) {
        final SoundEffectListenerDelegate delegate =
                new SoundEffectListenerDelegate(Objects.requireNonNull(executor),
                        Objects.requireNonNull(soundEffectListener));
        synchronized (mSoundEffectListenersLock) {
            mSoundEffectListeners.put(soundEffectListener, delegate);
        }
    }

    void removeSoundEffectListener(
            @NonNull VirtualDeviceManager.SoundEffectListener soundEffectListener) {
        synchronized (mSoundEffectListenersLock) {
            mSoundEffectListeners.remove(Objects.requireNonNull(soundEffectListener));
        }
    }

    void registerIntentInterceptor(
            @NonNull IntentFilter interceptorFilter,
            @CallbackExecutor @NonNull Executor executor,
            @NonNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(interceptorFilter);
        Objects.requireNonNull(interceptorCallback);
        final IntentInterceptorDelegate delegate =
                new IntentInterceptorDelegate(executor, interceptorCallback);
        try {
            mVirtualDevice.registerIntentInterceptor(delegate, interceptorFilter);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        synchronized (mIntentInterceptorListenersLock) {
            mIntentInterceptorListeners.put(interceptorCallback, delegate);
        }
    }

    void unregisterIntentInterceptor(
            @NonNull VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) {
        Objects.requireNonNull(interceptorCallback);
        final IntentInterceptorDelegate delegate;
        synchronized (mIntentInterceptorListenersLock) {
            delegate = mIntentInterceptorListeners.remove(interceptorCallback);
        }
        if (delegate != null) {
            try {
                mVirtualDevice.unregisterIntentInterceptor(delegate);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * A wrapper for {@link VirtualDeviceManager.ActivityListener} that executes callbacks on the
     * given executor.
     */
    private static class ActivityListenerDelegate {
        @NonNull private final VirtualDeviceManager.ActivityListener mActivityListener;
        @NonNull private final Executor mExecutor;

        ActivityListenerDelegate(@NonNull VirtualDeviceManager.ActivityListener listener,
                @NonNull Executor executor) {
            mActivityListener = listener;
            mExecutor = executor;
        }

        public void onTopActivityChanged(int displayId, ComponentName topActivity) {
            mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity));
        }

        public void onTopActivityChanged(int displayId, ComponentName topActivity,
                @UserIdInt int userId) {
            mExecutor.execute(() ->
                    mActivityListener.onTopActivityChanged(displayId, topActivity, userId));
        }

        public void onDisplayEmpty(int displayId) {
            mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId));
        }
    }

    /**
     * A wrapper for {@link VirtualDeviceManager.IntentInterceptorCallback} that executes callbacks
     * on the given executor.
     */
    private static class IntentInterceptorDelegate extends IVirtualDeviceIntentInterceptor.Stub {
        @NonNull private final VirtualDeviceManager.IntentInterceptorCallback
                mIntentInterceptorCallback;
        @NonNull private final Executor mExecutor;

        private IntentInterceptorDelegate(Executor executor,
                VirtualDeviceManager.IntentInterceptorCallback interceptorCallback) {
            mExecutor = executor;
            mIntentInterceptorCallback = interceptorCallback;
        }

        @Override
        public void onIntentIntercepted(Intent intent) {
            final long token = Binder.clearCallingIdentity();
            try {
                mExecutor.execute(() -> mIntentInterceptorCallback.onIntentIntercepted(intent));
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }

    /**
     * A wrapper for {@link VirtualDeviceManager.SoundEffectListener} that executes callbacks on the
     * given executor.
     */
    private static class SoundEffectListenerDelegate {
        @NonNull private final VirtualDeviceManager.SoundEffectListener mSoundEffectListener;
        @NonNull private final Executor mExecutor;

        private SoundEffectListenerDelegate(Executor executor,
                VirtualDeviceManager.SoundEffectListener soundEffectCallback) {
            mSoundEffectListener = soundEffectCallback;
            mExecutor = executor;
        }

        public void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType) {
            mExecutor.execute(() -> mSoundEffectListener.onPlaySoundEffect(effectType));
        }
    }
}
+44 −324

File changed.

Preview size limit exceeded, changes collapsed.