summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt53
-rw-r--r--core/api/system-current.txt36
-rw-r--r--core/api/test-current.txt8
-rw-r--r--core/java/android/app/SystemServiceRegistry.java3
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl12
-rw-r--r--core/java/android/hardware/input/InputDeviceLightsManager.java139
-rw-r--r--core/java/android/hardware/input/InputManager.java98
-rw-r--r--core/java/android/hardware/lights/Light.java60
-rw-r--r--core/java/android/hardware/lights/LightState.java73
-rw-r--r--core/java/android/hardware/lights/LightsManager.java142
-rw-r--r--core/java/android/hardware/lights/LightsRequest.java32
-rw-r--r--core/java/android/hardware/lights/SystemLightsManager.java181
-rw-r--r--core/java/android/view/InputDevice.java19
-rw-r--r--core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java227
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java161
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp113
-rw-r--r--services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java32
17 files changed, 1196 insertions, 193 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 1296dd19cc7e..fec073f1199c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18575,6 +18575,58 @@ package android.hardware.input {
}
+package android.hardware.lights {
+
+ public final class Light implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getId();
+ method @NonNull public String getName();
+ method public int getOrdinal();
+ method public int getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR;
+ field public static final int LIGHT_TYPE_INPUT_PLAYER_ID = 10; // 0xa
+ field public static final int LIGHT_TYPE_INPUT_RGB = 11; // 0xb
+ field public static final int LIGHT_TYPE_INPUT_SINGLE = 9; // 0x9
+ field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8
+ }
+
+ public final class LightState implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public static android.hardware.lights.LightState forColor(@ColorInt int);
+ method @NonNull public static android.hardware.lights.LightState forPlayerId(int);
+ method @ColorInt public int getColor();
+ method public int getPlayerId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.LightState> CREATOR;
+ }
+
+ public abstract class LightsManager {
+ method @NonNull public abstract android.hardware.lights.LightState getLightState(@NonNull android.hardware.lights.Light);
+ method @NonNull public abstract java.util.List<android.hardware.lights.Light> getLights();
+ method @NonNull public abstract android.hardware.lights.LightsManager.LightsSession openSession();
+ }
+
+ public abstract static class LightsManager.LightsSession implements java.lang.AutoCloseable {
+ ctor public LightsManager.LightsSession();
+ method public abstract void close();
+ method public abstract void requestLights(@NonNull android.hardware.lights.LightsRequest);
+ }
+
+ public final class LightsRequest {
+ method @NonNull public java.util.List<android.hardware.lights.LightState> getLightStates();
+ method @NonNull public java.util.List<java.lang.Integer> getLights();
+ }
+
+ public static final class LightsRequest.Builder {
+ ctor public LightsRequest.Builder();
+ method @NonNull public android.hardware.lights.LightsRequest.Builder addLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState);
+ method @NonNull public android.hardware.lights.LightsRequest build();
+ method @NonNull public android.hardware.lights.LightsRequest.Builder clearLight(@NonNull android.hardware.lights.Light);
+ }
+
+}
+
package android.hardware.usb {
public class UsbAccessory implements android.os.Parcelable {
@@ -46508,6 +46560,7 @@ package android.view {
method public int getId();
method public android.view.KeyCharacterMap getKeyCharacterMap();
method public int getKeyboardType();
+ method @NonNull public android.hardware.lights.LightsManager getLightsManager();
method public android.view.InputDevice.MotionRange getMotionRange(int);
method public android.view.InputDevice.MotionRange getMotionRange(int, int);
method public java.util.List<android.view.InputDevice.MotionRange> getMotionRanges();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d7dc6ef45cb4..f8f591457355 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3341,42 +3341,12 @@ package android.hardware.hdmi {
package android.hardware.lights {
- public final class Light implements android.os.Parcelable {
- method public int describeContents();
- method public int getId();
- method public int getOrdinal();
- method public int getType();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR;
- }
-
public final class LightState implements android.os.Parcelable {
- ctor public LightState(@ColorInt int);
- method public int describeContents();
- method @ColorInt public int getColor();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.LightState> CREATOR;
- }
-
- public final class LightsManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public java.util.List<android.hardware.lights.Light> getLights();
- method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightsManager.LightsSession openSession();
- field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8
- }
-
- public final class LightsManager.LightsSession implements java.lang.AutoCloseable {
- method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void close();
- method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void requestLights(@NonNull android.hardware.lights.LightsRequest);
- }
-
- public final class LightsRequest {
+ ctor @Deprecated public LightState(@ColorInt int);
}
- public static final class LightsRequest.Builder {
- ctor public LightsRequest.Builder();
- method @NonNull public android.hardware.lights.LightsRequest build();
- method @NonNull public android.hardware.lights.LightsRequest.Builder clearLight(@NonNull android.hardware.lights.Light);
- method @NonNull public android.hardware.lights.LightsRequest.Builder setLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState);
+ public abstract class LightsManager {
+ field @Deprecated public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8
}
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 694507db6fe7..01417ae3c0ff 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -998,14 +998,6 @@ package android.hardware.input {
}
-package android.hardware.lights {
-
- public final class LightsManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightState getLightState(@NonNull android.hardware.lights.Light);
- }
-
-}
-
package android.hardware.soundtrigger {
public class KeyphraseEnrollmentInfo {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d5e95708a805..8b0c41cb28e3 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -99,6 +99,7 @@ import android.hardware.input.InputManager;
import android.hardware.iris.IIrisService;
import android.hardware.iris.IrisManager;
import android.hardware.lights.LightsManager;
+import android.hardware.lights.SystemLightsManager;
import android.hardware.location.ContextHubManager;
import android.hardware.radio.RadioManager;
import android.hardware.usb.IUsbManager;
@@ -1331,7 +1332,7 @@ public final class SystemServiceRegistry {
@Override
public LightsManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
- return new LightsManager(ctx);
+ return new SystemLightsManager(ctx);
}});
registerService(Context.INCREMENTAL_SERVICE, IncrementalManager.class,
new CachedServiceFetcher<IncrementalManager>() {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index eaa38f3e862c..4743fee3257b 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,6 +25,8 @@ import android.hardware.input.TouchCalibration;
import android.os.CombinedVibrationEffect;
import android.hardware.input.IInputSensorEventListener;
import android.hardware.input.InputSensorInfo;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
import android.os.IBinder;
import android.os.IVibratorStateListener;
import android.os.VibrationEffect;
@@ -127,4 +129,14 @@ interface IInputManager {
void disableSensor(int deviceId, int sensorType);
boolean flushSensor(int deviceId, int sensorType);
+
+ List<Light> getLights(int deviceId);
+
+ LightState getLightState(int deviceId, int lightId);
+
+ void setLightStates(int deviceId, in int[] lightIds, in LightState[] states, in IBinder token);
+
+ void openLightSession(int deviceId, String opPkg, in IBinder token);
+
+ void closeLightSession(int deviceId, in IBinder token);
}
diff --git a/core/java/android/hardware/input/InputDeviceLightsManager.java b/core/java/android/hardware/input/InputDeviceLightsManager.java
new file mode 100644
index 000000000000..a3b91a99fdb7
--- /dev/null
+++ b/core/java/android/hardware/input/InputDeviceLightsManager.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 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.hardware.input;
+
+import android.annotation.NonNull;
+import android.app.ActivityThread;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
+import android.hardware.lights.LightsRequest;
+import android.util.CloseGuard;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.ref.Reference;
+import java.util.List;
+
+/**
+ * LightsManager manages an input device's lights {@link android.hardware.input.Light}.
+ */
+class InputDeviceLightsManager extends LightsManager {
+ private static final String TAG = "InputDeviceLightsManager";
+ private static final boolean DEBUG = false;
+
+ private final InputManager mInputManager;
+
+ // The input device ID.
+ private final int mDeviceId;
+ // Package name
+ private final String mPackageName;
+
+ InputDeviceLightsManager(InputManager inputManager, int deviceId) {
+ super(ActivityThread.currentActivityThread().getSystemContext());
+ mInputManager = inputManager;
+ mDeviceId = deviceId;
+ mPackageName = ActivityThread.currentPackageName();
+ }
+
+ /**
+ * Returns the lights available on the device.
+ *
+ * @return A list of available lights
+ */
+ @Override
+ public @NonNull List<Light> getLights() {
+ return mInputManager.getLights(mDeviceId);
+ }
+
+ /**
+ * Returns the state of a specified light.
+ *
+ * @hide
+ */
+ @Override
+ public @NonNull LightState getLightState(@NonNull Light light) {
+ Preconditions.checkNotNull(light);
+ return mInputManager.getLightState(mDeviceId, light);
+ }
+
+ /**
+ * Creates a new LightsSession that can be used to control the device lights.
+ */
+ @Override
+ public @NonNull LightsSession openSession() {
+ final LightsSession session = new InputDeviceLightsSession();
+ mInputManager.openLightSession(mDeviceId, mPackageName, session.getToken());
+ return session;
+ }
+
+ /**
+ * Encapsulates a session that can be used to control device lights and represents the lifetime
+ * of the requests.
+ */
+ public final class InputDeviceLightsSession extends LightsManager.LightsSession
+ implements AutoCloseable {
+
+ private final CloseGuard mCloseGuard = new CloseGuard();
+ private boolean mClosed = false;
+
+ /**
+ * Instantiated by {@link LightsManager#openSession()}.
+ */
+ private InputDeviceLightsSession() {
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Sends a request to modify the states of multiple lights.
+ *
+ * @param request the settings for lights that should change
+ */
+ @Override
+ public void requestLights(@NonNull LightsRequest request) {
+ Preconditions.checkNotNull(request);
+ Preconditions.checkArgument(!mClosed);
+
+ mInputManager.requestLights(mDeviceId, request, getToken());
+ }
+
+ /**
+ * Closes the session, reverting all changes made through it.
+ */
+ @Override
+ public void close() {
+ if (!mClosed) {
+ mInputManager.closeLightSession(mDeviceId, getToken());
+ mClosed = true;
+ mCloseGuard.close();
+ }
+ Reference.reachabilityFence(this);
+ }
+
+ /** @hide */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8a01c660ebd0..e15d6298d63d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -30,6 +30,10 @@ import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.SensorManager;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
+import android.hardware.lights.LightsRequest;
import android.os.BlockUntrustedTouchesMode;
import android.os.Build;
import android.os.CombinedVibrationEffect;
@@ -1409,7 +1413,7 @@ public final class InputManager {
}
/**
- * Gets a vibrator service associated with an input device, always create a new instance.
+ * Gets a vibrator service associated with an input device, always creates a new instance.
* @return The vibrator, never null.
* @hide
*/
@@ -1418,7 +1422,7 @@ public final class InputManager {
}
/**
- * Gets a vibrator manager service associated with an input device, always create a new
+ * Gets a vibrator manager service associated with an input device, always creates a new
* instance.
* @return The vibrator manager, never null.
* @hide
@@ -1486,10 +1490,8 @@ public final class InputManager {
/**
* Register input device vibrator state listener
- *
- * @hide
*/
- public boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) {
+ boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) {
try {
return mIm.registerVibratorStateListener(deviceId, listener);
} catch (RemoteException ex) {
@@ -1499,10 +1501,8 @@ public final class InputManager {
/**
* Unregister input device vibrator state listener
- *
- * @hide
*/
- public boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) {
+ boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) {
try {
return mIm.unregisterVibratorStateListener(deviceId, listener);
} catch (RemoteException ex) {
@@ -1511,7 +1511,7 @@ public final class InputManager {
}
/**
- * Gets a sensor manager service associated with an input device, always create a new instance.
+ * Gets a sensor manager service associated with an input device, always creates a new instance.
* @return The sensor manager, never null.
* @hide
*/
@@ -1533,6 +1533,86 @@ public final class InputManager {
}
/**
+ * Gets a lights manager associated with an input device, always creates a new instance.
+ * @return The lights manager, never null.
+ * @hide
+ */
+ @NonNull
+ public LightsManager getInputDeviceLightsManager(int deviceId) {
+ return new InputDeviceLightsManager(InputManager.this, deviceId);
+ }
+
+ /**
+ * Gets a list of light objects associated with an input device.
+ * @return The list of lights, never null.
+ */
+ @NonNull List<Light> getLights(int deviceId) {
+ try {
+ return mIm.getLights(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the state of an input device light.
+ * @return the light state
+ */
+ @NonNull LightState getLightState(int deviceId, @NonNull Light light) {
+ try {
+ return mIm.getLightState(deviceId, light.getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request to modify the states of multiple lights.
+ *
+ * @param request the settings for lights that should change
+ */
+ void requestLights(int deviceId, @NonNull LightsRequest request, IBinder token) {
+ try {
+ List<Integer> lightIdList = request.getLights();
+ int[] lightIds = new int[lightIdList.size()];
+ for (int i = 0; i < lightIds.length; i++) {
+ lightIds[i] = lightIdList.get(i);
+ }
+ List<LightState> lightStateList = request.getLightStates();
+ mIm.setLightStates(deviceId, lightIds,
+ lightStateList.toArray(new LightState[lightStateList.size()]),
+ token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Open light session for input device manager
+ *
+ * @param token The token for the light session
+ */
+ void openLightSession(int deviceId, String opPkg, @NonNull IBinder token) {
+ try {
+ mIm.openLightSession(deviceId, opPkg, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Close light session
+ *
+ */
+ void closeLightSession(int deviceId, @NonNull IBinder token) {
+ try {
+ mIm.closeLightSession(deviceId, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Listens for changes in input devices.
*/
public interface InputDeviceListener {
diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java
index da270182052d..7bfff5d3af97 100644
--- a/core/java/android/hardware/lights/Light.java
+++ b/core/java/android/hardware/lights/Light.java
@@ -16,22 +16,56 @@
package android.hardware.lights;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Represents a logical light on the device.
*
- * @hide
*/
-@SystemApi
public final class Light implements Parcelable {
+ // These enum values copy the values from {@link com.android.server.lights.LightsManager}
+ // and the light HAL. Since 0-7 are lights reserved for system use, 8 for microphone light is
+ // defined in {@link android.hardware.lights.LightsManager}, following types are available
+ // through this API.
+ /** Type for lights that indicate microphone usage */
+ public static final int LIGHT_TYPE_MICROPHONE = 8;
+
+ /**
+ * Type for lights that indicate a monochrome color LED light.
+ */
+ public static final int LIGHT_TYPE_INPUT_SINGLE = 9;
+
+ /**
+ * Type for lights that indicate a group of LED lights representing player ID.
+ */
+ public static final int LIGHT_TYPE_INPUT_PLAYER_ID = 10;
+
+ /**
+ * Type for lights that indicate a color LED light.
+ */
+ public static final int LIGHT_TYPE_INPUT_RGB = 11;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"LIGHT_TYPE_"},
+ value = {
+ LIGHT_TYPE_INPUT_PLAYER_ID,
+ LIGHT_TYPE_INPUT_SINGLE,
+ LIGHT_TYPE_INPUT_RGB,
+ })
+ public @interface LightType {}
+
private final int mId;
private final int mOrdinal;
private final int mType;
+ private final String mName;
/**
* Creates a new light with the given data.
@@ -39,15 +73,26 @@ public final class Light implements Parcelable {
* @hide
*/
public Light(int id, int ordinal, int type) {
+ this(id, ordinal, type, "Light");
+ }
+
+ /**
+ * Creates a new light with the given data.
+ *
+ * @hide
+ */
+ public Light(int id, int ordinal, int type, String name) {
mId = id;
mOrdinal = ordinal;
mType = type;
+ mName = name;
}
private Light(@NonNull Parcel in) {
mId = in.readInt();
mOrdinal = in.readInt();
mType = in.readInt();
+ mName = in.readString();
}
/** Implement the Parcelable interface */
@@ -56,6 +101,7 @@ public final class Light implements Parcelable {
dest.writeInt(mId);
dest.writeInt(mOrdinal);
dest.writeInt(mType);
+ dest.writeString(mName);
}
/** Implement the Parcelable interface */
@@ -100,6 +146,14 @@ public final class Light implements Parcelable {
}
/**
+ * Returns the name of the light.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
* Returns the ordinal of the light.
*
* <p>This is a sort key that represents the physical order of lights on the device with the
diff --git a/core/java/android/hardware/lights/LightState.java b/core/java/android/hardware/lights/LightState.java
index cd39e6df91a9..650b383eeb0f 100644
--- a/core/java/android/hardware/lights/LightState.java
+++ b/core/java/android/hardware/lights/LightState.java
@@ -32,36 +32,93 @@ import android.os.Parcelable;
* will be converted to only a brightness value and that will be used for the light's single
* channel.
*
- * @hide
*/
-@SystemApi
public final class LightState implements Parcelable {
private final int mColor;
+ private final int mPlayerId;
/**
- * Creates a new LightState with the desired color and intensity.
+ * Creates a new LightState with the desired color and intensity, for a light type
+ * of RBG color or monochrome color.
*
* @param color the desired color and intensity in ARGB format.
+ * @deprecated this has been replaced with {@link android.hardware.lights.LightState#forColor }
+ * @hide
*/
+ @Deprecated
+ @SystemApi
public LightState(@ColorInt int color) {
+ this(color, 0);
+ }
+
+ /**
+ * Creates a new LightState with the desired color and intensity, and the player Id.
+ * Player Id will only be applied on Light type
+ * {@link android.hardware.lights.Light#LIGHT_TYPE_INPUT_PLAYER_ID}
+ *
+ * @param color the desired color and intensity in ARGB format.
+ * @hide
+ */
+ public LightState(@ColorInt int color, int playerId) {
mColor = color;
+ mPlayerId = playerId;
+ }
+
+ /**
+ * Creates a new LightState with the desired color and intensity, for a light type
+ * of RBG color or single monochrome color.
+ *
+ * @param color the desired color and intensity in ARGB format.
+ * @return The LightState object contains the color.
+ */
+ @NonNull
+ public static LightState forColor(@ColorInt int color) {
+ return new LightState(color, 0);
+ }
+
+ /**
+ * Creates a new LightState with the desired player id, for a light of type
+ * {@link android.hardware.lights.Light#LIGHT_TYPE_INPUT_PLAYER_ID}.
+ *
+ * @param playerId the desired player id.
+ * @return The LightState object contains the player id.
+ */
+ @NonNull
+ public static LightState forPlayerId(int playerId) {
+ return new LightState(0, playerId);
}
+ /**
+ * Creates a new LightState from a parcel object.
+ */
private LightState(@NonNull Parcel in) {
mColor = in.readInt();
+ mPlayerId = in.readInt();
}
/**
- * Return the color and intensity associated with this LightState.
- * @return the color and intensity in ARGB format. The A channel is ignored.
+ * Returns the color and intensity associated with this LightState.
+ * @return the color and intensity in ARGB format. The A channel is ignored. return 0 when
+ * calling LightsManager.getLightState with LIGHT_TYPE_INPUT_PLAYER_ID.
*/
public @ColorInt int getColor() {
return mColor;
}
+ /**
+ * Returns the player ID associated with this LightState for Light type
+ * {@link android.hardware.lights.Light#LIGHT_TYPE_INPUT_PLAYER_ID},
+ * or 0 for other types.
+ * @return the player ID.
+ */
+ public int getPlayerId() {
+ return mPlayerId;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mColor);
+ dest.writeInt(mPlayerId);
}
@Override
@@ -69,6 +126,12 @@ public final class LightState implements Parcelable {
return 0;
}
+ @Override
+ public String toString() {
+ return "LightState{Color=0x" + Integer.toHexString(mColor) + ", PlayerId="
+ + mPlayerId + "}";
+ }
+
public static final @NonNull Parcelable.Creator<LightState> CREATOR =
new Parcelable.Creator<LightState>() {
public LightState createFromParcel(Parcel in) {
diff --git a/core/java/android/hardware/lights/LightsManager.java b/core/java/android/hardware/lights/LightsManager.java
index 33e5fcaf2abb..8fd56db33c4b 100644
--- a/core/java/android/hardware/lights/LightsManager.java
+++ b/core/java/android/hardware/lights/LightsManager.java
@@ -16,43 +16,38 @@
package android.hardware.lights;
-import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.TestApi;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.CloseGuard;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.Reference;
import java.util.List;
/**
* The LightsManager class allows control over device lights.
*
- * @hide
*/
-@SystemApi
@SystemService(Context.LIGHTS_SERVICE)
-public final class LightsManager {
+public abstract class LightsManager {
private static final String TAG = "LightsManager";
+ @NonNull private final Context mContext;
// These enum values copy the values from {@link com.android.server.lights.LightsManager}
// and the light HAL. Since 0-7 are lights reserved for system use, only the microphone light
- // is available through this API.
- /** Type for lights that indicate microphone usage */
+ // and following types are available through this API.
+ /** Type for lights that indicate microphone usage
+ * @deprecated this has been moved to {@link android.hardware.lights.Light }
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
public static final int LIGHT_TYPE_MICROPHONE = 8;
/** @hide */
@@ -63,28 +58,11 @@ public final class LightsManager {
})
public @interface LightType {}
- @NonNull private final Context mContext;
- @NonNull private final ILightsManager mService;
-
/**
- * Creates a LightsManager.
- *
- * @hide
+ * @hide to prevent subclassing from outside of the framework
*/
- public LightsManager(@NonNull Context context) throws ServiceNotFoundException {
- this(context, ILightsManager.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.LIGHTS_SERVICE)));
- }
-
- /**
- * Creates a LightsManager with a provided service implementation.
- *
- * @hide
- */
- @VisibleForTesting
- public LightsManager(@NonNull Context context, @NonNull ILightsManager service) {
+ public LightsManager(Context context) {
mContext = Preconditions.checkNotNull(context);
- mService = Preconditions.checkNotNull(service);
}
/**
@@ -92,112 +70,44 @@ public final class LightsManager {
*
* @return A list of available lights
*/
- @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
- public @NonNull List<Light> getLights() {
- try {
- return mService.getLights();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ public @NonNull abstract List<Light> getLights();
/**
* Returns the state of a specified light.
*
- * @hide
*/
- @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
- @TestApi
- public @NonNull LightState getLightState(@NonNull Light light) {
- Preconditions.checkNotNull(light);
- try {
- return mService.getLightState(light.getId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ public abstract @NonNull LightState getLightState(@NonNull Light light);
/**
* Creates a new LightsSession that can be used to control the device lights.
*/
- @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
- public @NonNull LightsSession openSession() {
- try {
- final LightsSession session = new LightsSession();
- mService.openSession(session.mToken);
- return session;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ public abstract @NonNull LightsSession openSession();
/**
* Encapsulates a session that can be used to control device lights and represents the lifetime
* of the requests.
*/
- public final class LightsSession implements AutoCloseable {
-
+ public abstract static class LightsSession implements AutoCloseable {
private final IBinder mToken = new Binder();
-
- private final CloseGuard mCloseGuard = new CloseGuard();
- private boolean mClosed = false;
-
- /**
- * Instantiated by {@link LightsManager#openSession()}.
- */
- @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
- private LightsSession() {
- mCloseGuard.open("close");
- }
-
/**
* Sends a request to modify the states of multiple lights.
*
- * <p>This method only controls lights that aren't overridden by higher-priority sessions.
- * Additionally, lights not controlled by this session can be controlled by lower-priority
- * sessions.
- *
* @param request the settings for lights that should change
*/
- @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
- public void requestLights(@NonNull LightsRequest request) {
- Preconditions.checkNotNull(request);
- if (!mClosed) {
- try {
- mService.setLightStates(mToken, request.mLightIds, request.mLightStates);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
+ public abstract void requestLights(@NonNull LightsRequest request);
- /**
- * Closes the session, reverting all changes made through it.
- */
- @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
@Override
- public void close() {
- if (!mClosed) {
- try {
- mService.closeSession(mToken);
- mClosed = true;
- mCloseGuard.close();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- Reference.reachabilityFence(this);
- }
+ public abstract void close();
- /** @hide */
- @Override
- protected void finalize() throws Throwable {
- try {
- mCloseGuard.warnIfOpen();
- close();
- } finally {
- super.finalize();
- }
+ /**
+ * Get the token of a light session.
+ *
+ * @return Binder token of the light session.
+ * @hide
+ */
+ public @NonNull IBinder getToken() {
+ return mToken;
}
}
+
}
diff --git a/core/java/android/hardware/lights/LightsRequest.java b/core/java/android/hardware/lights/LightsRequest.java
index a318992c35ee..2626a461aaf5 100644
--- a/core/java/android/hardware/lights/LightsRequest.java
+++ b/core/java/android/hardware/lights/LightsRequest.java
@@ -17,17 +17,17 @@
package android.hardware.lights;
import android.annotation.NonNull;
-import android.annotation.SystemApi;
import android.util.SparseArray;
import com.android.internal.util.Preconditions;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
/**
* Encapsulates a request to modify the state of multiple lights.
*
- * @hide
*/
-@SystemApi
public final class LightsRequest {
/** Visible to {@link LightsManager.Session}. */
@@ -50,6 +50,30 @@ public final class LightsRequest {
}
/**
+ * Get a list of Light as ids. The ids will returned in same order as the lights passed
+ * in Builder.
+ *
+ * @return List of light ids
+ */
+ public @NonNull List<Integer> getLights() {
+ List<Integer> lightList = new ArrayList<Integer>(mLightIds.length);
+ for (int i = 0; i < mLightIds.length; i++) {
+ lightList.add(mLightIds[i]);
+ }
+ return lightList;
+ }
+
+ /**
+ * Get a list of LightState. The states will be returned in same order as the light states
+ * passed in Builder.
+ *
+ * @return List of light states
+ */
+ public @NonNull List<LightState> getLightStates() {
+ return Arrays.asList(mLightStates);
+ }
+
+ /**
* Builder for creating device light change requests.
*/
public static final class Builder {
@@ -62,7 +86,7 @@ public final class LightsRequest {
* @param light the light to modify
* @param state the desired color and intensity of the light
*/
- public @NonNull Builder setLight(@NonNull Light light, @NonNull LightState state) {
+ public @NonNull Builder addLight(@NonNull Light light, @NonNull LightState state) {
Preconditions.checkNotNull(light);
Preconditions.checkNotNull(state);
mChanges.put(light.getId(), state);
diff --git a/core/java/android/hardware/lights/SystemLightsManager.java b/core/java/android/hardware/lights/SystemLightsManager.java
new file mode 100644
index 000000000000..726a61359c01
--- /dev/null
+++ b/core/java/android/hardware/lights/SystemLightsManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 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.hardware.lights;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.lights.LightsManager.LightsSession;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.CloseGuard;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.ref.Reference;
+import java.util.List;
+
+/**
+ * The LightsManager class allows control over device lights.
+ *
+ * @hide
+ */
+public final class SystemLightsManager extends LightsManager {
+ private static final String TAG = "LightsManager";
+
+ @NonNull private final ILightsManager mService;
+
+ /**
+ * Creates a SystemLightsManager.
+ *
+ * @hide
+ */
+ public SystemLightsManager(@NonNull Context context) throws ServiceNotFoundException {
+ this(context, ILightsManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.LIGHTS_SERVICE)));
+ }
+
+ /**
+ * Creates a SystemLightsManager with a provided service implementation.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public SystemLightsManager(@NonNull Context context, @NonNull ILightsManager service) {
+ super(context);
+ mService = Preconditions.checkNotNull(service);
+ }
+
+ /**
+ * Returns the lights available on the device.
+ *
+ * @return A list of available lights
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+ @Override
+ public @NonNull List<Light> getLights() {
+ try {
+ return mService.getLights();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the state of a specified light.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+ @Override
+ public @NonNull LightState getLightState(@NonNull Light light) {
+ Preconditions.checkNotNull(light);
+ try {
+ return mService.getLightState(light.getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a new LightsSession that can be used to control the device lights.
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+ @Override
+ public @NonNull LightsSession openSession() {
+ try {
+ final LightsSession session = new SystemLightsSession();
+ mService.openSession(session.getToken());
+ return session;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Encapsulates a session that can be used to control device lights and represents the lifetime
+ * of the requests.
+ */
+ public final class SystemLightsSession extends LightsManager.LightsSession
+ implements AutoCloseable {
+
+ private final CloseGuard mCloseGuard = new CloseGuard();
+ private boolean mClosed = false;
+
+ /**
+ * Instantiated by {@link LightsManager#openSession()}.
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+ private SystemLightsSession() {
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Sends a request to modify the states of multiple lights.
+ *
+ * <p>This method only controls lights that aren't overridden by higher-priority sessions.
+ * Additionally, lights not controlled by this session can be controlled by lower-priority
+ * sessions.
+ *
+ * @param request the settings for lights that should change
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+ @Override
+ public void requestLights(@NonNull LightsRequest request) {
+ Preconditions.checkNotNull(request);
+ if (!mClosed) {
+ try {
+ mService.setLightStates(getToken(), request.mLightIds, request.mLightStates);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Closes the session, reverting all changes made through it.
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS)
+ @Override
+ public void close() {
+ if (!mClosed) {
+ try {
+ mService.closeSession(getToken());
+ mClosed = true;
+ mCloseGuard.close();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ Reference.reachabilityFence(this);
+ }
+
+ /** @hide */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 59e493191711..fafb885c58e0 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -26,6 +26,7 @@ import android.hardware.Battery;
import android.hardware.SensorManager;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
+import android.hardware.lights.LightsManager;
import android.os.Build;
import android.os.NullVibrator;
import android.os.Parcel;
@@ -89,6 +90,9 @@ public final class InputDevice implements Parcelable {
@GuardedBy("mMotionRanges")
private Battery mBattery;
+ @GuardedBy("mMotionRanges")
+ private LightsManager mLightsManager;
+
/**
* A mask for input source classes.
*
@@ -859,6 +863,21 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Gets the lights manager associated with the device, if there is one.
+ * Even if the device does not have lights, the result is never null.
+ * Use {@link LightsManager#getLights} to determine whether any lights is
+ * present.
+ *
+ * @return The lights manager associated with the device, never null.
+ */
+ public @NonNull LightsManager getLightsManager() {
+ if (mLightsManager == null) {
+ mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId);
+ }
+ return mLightsManager;
+ }
+
+ /**
* Gets the sensor manager service associated with the input device.
* Even if the device does not have a sensor, the result is never null.
* Use {@link SensorManager#getSensorList} to get a full list of all supported sensors.
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
new file mode 100644
index 000000000000..412b36713fa2
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2021 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.hardware.input;
+
+import static android.hardware.lights.LightsRequest.Builder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.view.InputDevice;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link InputDeviceLightsManager}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:InputDeviceLightsManagerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class InputDeviceLightsManagerTest {
+ private static final String TAG = "InputDeviceLightsManagerTest";
+
+ private static final int DEVICE_ID = 1000;
+ private static final int PLAYER_ID = 3;
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ private InputManager mInputManager;
+
+ @Mock private IInputManager mIInputManagerMock;
+
+ @Before
+ public void setUp() throws Exception {
+ when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
+
+ when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
+ createInputDevice(DEVICE_ID));
+
+ mInputManager = InputManager.resetInstance(mIInputManagerMock);
+
+ ArrayMap<Integer, LightState> lightStatesById = new ArrayMap<>();
+ doAnswer(invocation -> {
+ final int[] lightIds = (int[]) invocation.getArguments()[1];
+ final LightState[] lightStates =
+ (LightState[]) invocation.getArguments()[2];
+ for (int i = 0; i < lightIds.length; i++) {
+ lightStatesById.put(lightIds[i], lightStates[i]);
+ }
+ return null;
+ }).when(mIInputManagerMock).setLightStates(eq(DEVICE_ID),
+ any(int[].class), any(LightState[].class), any(IBinder.class));
+
+ doAnswer(invocation -> {
+ int lightId = (int) invocation.getArguments()[1];
+ if (lightStatesById.containsKey(lightId)) {
+ return lightStatesById.get(lightId);
+ }
+ return new LightState(0);
+ }).when(mIInputManagerMock).getLightState(eq(DEVICE_ID), anyInt());
+ }
+
+ @After
+ public void tearDown() {
+ InputManager.clearInstance();
+ }
+
+ private InputDevice createInputDevice(int id) {
+ return new InputDevice(id, 0 /* generation */, 0 /* controllerNumber */, "name",
+ 0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */,
+ 0 /* sources */, 0 /* keyboardType */, null /* keyCharacterMap */,
+ false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */,
+ false /* hasSensor */, false /* hasBattery */);
+ }
+
+ private void mockLights(Light[] lights) throws Exception {
+ // Mock the Lights returned form InputManagerService
+ when(mIInputManagerMock.getLights(eq(DEVICE_ID))).thenReturn(
+ new ArrayList(Arrays.asList(lights)));
+ }
+
+ @Test
+ public void testGetInputDeviceLights() throws Exception {
+ InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
+ assertNotNull(device);
+
+ Light[] mockedLights = {
+ new Light(1 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_SINGLE),
+ new Light(2 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB),
+ new Light(3 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_PLAYER_ID)
+ };
+ mockLights(mockedLights);
+
+ LightsManager lightsManager = device.getLightsManager();
+ List<Light> lights = lightsManager.getLights();
+ verify(mIInputManagerMock).getLights(eq(DEVICE_ID));
+ assertEquals(lights, Arrays.asList(mockedLights));
+ }
+
+ @Test
+ public void testControlMultipleLights() throws Exception {
+ InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
+ assertNotNull(device);
+
+ Light[] mockedLights = {
+ new Light(1 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB),
+ new Light(2 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB),
+ new Light(3 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB),
+ new Light(4 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB)
+ };
+ mockLights(mockedLights);
+
+ LightsManager lightsManager = device.getLightsManager();
+ List<Light> lightList = lightsManager.getLights();
+ LightState[] states = new LightState[]{new LightState(0xf1), new LightState(0xf2),
+ new LightState(0xf3)};
+ // Open a session to request turn 3/4 lights on:
+ LightsManager.LightsSession session = lightsManager.openSession();
+ session.requestLights(new Builder()
+ .addLight(lightsManager.getLights().get(0), states[0])
+ .addLight(lightsManager.getLights().get(1), states[1])
+ .addLight(lightsManager.getLights().get(2), states[2])
+ .build());
+ IBinder token = session.getToken();
+
+ verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID),
+ any(String.class), eq(token));
+ verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}),
+ eq(states), eq(token));
+
+ // Then all 3 should turn on.
+ assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getColor())
+ .isEqualTo(0xf1);
+ assertThat(lightsManager.getLightState(lightsManager.getLights().get(1)).getColor())
+ .isEqualTo(0xf2);
+ assertThat(lightsManager.getLightState(lightsManager.getLights().get(2)).getColor())
+ .isEqualTo(0xf3);
+
+ // And the 4th should remain off.
+ assertThat(lightsManager.getLightState(lightsManager.getLights().get(3)).getColor())
+ .isEqualTo(0x00);
+
+ // close session
+ session.close();
+ verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token));
+ }
+
+ @Test
+ public void testControlPlayerIdLight() throws Exception {
+ InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
+ assertNotNull(device);
+
+ Light[] mockedLights = {
+ new Light(1 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_PLAYER_ID),
+ new Light(2 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_SINGLE),
+ new Light(3 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB),
+ };
+ mockLights(mockedLights);
+
+ LightsManager lightsManager = device.getLightsManager();
+ List<Light> lightList = lightsManager.getLights();
+ LightState[] states = new LightState[]{new LightState(0xf1, PLAYER_ID)};
+ // Open a session to request set Player ID light:
+ LightsManager.LightsSession session = lightsManager.openSession();
+ session.requestLights(new Builder()
+ .addLight(lightsManager.getLights().get(0), states[0])
+ .build());
+ IBinder token = session.getToken();
+
+ verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID),
+ any(String.class), eq(token));
+ verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1}),
+ eq(states), eq(token));
+
+ // Verify the light state
+ assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getColor())
+ .isEqualTo(0xf1);
+ assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getPlayerId())
+ .isEqualTo(PLAYER_ID);
+
+ // close session
+ session.close();
+ verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token));
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4e974112a5c3..096cb0743670 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.input;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -50,6 +51,8 @@ import android.hardware.input.InputManagerInternal.LidSwitchCallback;
import android.hardware.input.InputSensorInfo;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.TouchCalibration;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Bundle;
@@ -232,6 +235,13 @@ public class InputManagerService extends IInputManager.Stub
// List of vibrator states by device id.
@GuardedBy("mVibratorLock")
private final SparseBooleanArray mIsVibrating = new SparseBooleanArray();
+ private Object mLightLock = new Object();
+ // State for light tokens. A light token marks a lights manager session, it is generated
+ // by light session open() and deleted in session close().
+ // When lights session requests light states, the token will be used to find the light session.
+ @GuardedBy("mLightLock")
+ private final ArrayMap<IBinder, LightSession> mLightSessions =
+ new ArrayMap<IBinder, LightSession>();
// State for lid switch
private final Object mLidSwitchLock = new Object();
@@ -298,6 +308,12 @@ public class InputManagerService extends IInputManager.Stub
private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
private static native int nativeGetBatteryCapacity(long ptr, int deviceId);
private static native int nativeGetBatteryStatus(long ptr, int deviceId);
+ private static native List<Light> nativeGetLights(long ptr, int deviceId);
+ private static native int nativeGetLightPlayerId(long ptr, int deviceId, int lightId);
+ private static native int nativeGetLightColor(long ptr, int deviceId, int lightId);
+ private static native void nativeSetLightPlayerId(long ptr, int deviceId, int lightId,
+ int playerId);
+ private static native void nativeSetLightColor(long ptr, int deviceId, int lightId, int color);
private static native void nativeReloadKeyboardLayouts(long ptr);
private static native void nativeReloadDeviceAliases(long ptr);
private static native String nativeDump(long ptr);
@@ -2246,6 +2262,151 @@ public class InputManagerService extends IInputManager.Stub
}
}
+ /**
+ * LightSession represents a light session for lights manager.
+ */
+ private final class LightSession implements DeathRecipient {
+ private final int mDeviceId;
+ private final IBinder mToken;
+ private final String mOpPkg;
+ // The light ids and states that are requested by the light seesion
+ private int[] mLightIds;
+ private LightState[] mLightStates;
+
+ LightSession(int deviceId, String opPkg, IBinder token) {
+ mDeviceId = deviceId;
+ mOpPkg = opPkg;
+ mToken = token;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Light token died.");
+ }
+ synchronized (mLightLock) {
+ closeLightSession(mDeviceId, mToken);
+ mLightSessions.remove(mToken);
+ }
+ }
+ }
+
+ /**
+ * Returns the lights available for apps to control on the specified input device.
+ * Only lights that aren't reserved for system use are available to apps.
+ */
+ @Override // Binder call
+ public List<Light> getLights(int deviceId) {
+ return nativeGetLights(mPtr, deviceId);
+ }
+
+ /**
+ * Set specified light state with for a specific input device.
+ */
+ private void setLightStateInternal(int deviceId, Light light, LightState lightState) {
+ Preconditions.checkNotNull(light, "light does not exist");
+ if (DEBUG) {
+ Slog.d(TAG, "setLightStateInternal device " + deviceId + " light " + light
+ + "lightState " + lightState);
+ }
+ if (light.getType() == Light.LIGHT_TYPE_INPUT_PLAYER_ID) {
+ nativeSetLightPlayerId(mPtr, deviceId, light.getId(), lightState.getPlayerId());
+ } else if (light.getType() == Light.LIGHT_TYPE_INPUT_SINGLE
+ || light.getType() == Light.LIGHT_TYPE_INPUT_RGB) {
+ // Set ARGB format color to input device light
+ // Refer to https://developer.android.com/reference/kotlin/android/graphics/Color
+ nativeSetLightColor(mPtr, deviceId, light.getId(), lightState.getColor());
+ } else {
+ Slog.e(TAG, "setLightStates for unsupported light type " + light.getType());
+ }
+ }
+
+ /**
+ * Set multiple light states with multiple light ids for a specific input device.
+ */
+ private void setLightStatesInternal(int deviceId, int[] lightIds, LightState[] lightStates) {
+ final List<Light> lights = nativeGetLights(mPtr, deviceId);
+ SparseArray<Light> lightArray = new SparseArray<>();
+ for (int i = 0; i < lights.size(); i++) {
+ lightArray.put(lights.get(i).getId(), lights.get(i));
+ }
+ for (int i = 0; i < lightIds.length; i++) {
+ if (lightArray.contains(lightIds[i])) {
+ setLightStateInternal(deviceId, lightArray.get(lightIds[i]), lightStates[i]);
+ }
+ }
+ }
+
+ /**
+ * Set states for multiple lights for an opened light session.
+ */
+ @Override
+ public void setLightStates(int deviceId, int[] lightIds, LightState[] lightStates,
+ IBinder token) {
+ Preconditions.checkArgument(lightIds.length == lightStates.length,
+ "lights and light states are not same length");
+ synchronized (mLightLock) {
+ LightSession lightSession = mLightSessions.get(token);
+ Preconditions.checkArgument(lightSession != null, "not registered");
+ Preconditions.checkState(lightSession.mDeviceId == deviceId, "Incorrect device ID");
+ lightSession.mLightIds = lightIds.clone();
+ lightSession.mLightStates = lightStates.clone();
+ if (DEBUG) {
+ Slog.d(TAG, "setLightStates for " + lightSession.mOpPkg + " device " + deviceId);
+ }
+ }
+ setLightStatesInternal(deviceId, lightIds, lightStates);
+ }
+
+ @Override
+ public @Nullable LightState getLightState(int deviceId, int lightId) {
+ synchronized (mLightLock) {
+ int color = nativeGetLightColor(mPtr, deviceId, lightId);
+ int playerId = nativeGetLightPlayerId(mPtr, deviceId, lightId);
+
+ return new LightState(color, playerId);
+ }
+ }
+
+ @Override
+ public void openLightSession(int deviceId, String opPkg, IBinder token) {
+ Preconditions.checkNotNull(token);
+ synchronized (mLightLock) {
+ Preconditions.checkState(mLightSessions.get(token) == null, "already registered");
+ LightSession lightSession = new LightSession(deviceId, opPkg, token);
+ try {
+ token.linkToDeath(lightSession, 0);
+ } catch (RemoteException ex) {
+ // give up
+ ex.rethrowAsRuntimeException();
+ }
+ mLightSessions.put(token, lightSession);
+ if (DEBUG) {
+ Slog.d(TAG, "Open light session for " + opPkg + " device " + deviceId);
+ }
+ }
+ }
+
+ @Override
+ public void closeLightSession(int deviceId, IBinder token) {
+ Preconditions.checkNotNull(token);
+ synchronized (mLightLock) {
+ LightSession lightSession = mLightSessions.get(token);
+ Preconditions.checkState(lightSession != null, "not registered");
+ // Turn off the lights that were previously requested by the session to be closed.
+ Arrays.fill(lightSession.mLightStates, new LightState(0));
+ setLightStatesInternal(deviceId, lightSession.mLightIds,
+ lightSession.mLightStates);
+ mLightSessions.remove(token);
+ // If any other session is still pending with light request, apply the first session's
+ // request.
+ if (!mLightSessions.isEmpty()) {
+ LightSession nextSession = mLightSessions.valueAt(0);
+ setLightStatesInternal(deviceId, nextSession.mLightIds, nextSession.mLightStates);
+ }
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 643503d18bed..21d57d8c189f 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -158,6 +158,20 @@ static struct {
static struct {
jclass clazz;
jmethodID constructor;
+ jfieldID lightTypeSingle;
+ jfieldID lightTypePlayerId;
+ jfieldID lightTypeRgb;
+} gLightClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID constructor;
+ jmethodID add;
+} gArrayListClassInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID constructor;
jmethodID keyAt;
jmethodID valueAt;
jmethodID size;
@@ -1923,6 +1937,79 @@ static jintArray nativeGetVibratorIds(JNIEnv* env, jclass clazz, jlong ptr, jint
return vibIdArray;
}
+static jobject nativeGetLights(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ jobject jLights = env->NewObject(gArrayListClassInfo.clazz, gArrayListClassInfo.constructor);
+
+ std::vector<int> lightIds = im->getInputManager()->getReader()->getLightIds(deviceId);
+
+ for (size_t i = 0; i < lightIds.size(); i++) {
+ const InputDeviceLightInfo* lightInfo =
+ im->getInputManager()->getReader()->getLightInfo(deviceId, lightIds[i]);
+ if (lightInfo == nullptr) {
+ ALOGW("Failed to get input device %d light info for id %d", deviceId, lightIds[i]);
+ continue;
+ }
+
+ jint jTypeId = 0;
+ if (lightInfo->type == InputDeviceLightType::SINGLE) {
+ jTypeId =
+ env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeSingle);
+ } else if (lightInfo->type == InputDeviceLightType::PLAYER_ID) {
+ jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
+ gLightClassInfo.lightTypePlayerId);
+ } else if (lightInfo->type == InputDeviceLightType::RGB ||
+ lightInfo->type == InputDeviceLightType::MULTI_COLOR) {
+ jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeRgb);
+ } else {
+ ALOGW("Unknown light type %d", lightInfo->type);
+ continue;
+ }
+ ScopedLocalRef<jobject>
+ lightObj(env,
+ env->NewObject(gLightClassInfo.clazz, gLightClassInfo.constructor,
+ (jint)lightInfo->id, (jint)lightInfo->ordinal, jTypeId,
+ env->NewStringUTF(lightInfo->name.c_str())));
+ // Add light object to list
+ env->CallBooleanMethod(jLights, gArrayListClassInfo.add, lightObj.get());
+ }
+
+ return jLights;
+}
+
+static jint nativeGetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+ jint lightId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ std::optional<int32_t> ret =
+ im->getInputManager()->getReader()->getLightPlayerId(deviceId, lightId);
+
+ return static_cast<jint>(ret.value_or(0));
+}
+
+static jint nativeGetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+ jint lightId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ std::optional<int32_t> ret =
+ im->getInputManager()->getReader()->getLightColor(deviceId, lightId);
+ return static_cast<jint>(ret.value_or(0));
+}
+
+static void nativeSetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+ jint lightId, jint playerId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->getInputManager()->getReader()->setLightPlayerId(deviceId, lightId, playerId);
+}
+
+static void nativeSetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+ jint lightId, jint color) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->getInputManager()->getReader()->setLightColor(deviceId, lightId, color);
+}
+
static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -2192,6 +2279,11 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
{"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
{"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
+ {"nativeGetLights", "(JI)Ljava/util/List;", (void*)nativeGetLights},
+ {"nativeGetLightPlayerId", "(JII)I", (void*)nativeGetLightPlayerId},
+ {"nativeGetLightColor", "(JII)I", (void*)nativeGetLightColor},
+ {"nativeSetLightPlayerId", "(JIII)V", (void*)nativeSetLightPlayerId},
+ {"nativeSetLightColor", "(JIII)V", (void*)nativeSetLightColor},
{"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity},
{"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus},
{"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
@@ -2386,6 +2478,27 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gTouchCalibrationClassInfo.getAffineTransform, gTouchCalibrationClassInfo.clazz,
"getAffineTransform", "()[F");
+ // Light
+ FIND_CLASS(gLightClassInfo.clazz, "android/hardware/lights/Light");
+ gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz));
+ GET_METHOD_ID(gLightClassInfo.constructor, gLightClassInfo.clazz, "<init>",
+ "(IIILjava/lang/String;)V");
+
+ gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz));
+ gLightClassInfo.lightTypeSingle =
+ env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT_SINGLE", "I");
+ gLightClassInfo.lightTypePlayerId =
+ env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT_PLAYER_ID", "I");
+ gLightClassInfo.lightTypeRgb =
+ env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT_RGB", "I");
+
+ // ArrayList
+ FIND_CLASS(gArrayListClassInfo.clazz, "java/util/ArrayList");
+ gArrayListClassInfo.clazz = jclass(env->NewGlobalRef(gArrayListClassInfo.clazz));
+ GET_METHOD_ID(gArrayListClassInfo.constructor, gArrayListClassInfo.clazz, "<init>", "()V");
+ GET_METHOD_ID(gArrayListClassInfo.add, gArrayListClassInfo.clazz, "add",
+ "(Ljava/lang/Object;)Z");
+
// SparseArray
FIND_CLASS(gSparseArrayClassInfo.clazz, "android/util/SparseArray");
gSparseArrayClassInfo.clazz = jclass(env->NewGlobalRef(gSparseArrayClassInfo.clazz));
diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
index 3e9709d55268..f0a9a0089ec9 100644
--- a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
@@ -31,6 +31,7 @@ import android.hardware.light.ILights;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
+import android.hardware.lights.SystemLightsManager;
import android.os.Looper;
import androidx.test.filters.SmallTest;
@@ -93,7 +94,7 @@ public class LightsServiceTest {
@Test
public void testGetLights_filtersSystemLights() {
LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper());
- LightsManager manager = new LightsManager(mContext, service.mManagerService);
+ LightsManager manager = new SystemLightsManager(mContext, service.mManagerService);
// When lights are listed, only the 4 MICROPHONE lights should be visible.
assertThat(manager.getLights().size()).isEqualTo(4);
@@ -102,14 +103,14 @@ public class LightsServiceTest {
@Test
public void testControlMultipleLights() {
LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper());
- LightsManager manager = new LightsManager(mContext, service.mManagerService);
+ LightsManager manager = new SystemLightsManager(mContext, service.mManagerService);
// When the session requests to turn 3/4 lights on:
LightsManager.LightsSession session = manager.openSession();
session.requestLights(new Builder()
- .setLight(manager.getLights().get(0), new LightState(0xf1))
- .setLight(manager.getLights().get(1), new LightState(0xf2))
- .setLight(manager.getLights().get(2), new LightState(0xf3))
+ .addLight(manager.getLights().get(0), new LightState(0xf1))
+ .addLight(manager.getLights().get(1), new LightState(0xf2))
+ .addLight(manager.getLights().get(2), new LightState(0xf3))
.build());
// Then all 3 should turn on.
@@ -122,9 +123,9 @@ public class LightsServiceTest {
}
@Test
- public void testControlLights_onlyEffectiveForLifetimeOfClient() {
+ public void testControlLights_onlyEffectiveForLifetimeOfClient() throws Exception {
LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper());
- LightsManager manager = new LightsManager(mContext, service.mManagerService);
+ LightsManager manager = new SystemLightsManager(mContext, service.mManagerService);
Light micLight = manager.getLights().get(0);
// The light should begin by being off.
@@ -132,38 +133,41 @@ public class LightsServiceTest {
// When a session commits changes:
LightsManager.LightsSession session = manager.openSession();
- session.requestLights(new Builder().setLight(micLight, new LightState(GREEN)).build());
+ session.requestLights(new Builder().addLight(micLight, new LightState(GREEN)).build());
// Then the light should turn on.
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(GREEN);
// When the session goes away:
session.close();
+
// Then the light should turn off.
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(TRANSPARENT);
}
@Test
- public void testControlLights_firstCallerWinsContention() {
+ public void testControlLights_firstCallerWinsContention() throws Exception {
LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper());
- LightsManager manager = new LightsManager(mContext, service.mManagerService);
+ LightsManager manager = new SystemLightsManager(mContext, service.mManagerService);
Light micLight = manager.getLights().get(0);
LightsManager.LightsSession session1 = manager.openSession();
LightsManager.LightsSession session2 = manager.openSession();
// When session1 and session2 both request the same light:
- session1.requestLights(new Builder().setLight(micLight, new LightState(BLUE)).build());
- session2.requestLights(new Builder().setLight(micLight, new LightState(WHITE)).build());
+ session1.requestLights(new Builder().addLight(micLight, new LightState(BLUE)).build());
+ session2.requestLights(new Builder().addLight(micLight, new LightState(WHITE)).build());
// Then session1 should win because it was created first.
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(BLUE);
// When session1 goes away:
session1.close();
+
// Then session2 should have its request go into effect.
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(WHITE);
// When session2 goes away:
session2.close();
+
// Then the light should turn off because there are no more sessions.
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0);
}
@@ -171,12 +175,12 @@ public class LightsServiceTest {
@Test
public void testClearLight() {
LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper());
- LightsManager manager = new LightsManager(mContext, service.mManagerService);
+ LightsManager manager = new SystemLightsManager(mContext, service.mManagerService);
Light micLight = manager.getLights().get(0);
// When the session turns a light on:
LightsManager.LightsSession session = manager.openSession();
- session.requestLights(new Builder().setLight(micLight, new LightState(WHITE)).build());
+ session.requestLights(new Builder().addLight(micLight, new LightState(WHITE)).build());
// And then the session clears it again:
session.requestLights(new Builder().clearLight(micLight).build());