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

Commit ca930822 authored by Vladimir Komsiyski's avatar Vladimir Komsiyski
Browse files

Virtual sensor API.

Add VirtualSensor, VirtualSensorEvent and a callback to notify the sensor owner for any changes in the registered sensor listeners.

The virtual device sensors will registered with the sensor framework as runtime sensors. They must remain valid for the entire lifetime of the virtual device, so they are created when it is created and only unregistered when it is closed. The virtual device owners can access them from the device itself after creation.

This CL is only adding the API, the sensor registration and lifetime management is added in a follow-up CL.

Bug: 237278244
Test: atest VirtualSensorConfigTest
Test: atest VirtualSensorEventTest
Test: atest VirtualDeviceParamsTest
Change-Id: I8bb458dacba1bb65b00e288734ce41186c4fe624
parent b6159f31
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -2963,6 +2963,7 @@ package android.companion.virtual {
    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
    method public int getDeviceId();
    method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
    method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
    method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -2980,6 +2981,7 @@ package android.companion.virtual {
    method public int getLockState();
    method @Nullable public String getName();
    method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
    method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
    field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
@@ -2996,6 +2998,7 @@ package android.companion.virtual {
  public static final class VirtualDeviceParams.Builder {
    ctor public VirtualDeviceParams.Builder();
    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
    method @NonNull public android.companion.virtual.VirtualDeviceParams build();
    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
@@ -3053,6 +3056,50 @@ package android.companion.virtual.audio {
}
package android.companion.virtual.sensor {
  public class VirtualSensor {
    method @NonNull public String getName();
    method public int getType();
    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
  }
  public static interface VirtualSensor.SensorStateChangeCallback {
    method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
  }
  public final class VirtualSensorConfig implements android.os.Parcelable {
    method public int describeContents();
    method @NonNull public String getName();
    method public int getType();
    method @Nullable public String getVendor();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
  }
  public static final class VirtualSensorConfig.Builder {
    ctor public VirtualSensorConfig.Builder(int, @NonNull String);
    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback);
    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
  }
  public final class VirtualSensorEvent implements android.os.Parcelable {
    method public int describeContents();
    method public long getTimestampNanos();
    method @NonNull public float[] getValues();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR;
  }
  public static final class VirtualSensorEvent.Builder {
    ctor public VirtualSensorEvent.Builder(@NonNull float[]);
    method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build();
    method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long);
  }
}
package android.content {
  public class ApexEnvironment {
+21 −0
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ package android.companion.virtual;
import android.app.PendingIntent;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.input.VirtualKeyEvent;
@@ -96,6 +99,24 @@ interface IVirtualDevice {
    boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
    boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);

    /**
     * Creates a virtual sensor, capable of injecting sensor events into the system.
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
    void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);

    /**
     * Removes the sensor corresponding to the given token from the system.
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
    void unregisterSensor(IBinder token);

    /**
     * Sends an event to the virtual sensor corresponding to the given token.
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
    boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);

    /**
     * Launches a pending intent on the given display that is owned by this virtual device.
     */
+50 −0
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Point;
@@ -58,6 +60,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;

@@ -250,7 +253,10 @@ public final class VirtualDeviceManager {
                };
        @Nullable
        private VirtualAudioDevice mVirtualAudioDevice;
        @NonNull
        private List<VirtualSensor> mVirtualSensors = new ArrayList<>();

        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
        private VirtualDevice(
                IVirtualDeviceManager service,
                Context context,
@@ -264,6 +270,10 @@ public final class VirtualDeviceManager {
                    associationId,
                    params,
                    mActivityListenerBinder);
            final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
            for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
                mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
            }
        }

        /**
@@ -277,6 +287,23 @@ public final class VirtualDeviceManager {
            }
        }

        /**
         * Returns this device's sensor with the given type and name, if any.
         *
         * @see VirtualDeviceParams.Builder#addVirtualSensorConfig
         *
         * @param type The type of the sensor.
         * @param name The name of the sensor.
         * @return The matching sensor if found, {@code null} otherwise.
         */
        @Nullable
        public VirtualSensor getVirtualSensor(int type, @NonNull String name) {
            return mVirtualSensors.stream()
                    .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name))
                    .findAny()
                    .orElse(null);
        }

        /**
         * Launches a given pending intent on the give display ID.
         *
@@ -379,6 +406,7 @@ public final class VirtualDeviceManager {
        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
        public void close() {
            try {
                // This also takes care of unregistering all virtual sensors.
                mVirtualDevice.close();
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
@@ -563,6 +591,28 @@ public final class VirtualDeviceManager {
            }
        }

        /**
         * Creates a virtual sensor, capable of injecting sensor events into the system. Only for
         * internal use, since device sensors must remain valid for the entire lifetime of the
         * device.
         *
         * @param config The configuration of the sensor.
         * @hide
         */
        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
        @NonNull
        public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) {
            Objects.requireNonNull(config);
            try {
                final IBinder token = new Binder(
                        "android.hardware.sensor.VirtualSensor:" + config.getName());
                mVirtualDevice.createVirtualSensor(token, config);
                return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Adds an activity listener to listen for events such as top activity change or virtual
         * display task stack became empty.
+76 −27
Original line number Diff line number Diff line
@@ -23,20 +23,22 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.internal.util.Preconditions;

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.List;
import java.util.Objects;
import java.util.Set;

@@ -158,6 +160,7 @@ public final class VirtualDeviceParams implements Parcelable {
    @Nullable private final String mName;
    // Mapping of @PolicyType to @DevicePolicy
    @NonNull private final SparseIntArray mDevicePolicies;
    @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;

    private VirtualDeviceParams(
            @LockState int lockState,
@@ -169,24 +172,22 @@ public final class VirtualDeviceParams implements Parcelable {
            @NonNull Set<ComponentName> blockedActivities,
            @ActivityPolicy int defaultActivityPolicy,
            @Nullable String name,
            @NonNull SparseIntArray devicePolicies) {
        Preconditions.checkNotNull(usersWithMatchingAccounts);
        Preconditions.checkNotNull(allowedCrossTaskNavigations);
        Preconditions.checkNotNull(blockedCrossTaskNavigations);
        Preconditions.checkNotNull(allowedActivities);
        Preconditions.checkNotNull(blockedActivities);
        Preconditions.checkNotNull(devicePolicies);

            @NonNull SparseIntArray devicePolicies,
            @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
        mLockState = lockState;
        mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
        mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
        mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations);
        mUsersWithMatchingAccounts =
                new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
        mAllowedCrossTaskNavigations =
                new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations));
        mBlockedCrossTaskNavigations =
                new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations));
        mDefaultNavigationPolicy = defaultNavigationPolicy;
        mAllowedActivities = new ArraySet<>(allowedActivities);
        mBlockedActivities = new ArraySet<>(blockedActivities);
        mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities));
        mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities));
        mDefaultActivityPolicy = defaultActivityPolicy;
        mName = name;
        mDevicePolicies = devicePolicies;
        mDevicePolicies = Objects.requireNonNull(devicePolicies);
        mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
    }

    @SuppressWarnings("unchecked")
