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

Commit 30ea4280 authored by Vladimir Komsiyski's avatar Vladimir Komsiyski
Browse files

Virtual sensors lifecycle management.

Add a SensorController between virtual devices and the JNI layer of the sensor service.

The virtual sensors are registered with the sensor framework as runtime sensors when the virtual device is created, and unregistered when the device is closed.

Bug: 237278244
Test: atest SensorControllerTest
Test: atest VirtualDeviceManagerServiceTest
Change-Id: I474b64381ccaf6fd6b0055f7344ebe6d8d5d6e3f
parent ca930822
Loading
Loading
Loading
Loading
+235 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.companion.virtual;

import android.annotation.NonNull;
import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.sensors.SensorManagerInternal;

import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
public class SensorController {

    private static final String TAG = "SensorController";

    private final Object mLock;
    private final int mVirtualDeviceId;
    @GuardedBy("mLock")
    private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();

    private final SensorManagerInternal mSensorManagerInternal;

    public SensorController(@NonNull Object lock, int virtualDeviceId) {
        mLock = lock;
        mVirtualDeviceId = virtualDeviceId;
        mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
    }

    void close() {
        synchronized (mLock) {
            final Iterator<Map.Entry<IBinder, SensorDescriptor>> iterator =
                    mSensorDescriptors.entrySet().iterator();
            if (iterator.hasNext()) {
                final Map.Entry<IBinder, SensorDescriptor> entry = iterator.next();
                final IBinder token = entry.getKey();
                final SensorDescriptor sensorDescriptor = entry.getValue();
                iterator.remove();
                closeSensorDescriptorLocked(token, sensorDescriptor);
            }
        }
    }

