diff options
8 files changed, 756 insertions, 53 deletions
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java new file mode 100644 index 000000000000..ec7e993ec30e --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java @@ -0,0 +1,235 @@ +/* + * 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); + } + } +} 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 019f582d4790..798ae0ea3230 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -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() { diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java index fbb6644934f1..f17e5e73ceb0 100644 --- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java +++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java @@ -43,6 +43,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. */ public interface ProximityActiveListener { @@ -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); + } } diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java index 8fe2d52f7160..d8e3bddd6432 100644 --- a/services/core/java/com/android/server/sensors/SensorService.java +++ b/services/core/java/com/android/server/sensors/SensorService.java @@ -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); @@ -85,6 +95,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) { 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 63b7dfbc2a3b..10d8b42c2979 100644 --- a/services/core/jni/com_android_server_sensor_SensorService.cpp +++ b/services/core/jni/com_android_server_sensor_SensorService.cpp @@ -22,6 +22,7 @@ #include <cutils/properties.h> #include <jni.h> #include <sensorservice/SensorService.h> +#include <string.h> #include <utils/Log.h> #include <utils/misc.h> @@ -30,10 +31,14 @@ #define PROXIMITY_ACTIVE_CLASS \ "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener" +#define RUNTIME_SENSOR_CALLBACK_CLASS \ + "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback" + namespace android { static JavaVM* sJvm = nullptr; static jmethodID sMethodIdOnProximityActive; +static jmethodID sMethodIdOnStateChanged; class NativeSensorService { public: @@ -41,6 +46,11 @@ public: void registerProximityActiveListener(); void unregisterProximityActiveListener(); + jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor, + jobject callback); + void unregisterRuntimeSensor(jint handle); + jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp, + jfloatArray values); private: sp<SensorService> mService; @@ -56,6 +66,18 @@ private: jobject mListener; }; sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate; + + class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback { + public: + RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback); + ~RuntimeSensorCallbackDelegate(); + + void onStateChanged(bool enabled, int64_t samplingPeriodNs, + int64_t batchReportLatencyNs) override; + + private: + jobject mCallback; + }; }; NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener) @@ -85,6 +107,109 @@ void NativeSensorService::unregisterProximityActiveListener() { mService->removeProximityActiveListener(mProximityActiveListenerDelegate); } +jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, + jstring vendor, jobject callback) { + if (mService == nullptr) { + ALOGD("Dropping registerRuntimeSensor, sensor service not available."); + return -1; + } + + sensor_t sensor{ + .name = env->GetStringUTFChars(name, 0), + .vendor = env->GetStringUTFChars(vendor, 0), + .version = sizeof(sensor_t), + .type = type, + }; + + sp<RuntimeSensorCallbackDelegate> callbackDelegate( + new RuntimeSensorCallbackDelegate(env, callback)); + return mService->registerRuntimeSensor(sensor, deviceId, callbackDelegate); +} + +void NativeSensorService::unregisterRuntimeSensor(jint handle) { + if (mService == nullptr) { + ALOGD("Dropping unregisterProximityActiveListener, sensor service not available."); + return; + } + + mService->unregisterRuntimeSensor(handle); +} + +jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, + jlong timestamp, jfloatArray values) { + if (mService == nullptr) { + ALOGD("Dropping sendRuntimeSensorEvent, sensor service not available."); + return false; + } + if (values == nullptr) { + ALOGD("Dropping sendRuntimeSensorEvent, no values."); + return false; + } + + sensors_event_t event{ + .version = sizeof(sensors_event_t), + .timestamp = timestamp, + .sensor = handle, + .type = type, + }; + + int valuesLength = env->GetArrayLength(values); + jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr); + + switch (type) { + case SENSOR_TYPE_ACCELEROMETER: + case SENSOR_TYPE_MAGNETIC_FIELD: + case SENSOR_TYPE_ORIENTATION: + case SENSOR_TYPE_GYROSCOPE: + case SENSOR_TYPE_GRAVITY: + case SENSOR_TYPE_LINEAR_ACCELERATION: { + if (valuesLength != 3) { + ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values."); + return false; + } + event.acceleration.x = sensorValues[0]; + event.acceleration.y = sensorValues[1]; + event.acceleration.z = sensorValues[2]; + break; + } + case SENSOR_TYPE_DEVICE_ORIENTATION: + case SENSOR_TYPE_LIGHT: + case SENSOR_TYPE_PRESSURE: + case SENSOR_TYPE_TEMPERATURE: + case SENSOR_TYPE_PROXIMITY: + case SENSOR_TYPE_RELATIVE_HUMIDITY: + case SENSOR_TYPE_AMBIENT_TEMPERATURE: + case SENSOR_TYPE_SIGNIFICANT_MOTION: + case SENSOR_TYPE_STEP_DETECTOR: + case SENSOR_TYPE_TILT_DETECTOR: + case SENSOR_TYPE_WAKE_GESTURE: + case SENSOR_TYPE_GLANCE_GESTURE: + case SENSOR_TYPE_PICK_UP_GESTURE: + case SENSOR_TYPE_WRIST_TILT_GESTURE: + case SENSOR_TYPE_STATIONARY_DETECT: + case SENSOR_TYPE_MOTION_DETECT: + case SENSOR_TYPE_HEART_BEAT: + case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT: { + if (valuesLength != 1) { + ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values."); + return false; + } + event.data[0] = sensorValues[0]; + break; + } + default: { + if (valuesLength > 16) { + ALOGD("Dropping sendRuntimeSensorEvent, number of values exceeds the maximum."); + return false; + } + memcpy(event.data, sensorValues, valuesLength * sizeof(float)); + } + } + + status_t err = mService->sendRuntimeSensorEvent(event); + return err == OK; +} + NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate( JNIEnv* env, jobject listener) : mListener(env->NewGlobalRef(listener)) {} @@ -98,6 +223,22 @@ void NativeSensorService::ProximityActiveListenerDelegate::onProximityActive(boo jniEnv->CallVoidMethod(mListener, sMethodIdOnProximityActive, static_cast<jboolean>(isActive)); } +NativeSensorService::RuntimeSensorCallbackDelegate::RuntimeSensorCallbackDelegate(JNIEnv* env, + jobject callback) + : mCallback(env->NewGlobalRef(callback)) {} + +NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelegate() { + AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback); +} + +void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged( + bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) { + auto jniEnv = GetOrAttachJNIEnvironment(sJvm); + jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled), + static_cast<jint>(ns2us(samplingPeriodNs)), + static_cast<jint>(ns2us(batchReportLatencyNs))); +} + static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) { NativeSensorService* service = new NativeSensorService(env, listener); return reinterpret_cast<jlong>(service); @@ -113,26 +254,46 @@ static void unregisterProximityActiveListenerNative(JNIEnv* env, jclass, jlong p service->unregisterProximityActiveListener(); } -static const JNINativeMethod methods[] = { - { - "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J", - reinterpret_cast<void*>(startSensorServiceNative) - }, - { - "registerProximityActiveListenerNative", "(J)V", - reinterpret_cast<void*>(registerProximityActiveListenerNative) - }, - { - "unregisterProximityActiveListenerNative", "(J)V", - reinterpret_cast<void*>(unregisterProximityActiveListenerNative) - }, +static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type, + jstring name, jstring vendor, jobject callback) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback); +} + +static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + service->unregisterRuntimeSensor(handle); +} + +static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jint handle, jint type, + jlong timestamp, jfloatArray values) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values); +} +static const JNINativeMethod methods[] = { + {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J", + reinterpret_cast<void*>(startSensorServiceNative)}, + {"registerProximityActiveListenerNative", "(J)V", + reinterpret_cast<void*>(registerProximityActiveListenerNative)}, + {"unregisterProximityActiveListenerNative", "(J)V", + reinterpret_cast<void*>(unregisterProximityActiveListenerNative)}, + {"registerRuntimeSensorNative", + "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I", + reinterpret_cast<void*>(registerRuntimeSensorNative)}, + {"unregisterRuntimeSensorNative", "(JI)V", + reinterpret_cast<void*>(unregisterRuntimeSensorNative)}, + {"sendRuntimeSensorEventNative", "(JIIJ[F)Z", + reinterpret_cast<void*>(sendRuntimeSensorEventNative)}, }; int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) { sJvm = vm; jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS); sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V"); + jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS); + sMethodIdOnStateChanged = + GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V"); return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods, NELEM(methods)); } diff --git a/services/proguard.flags b/services/proguard.flags index 27fe5056b8b4..6cdf11c3c685 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -88,6 +88,7 @@ -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; } 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 new file mode 100644 index 000000000000..ef8a49f95a49 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java @@ -0,0 +1,140 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.companion.virtual.sensor.VirtualSensorConfig; +import android.companion.virtual.sensor.VirtualSensorEvent; +import android.hardware.Sensor; +import android.os.Binder; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.server.LocalServices; +import com.android.server.sensors.SensorManagerInternal; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class SensorControllerTest { + + private static final int VIRTUAL_DEVICE_ID = 42; + private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer"; + private static final int SENSOR_HANDLE = 7; + + @Mock + private SensorManagerInternal mSensorManagerInternalMock; + private SensorController mSensorController; + private VirtualSensorEvent mSensorEvent; + private VirtualSensorConfig mVirtualSensorConfig; + private IBinder mSensorToken; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + LocalServices.removeServiceForTest(SensorManagerInternal.class); + LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock); + + mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID); + mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build(); + mVirtualSensorConfig = + new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME) + .build(); + mSensorToken = new Binder("sensorToken"); + } + + @Test + public void createSensor_invalidHandle_throwsException() { + doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor( + anyInt(), anyInt(), anyString(), anyString(), any()); + + Throwable thrown = assertThrows( + RuntimeException.class, + () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig)); + + assertThat(thrown.getCause().getMessage()) + .contains("Received an invalid virtual sensor handle"); + } + + @Test + public void createSensor_success() { + doCreateSensorSuccessfully(); + + assertThat(mSensorController.getSensorDescriptors()).isNotEmpty(); + } + + @Test + public void sendSensorEvent_invalidToken_throwsException() { + doCreateSensorSuccessfully(); + + assertThrows( + IllegalArgumentException.class, + () -> mSensorController.sendSensorEvent( + new Binder("invalidSensorToken"), mSensorEvent)); + } + + @Test + public void sendSensorEvent_success() { + doCreateSensorSuccessfully(); + + mSensorController.sendSensorEvent(mSensorToken, mSensorEvent); + verify(mSensorManagerInternalMock).sendSensorEvent( + SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(), + mSensorEvent.getValues()); + } + + @Test + public void unregisterSensor_invalidToken_throwsException() { + doCreateSensorSuccessfully(); + + assertThrows( + IllegalArgumentException.class, + () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken"))); + } + + @Test + public void unregisterSensor_success() { + doCreateSensorSuccessfully(); + + mSensorController.unregisterSensor(mSensorToken); + verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE); + assertThat(mSensorController.getSensorDescriptors()).isEmpty(); + } + + private void doCreateSensorSuccessfully() { + doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor( + anyInt(), anyInt(), anyString(), anyString(), any()); + mSensorController.createSensor(mSensorToken, mVirtualSensorConfig); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 09dc367cceb4..41700ab1f04e 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -51,6 +51,7 @@ import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; +import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; @@ -58,6 +59,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.graphics.Point; +import android.hardware.Sensor; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.IInputManager; import android.hardware.input.VirtualKeyEvent; @@ -88,6 +90,7 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.app.BlockedAppStreamingActivity; import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; +import com.android.server.sensors.SensorManagerInternal; import org.junit.Before; import org.junit.Test; @@ -125,16 +128,19 @@ public class VirtualDeviceManagerServiceTest { private static final int VENDOR_ID = 5; private static final String UNIQUE_ID = "uniqueid"; private static final String PHYS = "phys"; - private static final int DEVICE_ID = 42; + private static final int DEVICE_ID = 53; private static final int HEIGHT = 1800; private static final int WIDTH = 900; + private static final int SENSOR_HANDLE = 64; private static final Binder BINDER = new Binder("binder"); private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000; + private static final int VIRTUAL_DEVICE_ID = 42; private Context mContext; private InputManagerMockHelper mInputManagerMockHelper; private VirtualDeviceImpl mDeviceImpl; private InputController mInputController; + private SensorController mSensorController; private AssociationInfo mAssociationInfo; private VirtualDeviceManagerService mVdms; private VirtualDeviceManagerInternal mLocalService; @@ -149,6 +155,8 @@ public class VirtualDeviceManagerServiceTest { @Mock private InputManagerInternal mInputManagerInternalMock; @Mock + private SensorManagerInternal mSensorManagerInternalMock; + @Mock private IVirtualDeviceActivityListener mActivityListener; @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; @@ -205,6 +213,9 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(InputManagerInternal.class); LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + LocalServices.removeServiceForTest(SensorManagerInternal.class); + LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock); + final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.uniqueId = UNIQUE_ID; doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt()); @@ -229,6 +240,7 @@ public class VirtualDeviceManagerServiceTest { mInputController = new InputController(new Object(), mNativeWrapperMock, new Handler(TestableLooper.get(this).getLooper()), mContext.getSystemService(WindowManager.class), threadVerifier); + mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID); mAssociationInfo = new AssociationInfo(1, 0, null, MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0); @@ -241,9 +253,9 @@ public class VirtualDeviceManagerServiceTest { .setBlockedActivities(getBlockedActivities()) .build(); mDeviceImpl = new VirtualDeviceImpl(mContext, - mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1, - mInputController, (int associationId) -> {}, mPendingTrampolineCallback, - mActivityListener, mRunningAppsChangedCallback, params); + mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID, + mInputController, mSensorController, (int associationId) -> {}, + mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params); mVdms.addVirtualDevice(mDeviceImpl); } @@ -285,9 +297,9 @@ public class VirtualDeviceManagerServiceTest { .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM) .build(); mDeviceImpl = new VirtualDeviceImpl(mContext, - mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1, - mInputController, (int associationId) -> {}, mPendingTrampolineCallback, - mActivityListener, mRunningAppsChangedCallback, params); + mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID, + mInputController, mSensorController, (int associationId) -> {}, + mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params); mVdms.addVirtualDevice(mDeviceImpl); assertThat( @@ -552,6 +564,18 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void createVirtualSensor_noPermission_failsSecurityException() { + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualSensor( + BINDER, + new VirtualSensorConfig.Builder( + Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build())); + } + + @Test public void onAudioSessionStarting_noPermission_failsSecurityException() { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( @@ -655,6 +679,17 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void close_cleanSensorController() { + mSensorController.addSensorForTesting( + BINDER, SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, DEVICE_NAME); + + mDeviceImpl.close(); + + assertThat(mSensorController.getSensorDescriptors()).isEmpty(); + verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE); + } + + @Test public void sendKeyEvent_noFd() { assertThrows( IllegalArgumentException.class, |