summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Vladimir Komsiyski <vladokom@google.com> 2025-03-07 12:54:09 -0800
committer Vladimir Komsiyski <vladokom@google.com> 2025-03-07 12:54:09 -0800
commitd6103f03a6d983a6f49ecad4f073357139566016 (patch)
tree5bdf8921568e789b8cac290c5371a38a268f4e15
parent0d1e54610f14ade81c1e0a87d9d86fca59aa439a (diff)
Support virtual sensor additional info
Higher level API compared to the sensors HAL Fix: 393517834 Test: CTS Flag: android.companion.virtualdevice.flags.virtual_sensor_additional_info Change-Id: Ib59871d6f8e4e63d15ddf507c202b543e236d970
-rw-r--r--core/api/system-current.txt17
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl6
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig8
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensor.java47
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl19
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java202
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorConfig.java35
-rw-r--r--services/companion/java/com/android/server/companion/virtual/SensorController.java36
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java13
-rw-r--r--services/core/java/com/android/server/sensors/SensorManagerInternal.java18
-rw-r--r--services/core/java/com/android/server/sensors/SensorService.java16
-rw-r--r--services/core/jni/com_android_server_sensor_SensorService.cpp49
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java58
13 files changed, 520 insertions, 4 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 32b170a6286b..90f6aebb8b02 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3642,11 +3642,26 @@ package android.companion.virtual.sensor {
method public int getDeviceId();
method @NonNull public String getName();
method public int getType();
+ method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public void sendAdditionalInfo(@NonNull android.companion.virtual.sensor.VirtualSensorAdditionalInfo);
method public void sendEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensor> CREATOR;
}
+ @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public final class VirtualSensorAdditionalInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getType();
+ method @NonNull public java.util.List<float[]> getValues();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorAdditionalInfo> CREATOR;
+ }
+
+ public static final class VirtualSensorAdditionalInfo.Builder {
+ ctor public VirtualSensorAdditionalInfo.Builder(int);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorAdditionalInfo.Builder addValues(@NonNull float[]);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorAdditionalInfo build();
+ }
+
public interface VirtualSensorCallback {
method public void onConfigurationChanged(@NonNull android.companion.virtual.sensor.VirtualSensor, boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
}
@@ -3664,6 +3679,7 @@ package android.companion.virtual.sensor {
method public float getResolution();
method public int getType();
method @Nullable public String getVendor();
+ method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") public boolean isAdditionalInfoSupported();
method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public boolean isWakeUpSensor();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
@@ -3672,6 +3688,7 @@ package android.companion.virtual.sensor {
public static final class VirtualSensorConfig.Builder {
ctor public VirtualSensorConfig.Builder(@IntRange(from=1) int, @NonNull String);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
+ method @FlaggedApi("android.companion.virtualdevice.flags.virtual_sensor_additional_info") @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setAdditionalInfoSupported(boolean);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setDirectChannelTypesSupported(int);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setHighestDirectReportRateLevel(int);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setMaxDelay(int);
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index f8ac27de1754..db77adeb5a3d 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -24,6 +24,7 @@ import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorAdditionalInfo;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.companion.virtual.camera.VirtualCameraConfig;
@@ -251,6 +252,11 @@ interface IVirtualDevice {
boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
/**
+ * Sends additional information about the virtual sensor corresponding to the given token.
+ */
+ boolean sendSensorAdditionalInfo(IBinder token, in VirtualSensorAdditionalInfo info);
+
+ /**
* Launches a pending intent on the given display that is owned by this virtual device.
*/
void launchPendingIntent(int displayId, in PendingIntent pendingIntent,
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 4fb3982c3754..615a6dffdf99 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -158,3 +158,11 @@ flag {
bug: "370720522"
is_exported: true
}
+
+flag {
+ name: "virtual_sensor_additional_info"
+ namespace: "virtual_devices"
+ description: "API for injecting SensorAdditionalInfo for VirtualSensor"
+ bug: "393517834"
+ is_exported: true
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index 934a1a8ffcbd..8d4acfcb30d7 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -16,12 +16,15 @@
package android.companion.virtual.sensor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtualdevice.flags.Flags;
import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,20 +40,33 @@ import android.os.RemoteException;
*/
@SystemApi
public final class VirtualSensor implements Parcelable {
+
private final int mHandle;
private final int mType;
private final String mName;
+ private final int mFlags;
private final IVirtualDevice mVirtualDevice;
private final IBinder mToken;
+ // Only one additional info frame set at a time.
+ private final Object mAdditionalInfoLock = new Object();
/**
* @hide
*/
public VirtualSensor(int handle, int type, String name, IVirtualDevice virtualDevice,
IBinder token) {
+ this(handle, type, name, /*flags=*/0, virtualDevice, token);
+ }
+
+ /**
+ * @hide
+ */
+ public VirtualSensor(int handle, int type, String name, int flags, IVirtualDevice virtualDevice,
+ IBinder token) {
mHandle = handle;
mType = type;
mName = name;
+ mFlags = flags;
mVirtualDevice = virtualDevice;
mToken = token;
}
@@ -61,13 +77,14 @@ public final class VirtualSensor implements Parcelable {
@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
public VirtualSensor(int handle, int type, @NonNull String name) {
- this(handle, type, name, /*virtualDevice=*/null, /*token=*/null);
+ this(handle, type, name, /*flags=*/0, /*virtualDevice=*/null, /*token=*/null);
}
private VirtualSensor(Parcel parcel) {
mHandle = parcel.readInt();
mType = parcel.readInt();
mName = parcel.readString8();
+ mFlags = parcel.readInt();
mVirtualDevice = IVirtualDevice.Stub.asInterface(parcel.readStrongBinder());
mToken = parcel.readStrongBinder();
}
@@ -123,6 +140,7 @@ public final class VirtualSensor implements Parcelable {
parcel.writeInt(mHandle);
parcel.writeInt(mType);
parcel.writeString8(mName);
+ parcel.writeInt(mFlags);
parcel.writeStrongBinder(mVirtualDevice.asBinder());
parcel.writeStrongBinder(mToken);
}
@@ -143,6 +161,33 @@ public final class VirtualSensor implements Parcelable {
}
}
+ /**
+ * Send additional information about the sensor to the system.
+ *
+ * @param info the additional sensor information to send.
+ * @throws UnsupportedOperationException if the sensor does not support sending additional info.
+ * @see Sensor#isAdditionalInfoSupported()
+ * @see VirtualSensorConfig.Builder#setAdditionalInfoSupported(boolean)
+ * @see SensorAdditionalInfo
+ * @see VirtualSensorAdditionalInfo
+ */
+ @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO)
+ public void sendAdditionalInfo(@NonNull VirtualSensorAdditionalInfo info) {
+ if (!Flags.virtualSensorAdditionalInfo()) {
+ return;
+ }
+ if ((mFlags & VirtualSensorConfig.ADDITIONAL_INFO_MASK) == 0) {
+ throw new UnsupportedOperationException("Sensor additional info not supported.");
+ }
+ try {
+ synchronized (mAdditionalInfoLock) {
+ mVirtualDevice.sendSensorAdditionalInfo(mToken, info);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
public static final Parcelable.Creator<VirtualSensor> CREATOR =
new Parcelable.Creator<VirtualSensor>() {
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl
new file mode 100644
index 000000000000..7267be88ca75
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+parcelable VirtualSensorAdditionalInfo; \ No newline at end of file
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java
new file mode 100644
index 000000000000..a4fca507b1d5
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorAdditionalInfo.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtualdevice.flags.Flags;
+import android.hardware.SensorAdditionalInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An additional information frame for a {@link VirtualSensor}, which is reported through listener
+ * callback {@link android.hardware.SensorEventCallback#onSensorAdditionalInfo}.
+ *
+ * @see SensorAdditionalInfo
+ * @see VirtualSensorConfig.Builder#setAdditionalInfoSupported(boolean)
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO)
+@SystemApi
+public final class VirtualSensorAdditionalInfo implements Parcelable {
+
+ private final int mType;
+ @NonNull
+ private final List<float[]> mValues;
+
+ /** @hide */
+ @IntDef(prefix = "TYPE_", value = {
+ SensorAdditionalInfo.TYPE_UNTRACKED_DELAY,
+ SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE,
+ SensorAdditionalInfo.TYPE_VEC3_CALIBRATION,
+ SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT,
+ SensorAdditionalInfo.TYPE_SAMPLING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ private VirtualSensorAdditionalInfo(int type, @NonNull List<float[]> values) {
+ mType = type;
+ mValues = values;
+ }
+
+ private VirtualSensorAdditionalInfo(@NonNull Parcel parcel) {
+ mType = parcel.readInt();
+ final int valuesLength = parcel.readInt();
+ mValues = new ArrayList<>(valuesLength);
+ for (int i = 0; i < valuesLength; ++i) {
+ mValues.add(parcel.createFloatArray());
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeInt(mType);
+ parcel.writeInt(mValues.size());
+ for (int i = 0; i < mValues.size(); ++i) {
+ parcel.writeFloatArray(mValues.get(i));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the type of this information frame.
+ *
+ * @see SensorAdditionalInfo#type
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the float values of this information frame, if any.
+ *
+ * @see SensorAdditionalInfo#floatValues
+ */
+ @NonNull
+ public List<float[]> getValues() {
+ return mValues;
+ }
+
+ /**
+ * Builder for {@link VirtualSensorAdditionalInfo}.
+ */
+ public static final class Builder {
+
+ @VirtualSensorAdditionalInfo.Type
+ private final int mType;
+ @NonNull
+ private final ArrayList<float[]> mValues = new ArrayList<>();
+
+ /**
+ * Creates a new builder.
+ *
+ * @param type type of this additional info frame.
+ * @see SensorAdditionalInfo
+ */
+ public Builder(@VirtualSensorAdditionalInfo.Type int type) {
+ switch (type) {
+ case SensorAdditionalInfo.TYPE_UNTRACKED_DELAY:
+ case SensorAdditionalInfo.TYPE_SAMPLING:
+ case SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE:
+ case SensorAdditionalInfo.TYPE_VEC3_CALIBRATION:
+ case SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT:
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported type " + type);
+ }
+ mType = type;
+ }
+
+ /**
+ * Additional info payload data represented in float values. Depending on the type of
+ * information, this may be null.
+ *
+ * @see SensorAdditionalInfo#floatValues
+ */
+ @NonNull
+ public Builder addValues(@NonNull float[] values) {
+ if (values.length > 14) {
+ throw new IllegalArgumentException("Maximum payload value size is 14.");
+ }
+ if (mValues.isEmpty()) {
+ switch (mType) {
+ case SensorAdditionalInfo.TYPE_UNTRACKED_DELAY:
+ case SensorAdditionalInfo.TYPE_SAMPLING:
+ assertValuesLength(values, 2);
+ break;
+ case SensorAdditionalInfo.TYPE_INTERNAL_TEMPERATURE:
+ assertValuesLength(values, 1);
+ break;
+ case SensorAdditionalInfo.TYPE_VEC3_CALIBRATION:
+ case SensorAdditionalInfo.TYPE_SENSOR_PLACEMENT:
+ assertValuesLength(values, 11);
+ break;
+ }
+ } else if (values.length != mValues.getFirst().length) {
+ throw new IllegalArgumentException("All payload values must have the same length");
+ }
+
+ mValues.add(values);
+ return this;
+ }
+
+ private void assertValuesLength(float[] values, int expected) {
+ if (values.length != expected) {
+ throw new IllegalArgumentException(
+ "Payload values must have size " + expected + " for type " + mType);
+ }
+ }
+
+ /**
+ * Creates a new {@link VirtualSensorAdditionalInfo}.
+ *
+ * @throws IllegalArgumentException if the payload doesn't match the expectation for the
+ * given type, as documented in {@link SensorAdditionalInfo}.
+ */
+ @NonNull
+ public VirtualSensorAdditionalInfo build() {
+ if (mValues.isEmpty()) {
+ throw new IllegalArgumentException("Payload is required");
+ }
+ return new VirtualSensorAdditionalInfo(mType, mValues);
+ }
+ }
+
+ public static final @NonNull Creator<VirtualSensorAdditionalInfo> CREATOR =
+ new Creator<>() {
+ public VirtualSensorAdditionalInfo createFromParcel(Parcel source) {
+ return new VirtualSensorAdditionalInfo(source);
+ }
+
+ public VirtualSensorAdditionalInfo[] newArray(int size) {
+ return new VirtualSensorAdditionalInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index 68bc9bce28d2..be8974ec29ad 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -59,10 +59,14 @@ public final class VirtualSensorConfig implements Parcelable {
private static final int REPORTING_MODE_MASK = 0xE;
private static final int REPORTING_MODE_SHIFT = 1;
+ // Mask for indication bit of sensor additional information support, bit 6.
+ static final int ADDITIONAL_INFO_MASK = 0x40;
+
// Mask for direct mode highest rate level, bit 7, 8, 9.
private static final int DIRECT_REPORT_MASK = 0x380;
private static final int DIRECT_REPORT_SHIFT = 7;
+
// Mask for supported direct channel, bit 10, 11
private static final int DIRECT_CHANNEL_SHIFT = 10;
@@ -253,6 +257,18 @@ public final class VirtualSensorConfig implements Parcelable {
}
/**
+ * Returns whether the sensor supports additional information.
+ *
+ * @see Builder#setAdditionalInfoSupported(boolean)
+ * @see Sensor#isAdditionalInfoSupported()
+ * @see android.hardware.SensorAdditionalInfo
+ */
+ @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO)
+ public boolean isAdditionalInfoSupported() {
+ return (mFlags & ADDITIONAL_INFO_MASK) > 0;
+ }
+
+ /**
* Returns the reporting mode of this sensor.
*
* @see Builder#setReportingMode(int)
@@ -450,6 +466,25 @@ public final class VirtualSensorConfig implements Parcelable {
}
/**
+ * Sets whether this sensor supports sensor additional information.
+ *
+ * @see Sensor#isAdditionalInfoSupported()
+ * @see android.hardware.SensorAdditionalInfo
+ * @see VirtualSensorAdditionalInfo
+ */
+ @FlaggedApi(Flags.FLAG_VIRTUAL_SENSOR_ADDITIONAL_INFO)
+ @NonNull
+ public VirtualSensorConfig.Builder setAdditionalInfoSupported(
+ boolean additionalInfoSupported) {
+ if (additionalInfoSupported) {
+ mFlags |= ADDITIONAL_INFO_MASK;
+ } else {
+ mFlags &= ~ADDITIONAL_INFO_MASK;
+ }
+ return this;
+ }
+
+ /**
* Sets the reporting mode of this sensor.
*
* @throws IllegalArgumentException if the reporting mode is not one of
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index 88b791046c87..6e098d014ee3 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -21,15 +21,18 @@ import android.annotation.Nullable;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorAdditionalInfo;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.AttributionSource;
+import android.hardware.SensorAdditionalInfo;
import android.hardware.SensorDirectChannel;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SharedMemory;
+import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
@@ -140,7 +143,7 @@ public class SensorController {
final IBinder sensorToken =
new Binder("android.hardware.sensor.VirtualSensor:" + config.getName());
VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(),
- virtualDevice, sensorToken);
+ config.getFlags(), virtualDevice, sensorToken);
synchronized (mLock) {
mSensorDescriptors.put(sensorToken, sensorDescriptor);
mVirtualSensors.put(handle, sensor);
@@ -164,6 +167,37 @@ public class SensorController {
}
}
+ boolean sendSensorAdditionalInfo(@NonNull IBinder token,
+ @NonNull VirtualSensorAdditionalInfo info) {
+ Objects.requireNonNull(token);
+ Objects.requireNonNull(info);
+ synchronized (mLock) {
+ final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token);
+ long timestamp = SystemClock.elapsedRealtimeNanos();
+ if (sensorDescriptor == null) {
+ throw new IllegalArgumentException("Could not send sensor event for given token");
+ }
+ if (!mSensorManagerInternal.sendSensorAdditionalInfo(
+ sensorDescriptor.getHandle(), SensorAdditionalInfo.TYPE_FRAME_BEGIN,
+ /* serial= */ 0, timestamp++, /* values= */ null)) {
+ return false;
+ }
+ for (int i = 0; i < info.getValues().size(); ++i) {
+ if (!mSensorManagerInternal.sendSensorAdditionalInfo(
+ sensorDescriptor.getHandle(), info.getType(), /* serial= */ i,
+ timestamp++, info.getValues().get(i))) {
+ return false;
+ }
+ }
+ if (!mSensorManagerInternal.sendSensorAdditionalInfo(
+ sensorDescriptor.getHandle(), SensorAdditionalInfo.TYPE_FRAME_END,
+ /* serial= */ 0, timestamp, /* values= */ null)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@Nullable
VirtualSensor getSensorByHandle(int handle) {
synchronized (mLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 28efdfc01ee2..0023b6d53837 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -55,6 +55,7 @@ import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorAdditionalInfo;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.companion.virtualdevice.flags.Flags;
import android.compat.annotation.ChangeId;
@@ -1294,6 +1295,18 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
+ public boolean sendSensorAdditionalInfo(@NonNull IBinder token,
+ @NonNull VirtualSensorAdditionalInfo info) {
+ checkCallerIsDeviceOwner();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mSensorController.sendSensorAdditionalInfo(token, info);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public void registerIntentInterceptor(IVirtualDeviceIntentInterceptor intentInterceptor,
IntentFilter filter) {
checkCallerIsDeviceOwner();
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index 7ff4ade1101c..9636cc6c77a7 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -17,6 +17,7 @@
package com.android.server.sensors;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.SensorDirectChannel;
import android.os.ParcelFileDescriptor;
@@ -71,7 +72,7 @@ public abstract class SensorManagerInternal {
/**
* Sends an event for the runtime sensor with the given handle to the framework.
*
- * Only relevant for sending runtime sensor events. @see #createRuntimeSensor.
+ * <p>Only relevant for sending runtime sensor events. @see #createRuntimeSensor.</p>
*
* @param handle The sensor handle.
* @param type The type of the sensor.
@@ -83,6 +84,21 @@ public abstract class SensorManagerInternal {
@NonNull float[] values);
/**
+ * Sends an additional info event for the runtime sensor with the given handle to the framework.
+ *
+ * <p>Only relevant for runtime sensors. @see #createRuntimeSensor.</p>
+ *
+ * @param handle The sensor handle.
+ * @param type The type of payload data.
+ * @param serial The sequence number of this frame for this type.
+ * @param timestampNanos Timestamp of the event.
+ * @param values The payload data represented in float values.
+ * @return Whether the event injection was successful.
+ */
+ public abstract boolean sendSensorAdditionalInfo(int handle, int type, int serial,
+ long timestampNanos, @Nullable float[] values);
+
+ /**
* Listener for proximity sensor state changes.
*/
public interface ProximityActiveListener {
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 3de191030d71..0d31b22e2020 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -19,6 +19,7 @@ package com.android.server.sensors;
import static com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.util.ArrayMap;
@@ -62,6 +63,9 @@ public class SensorService extends SystemService {
private static native void unregisterRuntimeSensorNative(long ptr, int handle);
private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
long timestampNanos, float[] values);
+ private static native boolean sendRuntimeSensorAdditionalInfoNative(long ptr, int handle,
+ int type, int serial, long timestampNanos, float[] values);
+
public SensorService(Context ctx) {
super(ctx);
@@ -129,6 +133,18 @@ public class SensorService extends SystemService {
}
@Override
+ public boolean sendSensorAdditionalInfo(int handle, int type, int serial,
+ long timestampNanos, @Nullable float[] values) {
+ synchronized (mLock) {
+ if (!mRuntimeSensorHandles.contains(handle)) {
+ return false;
+ }
+ return sendRuntimeSensorAdditionalInfoNative(mPtr, handle, type, serial,
+ timestampNanos, values);
+ }
+ }
+
+ @Override
public void addProximityActiveListener(@NonNull Executor executor,
@NonNull ProximityActiveListener listener) {
Objects.requireNonNull(executor, "executor must not be null");
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index eb729de6afd4..0bee7181c2d5 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -60,6 +60,8 @@ public:
void unregisterRuntimeSensor(jint handle);
jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
jfloatArray values);
+ jboolean sendRuntimeSensorAdditionalInfo(JNIEnv* env, jint handle, jint type, jint serial,
+ jlong timestamp, jfloatArray values);
private:
sp<SensorService> mService;
@@ -172,9 +174,9 @@ jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, j
sensors_event_t event{
.version = sizeof(sensors_event_t),
- .timestamp = timestamp,
.sensor = handle,
.type = type,
+ .timestamp = timestamp,
};
int valuesLength = env->GetArrayLength(values);
@@ -234,6 +236,42 @@ jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, j
return err == OK;
}
+jboolean NativeSensorService::sendRuntimeSensorAdditionalInfo(JNIEnv* env, jint handle, jint type,
+ jint serial, jlong timestamp,
+ jfloatArray values) {
+ if (mService == nullptr) {
+ ALOGD("Dropping sendRuntimeSensorAdditionalInfo, sensor service not available.");
+ return false;
+ }
+
+ sensors_event_t event{
+ .version = sizeof(sensors_event_t),
+ .sensor = handle,
+ .type = SENSOR_TYPE_ADDITIONAL_INFO,
+ .timestamp = timestamp,
+ .additional_info =
+ (additional_info_event_t){
+ .type = type,
+ .serial = serial,
+ },
+ };
+
+ if (values != nullptr) {
+ int valuesLength = env->GetArrayLength(values);
+ if (valuesLength > 14) {
+ ALOGD("Dropping sendRuntimeSensorAdditionalInfo, number of values exceeds maximum.");
+ return false;
+ }
+ if (valuesLength > 0) {
+ jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr);
+ memcpy(event.additional_info.data_float, sensorValues, valuesLength * sizeof(float));
+ }
+ }
+
+ status_t err = mService->sendRuntimeSensorEvent(event);
+ return err == OK;
+}
+
NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate(
JNIEnv* env, jobject listener)
: mListener(env->NewGlobalRef(listener)) {}
@@ -326,6 +364,13 @@ static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jin
return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values);
}
+static jboolean sendRuntimeSensorAdditionalInfoNative(JNIEnv* env, jclass, jlong ptr, jint handle,
+ jint type, jint serial, jlong timestamp,
+ jfloatArray values) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ return service->sendRuntimeSensorAdditionalInfo(env, handle, type, serial, timestamp, values);
+}
+
static const JNINativeMethod methods[] = {
{"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
reinterpret_cast<void*>(startSensorServiceNative)},
@@ -340,6 +385,8 @@ static const JNINativeMethod methods[] = {
reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
{"sendRuntimeSensorEventNative", "(JIIJ[F)Z",
reinterpret_cast<void*>(sendRuntimeSensorEventNative)},
+ {"sendRuntimeSensorAdditionalInfoNative", "(JIIIJ[F)Z",
+ reinterpret_cast<void*>(sendRuntimeSensorAdditionalInfoNative)},
};
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
index 67fc564fa778..2e07cd8ae698 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -22,18 +22,25 @@ import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorAdditionalInfo;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.AttributionSource;
import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -49,6 +56,7 @@ import com.google.common.collect.Iterables;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -65,6 +73,9 @@ public class SensorControllerTest {
private static final int VIRTUAL_SENSOR_TYPE = Sensor.TYPE_ACCELEROMETER;
+ private static final float[] ADDITIONAL_INFO_VALUES_1 = new float[] {1.2f, 3.4f};
+ private static final float[] ADDITIONAL_INFO_VALUES_2 = new float[] {5.6f, 7.8f};
+
@Mock
private SensorManagerInternal mSensorManagerInternalMock;
@Mock
@@ -155,6 +166,53 @@ public class SensorControllerTest {
}
@Test
+ public void sendSensorAdditionalInfo_invalidToken_throwsException() throws Exception {
+ SensorController sensorController = doCreateSensorSuccessfully();
+
+ final VirtualSensorAdditionalInfo info =
+ new VirtualSensorAdditionalInfo.Builder(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY)
+ .addValues(ADDITIONAL_INFO_VALUES_1)
+ .addValues(ADDITIONAL_INFO_VALUES_2)
+ .build();
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> sensorController.sendSensorAdditionalInfo(
+ new Binder("invalidSensorToken"), info));
+ }
+
+ @Test
+ public void sendSensorAdditionalInfo_success() throws Exception {
+ SensorController sensorController = doCreateSensorSuccessfully();
+
+ clearInvocations(mSensorManagerInternalMock);
+ when(mSensorManagerInternalMock.sendSensorAdditionalInfo(
+ anyInt(), anyInt(), anyInt(), anyLong(), any()))
+ .thenReturn(true);
+ IBinder token = Iterables.getOnlyElement(sensorController.getSensorDescriptors().keySet());
+
+ final VirtualSensorAdditionalInfo info =
+ new VirtualSensorAdditionalInfo.Builder(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY)
+ .addValues(ADDITIONAL_INFO_VALUES_1)
+ .addValues(ADDITIONAL_INFO_VALUES_2)
+ .build();
+ sensorController.sendSensorAdditionalInfo(token, info);
+
+ InOrder inOrder = inOrder(mSensorManagerInternalMock);
+ inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo(
+ eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_FRAME_BEGIN),
+ /*serial=*/ eq(0), /* timestamp= */ anyLong(), /*values=*/ isNull());
+ inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo(
+ eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY),
+ /*serial=*/ eq(0), /* timestamp= */ anyLong(), eq(ADDITIONAL_INFO_VALUES_1));
+ inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo(
+ eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_UNTRACKED_DELAY),
+ /*serial=*/ eq(1), /* timestamp= */ anyLong(), eq(ADDITIONAL_INFO_VALUES_2));
+ inOrder.verify(mSensorManagerInternalMock).sendSensorAdditionalInfo(
+ eq(SENSOR_HANDLE), eq(SensorAdditionalInfo.TYPE_FRAME_END),
+ /*serial=*/ eq(0), /* timestamp= */ anyLong(), /*values=*/ isNull());
+ }
+
+ @Test
public void close_unregistersSensors() throws Exception {
SensorController sensorController = doCreateSensorSuccessfully();