    void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) {
        Objects.requireNonNull(deviceToken);
        Objects.requireNonNull(config);
        try {
            createSensorInternal(deviceToken, config);
        } catch (SensorCreationException e) {
            throw new RuntimeException(
                    "Failed to create virtual sensor '" + config.getName() + "'.", e);
        }
    }

    private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config)
            throws SensorCreationException {
        final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback =
                (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> {
                    IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback();
                    if (callback != null) {
                        try {
                            callback.onStateChanged(
                                    enabled, samplingPeriodMicros, batchReportLatencyMicros);
                        } catch (RemoteException e) {
                            throw new RuntimeException("Failed to call sensor callback.", e);
                        }
                    }
                };

        final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
                config.getType(), config.getName(),
                config.getVendor() == null ? "" : config.getVendor(),
                runtimeSensorCallback);
        if (handle <= 0) {
            throw new SensorCreationException("Received an invalid virtual sensor handle.");
        }

        // The handle is valid from here, so ensure that all failures clean it up.
        final BinderDeathRecipient binderDeathRecipient;
        try {
            binderDeathRecipient = new BinderDeathRecipient(deviceToken);
            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
        } catch (RemoteException e) {
            mSensorManagerInternal.removeRuntimeSensor(handle);
            throw new SensorCreationException("Client died before sensor could be created.", e);
        }

        synchronized (mLock) {
            SensorDescriptor sensorDescriptor = new SensorDescriptor(
                    handle, config.getType(), config.getName(), binderDeathRecipient);
            mSensorDescriptors.put(deviceToken, sensorDescriptor);
        }
    }

    boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
        Objects.requireNonNull(token);
        Objects.requireNonNull(event);
        synchronized (mLock) {
            final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token);
            if (sensorDescriptor == null) {
                throw new IllegalArgumentException("Could not send sensor event for given token");
            }
            return mSensorManagerInternal.sendSensorEvent(
                    sensorDescriptor.getHandle(), sensorDescriptor.getType(),
                    event.getTimestampNanos(), event.getValues());
        }
    }

    void unregisterSensor(@NonNull IBinder token) {
        Objects.requireNonNull(token);
        synchronized (mLock) {
            final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
            if (sensorDescriptor == null) {
                throw new IllegalArgumentException("Could not unregister sensor for given token");
            }
            closeSensorDescriptorLocked(token, sensorDescriptor);
        }
    }

    @GuardedBy("mLock")
    private void closeSensorDescriptorLocked(IBinder token, SensorDescriptor sensorDescriptor) {
        token.unlinkToDeath(sensorDescriptor.getDeathRecipient(), /* flags= */ 0);
        final int handle = sensorDescriptor.getHandle();
        mSensorManagerInternal.removeRuntimeSensor(handle);
    }


    void dump(@NonNull PrintWriter fout) {
        fout.println("    SensorController: ");
        synchronized (mLock) {
            fout.println("      Active descriptors: ");
            for (SensorDescriptor sensorDescriptor : mSensorDescriptors.values()) {
                fout.println("        handle: " + sensorDescriptor.getHandle());
                fout.println("          type: " + sensorDescriptor.getType());
                fout.println("          name: " + sensorDescriptor.getName());
            }
        }
    }

    @VisibleForTesting
    void addSensorForTesting(IBinder deviceToken, int handle, int type, String name) {
        synchronized (mLock) {
            mSensorDescriptors.put(deviceToken,
                    new SensorDescriptor(handle, type, name, () -> {}));
        }
    }

    @VisibleForTesting
    Map<IBinder, SensorDescriptor> getSensorDescriptors() {
        synchronized (mLock) {
            return mSensorDescriptors;
        }
    }

    @VisibleForTesting
    static final class SensorDescriptor {

        private final int mHandle;
        private final IBinder.DeathRecipient mDeathRecipient;
        private final int mType;
        private final String mName;

        SensorDescriptor(int handle, int type, String name, IBinder.DeathRecipient deathRecipient) {
            mHandle = handle;
            mDeathRecipient = deathRecipient;
            mType = type;
            mName = name;
        }
        public int getHandle() {
            return mHandle;
        }
        public int getType() {
            return mType;
        }
        public String getName() {
            return mName;
        }
        public IBinder.DeathRecipient getDeathRecipient() {
            return mDeathRecipient;
        }
    }

    private final class BinderDeathRecipient implements IBinder.DeathRecipient {
        private final IBinder mDeviceToken;

        BinderDeathRecipient(IBinder deviceToken) {
            mDeviceToken = deviceToken;
        }

        @Override
        public void binderDied() {
            // All callers are expected to call {@link VirtualDevice#unregisterSensor} before
            // quitting, which removes this death recipient. If this is invoked, the remote end
            // died, or they disposed of the object without properly unregistering.
            Slog.e(TAG, "Virtual sensor controller binder died");
            unregisterSensor(mDeviceToken);
        }
    }

    /** An internal exception that is thrown to indicate an error when opening a virtual sensor. */
    private static class SensorCreationException extends Exception {
        SensorCreationException(String message) {
            super(message);
        }
        SensorCreationException(String message, Exception cause) {
            super(message, cause);
        }
    }
}
+72 −33
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ import com.android.server.companion.virtual.audio.VirtualAudioController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;

@@ -99,6 +100,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
    private final int mOwnerUid;
    private final int mDeviceId;
    private final InputController mInputController;
    private final SensorController mSensorController;
    private VirtualAudioController mVirtualAudioController;
    @VisibleForTesting
    final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -161,6 +163,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                ownerUid,
                deviceId,
                /* inputController= */ null,
                /* sensorController= */ null,
                listener,
                pendingTrampolineCallback,
                activityListener,
@@ -176,6 +179,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
            int ownerUid,
            int deviceId,
            InputController inputController,
            SensorController sensorController,
            OnDeviceCloseListener listener,
            PendingTrampolineCallback pendingTrampolineCallback,
            IVirtualDeviceActivityListener activityListener,
