Merge changes from topic "LightsManager" into sc-dev
* changes: Support input device lights manager feature in frameworks. Add SET_REPORT data output to hid commandline tool.
This commit is contained in:
@@ -113,9 +113,10 @@ void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) {
|
||||
checkAndClearException(env, "onDeviceGetReport");
|
||||
}
|
||||
|
||||
void DeviceCallback::onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data) {
|
||||
void DeviceCallback::onDeviceOutput(uint8_t eventId, uint8_t rType,
|
||||
const std::vector<uint8_t>& data) {
|
||||
JNIEnv* env = getJNIEnv();
|
||||
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput, rType,
|
||||
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput, eventId, rType,
|
||||
toJbyteArray(env, data).get());
|
||||
checkAndClearException(env, "onDeviceOutput");
|
||||
}
|
||||
@@ -261,6 +262,7 @@ int Device::handleEvents(int events) {
|
||||
ALOGD("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id,
|
||||
set_report.rnum, toString(data).c_str());
|
||||
}
|
||||
mDeviceCallback->onDeviceOutput(UHID_SET_REPORT, set_report.rtype, data);
|
||||
break;
|
||||
}
|
||||
case UHID_OUTPUT: {
|
||||
@@ -269,7 +271,7 @@ int Device::handleEvents(int events) {
|
||||
if (DEBUG_OUTPUT) {
|
||||
ALOGD("UHID_OUTPUT rtype=%" PRIu8 " data=%s", output.rtype, toString(data).c_str());
|
||||
}
|
||||
mDeviceCallback->onDeviceOutput(output.rtype, data);
|
||||
mDeviceCallback->onDeviceOutput(UHID_OUTPUT, output.rtype, data);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -365,7 +367,7 @@ int register_com_android_commands_hid_Device(JNIEnv* env) {
|
||||
uhid::gDeviceCallbackClassInfo.onDeviceGetReport =
|
||||
env->GetMethodID(clazz, "onDeviceGetReport", "(II)V");
|
||||
uhid::gDeviceCallbackClassInfo.onDeviceOutput =
|
||||
env->GetMethodID(clazz, "onDeviceOutput", "(B[B)V");
|
||||
env->GetMethodID(clazz, "onDeviceOutput", "(BB[B)V");
|
||||
uhid::gDeviceCallbackClassInfo.onDeviceError =
|
||||
env->GetMethodID(clazz, "onDeviceError", "()V");
|
||||
if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL ||
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
|
||||
void onDeviceOpen();
|
||||
void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
|
||||
void onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data);
|
||||
void onDeviceOutput(uint8_t eventId, uint8_t rType, const std::vector<uint8_t>& data);
|
||||
void onDeviceError();
|
||||
|
||||
private:
|
||||
|
||||
@@ -46,7 +46,8 @@ public class Device {
|
||||
|
||||
// Sync with linux uhid_event_type::UHID_OUTPUT
|
||||
private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6;
|
||||
|
||||
// Sync with linux uhid_event_type::UHID_SET_REPORT
|
||||
private static final byte UHID_EVENT_TYPE_SET_REPORT = 13;
|
||||
private final int mId;
|
||||
private final HandlerThread mThread;
|
||||
private final DeviceHandler mHandler;
|
||||
@@ -199,10 +200,10 @@ public class Device {
|
||||
}
|
||||
|
||||
// native callback
|
||||
public void onDeviceOutput(byte rtype, byte[] data) {
|
||||
public void onDeviceOutput(byte eventId, byte rtype, byte[] data) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("eventId", UHID_EVENT_TYPE_UHID_OUTPUT);
|
||||
json.put("eventId", eventId);
|
||||
json.put("deviceId", mId);
|
||||
json.put("reportType", rtype);
|
||||
JSONArray dataArray = new JSONArray();
|
||||
|
||||
@@ -18659,6 +18659,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 {
|
||||
@@ -46898,6 +46950,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();
|
||||
|
||||
@@ -3398,42 +3398,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;
|
||||
ctor @Deprecated public LightState(@ColorInt int);
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1001,14 +1001,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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -1344,7 +1345,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>() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
139
core/java/android/hardware/input/InputDeviceLightsManager.java
Normal file
139
core/java/android/hardware/input/InputDeviceLightsManager.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
@@ -1532,6 +1532,86 @@ public final class InputManager {
|
||||
return new InputDeviceBattery(this, deviceId, hasBattery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -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 */
|
||||
@@ -99,6 +145,14 @@ public final class Light implements Parcelable {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the light.
|
||||
*/
|
||||
@NonNull
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ordinal of the light.
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
private LightState(@NonNull Parcel in) {
|
||||
mColor = in.readInt();
|
||||
this(color, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the color and intensity associated with this LightState.
|
||||
* @return the color and intensity in ARGB format. The A channel is ignored.
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@Override
|
||||
public abstract void close();
|
||||
|
||||
/**
|
||||
* Closes the session, reverting all changes made through it.
|
||||
* Get the token of a light session.
|
||||
*
|
||||
* @return Binder token of the light session.
|
||||
* @hide
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
mCloseGuard.warnIfOpen();
|
||||
close();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
public @NonNull IBinder getToken() {
|
||||
return mToken;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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}. */
|
||||
@@ -49,6 +49,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.
|
||||
*/
|
||||
@@ -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);
|
||||
|
||||
181
core/java/android/hardware/lights/SystemLightsManager.java
Normal file
181
core/java/android/hardware/lights/SystemLightsManager.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
@@ -858,6 +862,21 @@ public final class InputDevice implements Parcelable {
|
||||
return mBattery;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -51,6 +52,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;
|
||||
@@ -237,6 +240,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();
|
||||
@@ -303,6 +313,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);
|
||||
@@ -2294,6 +2310,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;
|
||||
|
||||
@@ -155,6 +155,20 @@ static struct {
|
||||
jmethodID getAffineTransform;
|
||||
} gTouchCalibrationClassInfo;
|
||||
|
||||
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;
|
||||
@@ -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));
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user