@@ -201,6 +202,8 @@ public final class VirtualDeviceParams implements Parcelable {
        mDefaultActivityPolicy = parcel.readInt();
        mName = parcel.readString8();
        mDevicePolicies = parcel.readSparseIntArray();
        mVirtualSensorConfigs = new ArrayList<>();
        parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
    }

    /**
@@ -316,6 +319,15 @@ public final class VirtualDeviceParams implements Parcelable {
        return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
    }

    /**
     * Returns the configurations for all sensors that should be created for this device.
     *
     * @see Builder#addVirtualSensorConfig
     */
    public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() {
        return mVirtualSensorConfigs;
    }

    @Override
    public int describeContents() {
        return 0;
@@ -333,6 +345,7 @@ public final class VirtualDeviceParams implements Parcelable {
        dest.writeInt(mDefaultActivityPolicy);
        dest.writeString8(mName);
        dest.writeSparseIntArray(mDevicePolicies);
        dest.writeTypedList(mVirtualSensorConfigs);
    }

    @Override
@@ -428,6 +441,7 @@ public final class VirtualDeviceParams implements Parcelable {
        private boolean mDefaultActivityPolicyConfigured = false;
        @Nullable private String mName;
        @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
        @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();

        /**
         * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -467,8 +481,7 @@ public final class VirtualDeviceParams implements Parcelable {
        @NonNull
        public Builder setUsersWithMatchingAccounts(
                @NonNull Set<UserHandle> usersWithMatchingAccounts) {
            Preconditions.checkNotNull(usersWithMatchingAccounts);
            mUsersWithMatchingAccounts = usersWithMatchingAccounts;
            mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts);
            return this;
        }

@@ -491,7 +504,6 @@ public final class VirtualDeviceParams implements Parcelable {
        @NonNull
        public Builder setAllowedCrossTaskNavigations(
                @NonNull Set<ComponentName> allowedCrossTaskNavigations) {
            Preconditions.checkNotNull(allowedCrossTaskNavigations);
            if (mDefaultNavigationPolicyConfigured
                    && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) {
                throw new IllegalArgumentException(
@@ -500,7 +512,7 @@ public final class VirtualDeviceParams implements Parcelable {
            }
            mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
            mDefaultNavigationPolicyConfigured = true;
            mAllowedCrossTaskNavigations = allowedCrossTaskNavigations;
            mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations);
            return this;
        }

@@ -523,7 +535,6 @@ public final class VirtualDeviceParams implements Parcelable {
        @NonNull
        public Builder setBlockedCrossTaskNavigations(
                @NonNull Set<ComponentName> blockedCrossTaskNavigations) {
            Preconditions.checkNotNull(blockedCrossTaskNavigations);
            if (mDefaultNavigationPolicyConfigured
                     && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) {
                throw new IllegalArgumentException(
@@ -532,7 +543,7 @@ public final class VirtualDeviceParams implements Parcelable {
            }
            mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
            mDefaultNavigationPolicyConfigured = true;
            mBlockedCrossTaskNavigations = blockedCrossTaskNavigations;
            mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations);
            return this;
        }

@@ -551,7 +562,6 @@ public final class VirtualDeviceParams implements Parcelable {
         */
        @NonNull
        public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
            Preconditions.checkNotNull(allowedActivities);
            if (mDefaultActivityPolicyConfigured
                    && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) {
                throw new IllegalArgumentException(
@@ -559,7 +569,7 @@ public final class VirtualDeviceParams implements Parcelable {
            }
            mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
            mDefaultActivityPolicyConfigured = true;
            mAllowedActivities = allowedActivities;
            mAllowedActivities = Objects.requireNonNull(allowedActivities);
            return this;
        }

@@ -578,7 +588,6 @@ public final class VirtualDeviceParams implements Parcelable {
         */
        @NonNull
        public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
            Preconditions.checkNotNull(blockedActivities);
            if (mDefaultActivityPolicyConfigured
                    && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) {
                throw new IllegalArgumentException(
@@ -586,7 +595,7 @@ public final class VirtualDeviceParams implements Parcelable {
            }
            mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
            mDefaultActivityPolicyConfigured = true;
            mBlockedActivities = blockedActivities;
            mBlockedActivities = Objects.requireNonNull(blockedActivities);
            return this;
        }

@@ -620,11 +629,50 @@ public final class VirtualDeviceParams implements Parcelable {
            return this;
        }

        /**
         * Adds a configuration for a sensor that should be created for this virtual device.
         *
         * Device sensors must remain valid for the entire lifetime of the device, hence they are
         * created together with the device itself, and removed when the device is removed.
         *
         * Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
         *
         * @see android.companion.virtual.sensor.VirtualSensor
         * @see #addDevicePolicy
         */
        @NonNull
        public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) {
            mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig));
            return this;
        }

        /**
         * Builds the {@link VirtualDeviceParams} instance.
         *
         * @throws IllegalArgumentException if there's mismatch between policy definition and
         * the passed parameters or if there are sensor configs with the same type and name.
         *
         */
        @NonNull
        public VirtualDeviceParams build() {
            if (!mVirtualSensorConfigs.isEmpty()
                    && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
                            != DEVICE_POLICY_CUSTOM)) {
                throw new IllegalArgumentException(
                        "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
                                + "virtual sensors.");
            }
            SparseArray<Set<String>> sensorNameByType = new SparseArray();
            for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
                VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
                Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
                if (!sensorNames.add(config.getName())) {
                    throw new IllegalArgumentException(
                            "Sensor names must be unique for a particular sensor type.");
                }
                sensorNameByType.put(config.getType(), sensorNames);
            }

            return new VirtualDeviceParams(
                    mLockState,
                    mUsersWithMatchingAccounts,
@@ -635,7 +683,8 @@ public final class VirtualDeviceParams implements Parcelable {
                    mBlockedActivities,
                    mDefaultActivityPolicy,
                    mName,
                    mDevicePolicies);
                    mDevicePolicies,
                    mVirtualSensorConfigs);
        }
    }
}
+35 −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 android.companion.virtual.sensor;

/**
 * Interface for notification of listener registration changes for a virtual sensor.
 *
 * @hide
 */
oneway interface IVirtualSensorStateChangeCallback {

    /**
     * Called when the registered listeners to a virtual sensor have changed.
     *
     * @param enabled Whether the sensor is enabled.
     * @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
     * @param batchReportingLatencyMicros The requested maximum time interval in microseconds
     * between the delivery of two batches of sensor events.
     */
    void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros);
}
Loading