@@ -199,6 +203,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        } else {
            mInputController = inputController;
        }
        if (sensorController == null) {
            mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId);
        } else {
            mSensorController = sensorController;
        }
        mListener = listener;
        try {
            token.linkToDeath(this, 0);
@@ -320,11 +329,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        mListener.onClose(mAssociationInfo.getId());
        mAppToken.unlinkToDeath(this, 0);

        final long token = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            mInputController.close();
            mSensorController.close();
        } finally {
            Binder.restoreCallingIdentity(token);
            Binder.restoreCallingIdentity(ident);
        }
    }

@@ -404,12 +414,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                                + "this virtual device");
            }
        }
        final long token = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
                    displayId);
        } finally {
            Binder.restoreCallingIdentity(token);
            Binder.restoreCallingIdentity(ident);
        }
    }

@@ -431,12 +441,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                                + "this virtual device");
            }
        }
        final long token = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
                    displayId);
        } finally {
            Binder.restoreCallingIdentity(token);
            Binder.restoreCallingIdentity(ident);
        }
    }

@@ -458,11 +468,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                                + "virtual device");
            }
        }
        final long token = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
        } finally {
            Binder.restoreCallingIdentity(token);
            Binder.restoreCallingIdentity(ident);
        }
    }

@@ -492,12 +502,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                            + screenSize);
        }

        final long token = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            mInputController.createTouchscreen(deviceName, vendorId, productId,
                    deviceToken, displayId, screenSize);
        } finally {
            Binder.restoreCallingIdentity(token);
            Binder.restoreCallingIdentity(ident);
        }
    }

@@ -507,92 +517,92 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                "Permission required to unregister this input device");

        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            mInputController.unregisterInputDevice(token);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public int getInputDeviceId(IBinder token) {
        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            return mInputController.getInputDeviceId(token);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }


    @Override // Binder call
    public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            return mInputController.sendDpadKeyEvent(token, event);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            return mInputController.sendKeyEvent(token, event);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            return mInputController.sendButtonEvent(token, event);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            return mInputController.sendTouchEvent(token, event);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            return mInputController.sendRelativeEvent(token, event);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            return mInputController.sendScrollEvent(token, event);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public PointF getCursorPosition(IBinder token) {
        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            return mInputController.getCursorPosition(token);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

@@ -602,7 +612,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                "Permission required to unregister this input device");

        final long binderToken = Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mVirtualDeviceLock) {
                mDefaultShowPointerIcon = showPointerIcon;
@@ -611,7 +621,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                }
            }
        } finally {
            Binder.restoreCallingIdentity(binderToken);
            Binder.restoreCallingIdentity(ident);
        }
    }

@@ -619,15 +629,43 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
    public void createVirtualSensor(
            @NonNull IBinder deviceToken,
            @NonNull VirtualSensorConfig config) {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                "Permission required to create a virtual sensor");
        Objects.requireNonNull(config);
        Objects.requireNonNull(deviceToken);
        final long ident = Binder.clearCallingIdentity();
        try {
            mSensorController.createSensor(deviceToken, config);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public void unregisterSensor(IBinder token) {
    public void unregisterSensor(@NonNull IBinder token) {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                "Permission required to unregister a virtual sensor");
        final long ident = Binder.clearCallingIdentity();
        try {
            mSensorController.unregisterSensor(token);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override // Binder call
    public boolean sendSensorEvent(IBinder token, VirtualSensorEvent event) {
        return true;
    public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                "Permission required to send a virtual sensor event");
        final long ident = Binder.clearCallingIdentity();
        try {
            return mSensorController.sendSensorEvent(token, event);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    @Override
@@ -643,6 +681,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
            fout.println("    mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
        }
        mInputController.dump(fout);
        mSensorController.dump(fout);
    }

    GenericWindowPolicyController createWindowPolicyController() {
+50 −0
Original line number Diff line number Diff line
@@ -42,6 +42,43 @@ public abstract class SensorManagerInternal {
     */
    public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener);

    /**
     * Creates a sensor that is registered at runtime by the system with the sensor service.
     *
     * The runtime sensors created here are different from the
     * <a href="https://source.android.com/docs/core/interaction/sensors/sensors-hal2#dynamic-sensors">
     * dynamic sensor support in the HAL</a>. These sensors have no HAL dependency and correspond to
     * sensors that belong to an external (virtual) device.
     *
     * @param deviceId The identifier of the device this sensor is associated with.
     * @param type The generic type of the sensor.
     * @param name The name of the sensor.
     * @param vendor The vendor string of the sensor.
     * @param callback The callback to get notified when the sensor listeners have changed.
     * @return The sensor handle.
     */
    public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
            @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback);

    /**
     * Unregisters the sensor with the given handle from the framework.
     */
    public abstract void removeRuntimeSensor(int handle);

    /**
     * Sends an event for the runtime sensor with the given handle to the framework.
     *
     * Only relevant for sending runtime sensor events. @see #createRuntimeSensor.
     *
     * @param handle The sensor handle.
     * @param type The type of the sensor.
     * @param timestampNanos When the event occurred.
     * @param values The values of the event.
     * @return Whether the event injection was successful.
     */
    public abstract boolean sendSensorEvent(int handle, int type, long timestampNanos,
            @NonNull float[] values);

    /**
     * Listener for proximity sensor state changes.
     */
@@ -52,4 +89,17 @@ public abstract class SensorManagerInternal {
         */
        void onProximityActive(boolean isActive);
    }

    /**
     * Callback for runtime sensor state changes. Only relevant to sensors created via
     * {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are
     * not covered.
     */
    public interface RuntimeSensorStateChangeCallback {
        /**
         * Invoked when the listeners of the runtime sensor have changed.
         */
        void onStateChanged(boolean enabled, int samplingPeriodMicros,
                int batchReportLatencyMicros);
    }
}
+42 −0
Original line number Diff line number Diff line
@@ -29,7 +29,9 @@ import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.utils.TimingsTraceAndSlog;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;

@@ -40,6 +42,8 @@ public class SensorService extends SystemService {
    private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners =
            new ArrayMap<>();
    @GuardedBy("mLock")
    private final Set<Integer> mRuntimeSensorHandles = new HashSet<>();
    @GuardedBy("mLock")
    private Future<?> mSensorServiceStart;
    @GuardedBy("mLock")
    private long mPtr;
@@ -51,6 +55,12 @@ public class SensorService extends SystemService {
    private static native void registerProximityActiveListenerNative(long ptr);
    private static native void unregisterProximityActiveListenerNative(long ptr);

    private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
            String name, String vendor,
            SensorManagerInternal.RuntimeSensorStateChangeCallback callback);
    private static native void unregisterRuntimeSensorNative(long ptr, int handle);
    private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
            long timestampNanos, float[] values);

    public SensorService(Context ctx) {
        super(ctx);
@@ -84,6 +94,38 @@ public class SensorService extends SystemService {
    }

    class LocalService extends SensorManagerInternal {
        @Override
        public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
                @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) {
            synchronized (mLock) {
                int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
                        callback);
                mRuntimeSensorHandles.add(handle);
                return handle;
            }
        }

        @Override
        public void removeRuntimeSensor(int handle) {
            synchronized (mLock) {
                if (mRuntimeSensorHandles.contains(handle)) {
                    mRuntimeSensorHandles.remove(handle);
                    unregisterRuntimeSensorNative(mPtr, handle);
                }
            }
        }

        @Override
        public boolean sendSensorEvent(int handle, int type, long timestampNanos,
                @NonNull float[] values) {
            synchronized (mLock) {
                if (!mRuntimeSensorHandles.contains(handle)) {
                    return false;
                }
                return sendRuntimeSensorEventNative(mPtr, handle, type, timestampNanos, values);
            }
        }

        @Override
        public void addProximityActiveListener(@NonNull Executor executor,
                @NonNull ProximityActiveListener listener) {
+174 −13

File changed.

Preview size limit exceeded, changes collapsed.

Loading