Add LightsManager implementation to LightsService
Includes backend code to support LightsManager binder calls and route them to the HALs. Bug: 144979010 Bug: 144978691 Bug: 142715294 Change-Id: I0080972620ba7a3fb1197cdd0288287d3cfa8780 Fix: 142230898 Test: atest LightsManagerTest Test: atest LightsServiceTest Merged-In: I2db7f2caa432cd1e2389ea5ca6544200ada18675
This commit is contained in:
committed by
Robin Lee
parent
4c5ac6b353
commit
3e376b79ce
@@ -59,6 +59,7 @@ package android {
|
||||
field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
|
||||
field @Deprecated public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
|
||||
field public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
|
||||
field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
|
||||
field public static final String CONTROL_DISPLAY_COLOR_TRANSFORMS = "android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS";
|
||||
field public static final String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION";
|
||||
field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
|
||||
@@ -2562,6 +2563,48 @@ 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 setLights(@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.hardware.location {
|
||||
|
||||
public class ContextHubClient implements java.io.Closeable {
|
||||
|
||||
@@ -10,6 +10,7 @@ package android {
|
||||
field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
|
||||
field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
|
||||
field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
|
||||
field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
|
||||
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
|
||||
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
|
||||
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
|
||||
@@ -985,6 +986,49 @@ package android.hardware.display {
|
||||
|
||||
}
|
||||
|
||||
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 android.hardware.lights.LightState getLightState(@NonNull android.hardware.lights.Light);
|
||||
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 setLights(@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.location {
|
||||
|
||||
public final class GnssClock implements android.os.Parcelable {
|
||||
|
||||
@@ -83,6 +83,7 @@ import android.hardware.hdmi.IHdmiControlService;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.iris.IIrisService;
|
||||
import android.hardware.iris.IrisManager;
|
||||
import android.hardware.lights.LightsManager;
|
||||
import android.hardware.location.ContextHubManager;
|
||||
import android.hardware.radio.RadioManager;
|
||||
import android.hardware.usb.IUsbManager;
|
||||
@@ -1348,6 +1349,13 @@ final class SystemServiceRegistry {
|
||||
return new DynamicSystemManager(
|
||||
IDynamicSystemService.Stub.asInterface(b));
|
||||
}});
|
||||
registerService(Context.LIGHTS_SERVICE, LightsManager.class,
|
||||
new CachedServiceFetcher<LightsManager>() {
|
||||
@Override
|
||||
public LightsManager createService(ContextImpl ctx)
|
||||
throws ServiceNotFoundException {
|
||||
return new LightsManager(ctx);
|
||||
}});
|
||||
//CHECKSTYLE:ON IndentationCheck
|
||||
}
|
||||
|
||||
|
||||
@@ -3380,6 +3380,7 @@ public abstract class Context {
|
||||
//@hide: TIME_DETECTOR_SERVICE,
|
||||
//@hide: TIME_ZONE_DETECTOR_SERVICE,
|
||||
PERMISSION_SERVICE,
|
||||
LIGHTS_SERVICE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ServiceName {}
|
||||
@@ -4879,6 +4880,15 @@ public abstract class Context {
|
||||
@SystemApi
|
||||
public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to retrieve a
|
||||
* {@link android.hardware.lights.LightsManager} for controlling device lights.
|
||||
*
|
||||
* @see #getSystemService(String)
|
||||
* @hide
|
||||
*/
|
||||
public static final String LIGHTS_SERVICE = "lights";
|
||||
|
||||
/**
|
||||
* Determine whether the given permission is allowed for a particular
|
||||
* process and user ID running in the system.
|
||||
|
||||
33
core/java/android/hardware/lights/ILightsManager.aidl
Normal file
33
core/java/android/hardware/lights/ILightsManager.aidl
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (C) 2020 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.hardware.lights.Light;
|
||||
import android.hardware.lights.LightState;
|
||||
|
||||
/**
|
||||
* API to lights manager service.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
interface ILightsManager {
|
||||
List<Light> getLights();
|
||||
LightState getLightState(int lightId);
|
||||
void openSession(in IBinder sessionToken);
|
||||
void closeSession(in IBinder sessionToken);
|
||||
void setLightStates(in IBinder sessionToken, in int[] lightIds, in LightState[] states);
|
||||
}
|
||||
20
core/java/android/hardware/lights/Light.aidl
Normal file
20
core/java/android/hardware/lights/Light.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (C) 2020 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;
|
||||
|
||||
/** @hide */
|
||||
parcelable Light;
|
||||
105
core/java/android/hardware/lights/Light.java
Normal file
105
core/java/android/hardware/lights/Light.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (C) 2020 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.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Represents a logical light on the device.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
public final class Light implements Parcelable {
|
||||
private final int mId;
|
||||
private final int mOrdinal;
|
||||
private final int mType;
|
||||
|
||||
/**
|
||||
* Creates a new light with the given data.
|
||||
*
|
||||
* @hide */
|
||||
public Light(int id, int ordinal, int type) {
|
||||
mId = id;
|
||||
mOrdinal = ordinal;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
private Light(@NonNull Parcel in) {
|
||||
mId = in.readInt();
|
||||
mOrdinal = in.readInt();
|
||||
mType = in.readInt();
|
||||
}
|
||||
|
||||
/** Implement the Parcelable interface */
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mId);
|
||||
dest.writeInt(mOrdinal);
|
||||
dest.writeInt(mType);
|
||||
}
|
||||
|
||||
/** Implement the Parcelable interface */
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Implement the Parcelable interface */
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<Light> CREATOR =
|
||||
new Parcelable.Creator<Light>() {
|
||||
public Light createFromParcel(Parcel in) {
|
||||
return new Light(in);
|
||||
}
|
||||
|
||||
public Light[] newArray(int size) {
|
||||
return new Light[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the id of the light.
|
||||
*/
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ordinal of the light.
|
||||
*
|
||||
* <p>This represents the physical order of the lights on the device. The exact values are
|
||||
* device-dependent, but for example, if there are lights in a row, sorting the Light objects
|
||||
* by ordinal should match the order in which they appear on the device. If the device has
|
||||
* 4 lights, the ordinals could be [1, 2, 3, 4] or [0, 10, 20, 30] or any other values that
|
||||
* have the same sort order.
|
||||
*/
|
||||
public int getOrdinal() {
|
||||
return mOrdinal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logical type of the light.
|
||||
*/
|
||||
public @LightsManager.LightType int getType() {
|
||||
return mType;
|
||||
}
|
||||
}
|
||||
20
core/java/android/hardware/lights/LightState.aidl
Normal file
20
core/java/android/hardware/lights/LightState.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (C) 2020 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;
|
||||
|
||||
/** @hide */
|
||||
parcelable LightState;
|
||||
84
core/java/android/hardware/lights/LightState.java
Normal file
84
core/java/android/hardware/lights/LightState.java
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (C) 2020 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.annotation.ColorInt;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Represents the state of a device light.
|
||||
*
|
||||
* <p>Controlling the color and brightness of a light is done on a best-effort basis. Each of the R,
|
||||
* G and B channels represent the intensities of the respective part of an RGB LED, if that is
|
||||
* supported. For devices that only support on or off lights, everything that's not off will turn
|
||||
* the light on. If the light is monochrome and only the brightness can be controlled, the RGB color
|
||||
* will be converted to only a brightness value and that will be used for the light's single
|
||||
* channel.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
public final class LightState implements Parcelable {
|
||||
private final int mColor;
|
||||
|
||||
/**
|
||||
* Creates a new LightState with the desired color and intensity.
|
||||
*
|
||||
* @param color the desired color and intensity in ARGB format.
|
||||
*/
|
||||
public LightState(@ColorInt int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
private LightState(@NonNull Parcel in) {
|
||||
mColor = in.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the color and intensity associated with this LightState.
|
||||
* @return the color and intensity in ARGB format. The A channel is ignored.
|
||||
*/
|
||||
public @ColorInt int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<LightState> CREATOR =
|
||||
new Parcelable.Creator<LightState>() {
|
||||
public LightState createFromParcel(Parcel in) {
|
||||
return new LightState(in);
|
||||
}
|
||||
|
||||
public LightState[] newArray(int size) {
|
||||
return new LightState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
204
core/java/android/hardware/lights/LightsManager.java
Normal file
204
core/java/android/hardware/lights/LightsManager.java
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.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
|
||||
@TestApi
|
||||
@SystemService(Context.LIGHTS_SERVICE)
|
||||
public final class LightsManager {
|
||||
private static final String TAG = "LightsManager";
|
||||
|
||||
// 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 */
|
||||
public static final int LIGHT_TYPE_MICROPHONE = 8;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(prefix = {"LIGHT_TYPE_"},
|
||||
value = {
|
||||
LIGHT_TYPE_MICROPHONE,
|
||||
})
|
||||
public @interface LightType {}
|
||||
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final ILightsManager mService;
|
||||
|
||||
/**
|
||||
* Creates a LightsManager.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
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) {
|
||||
mContext = Preconditions.checkNotNull(context);
|
||||
mService = Preconditions.checkNotNull(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lights available on the device.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates a session that can be used to control device lights and represents the lifetime
|
||||
* of the requests.
|
||||
*/
|
||||
public final 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 setLights(@NonNull LightsRequest request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
if (!mClosed) {
|
||||
try {
|
||||
mService.setLightStates(mToken, 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(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
core/java/android/hardware/lights/LightsRequest.java
Normal file
95
core/java/android/hardware/lights/LightsRequest.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Encapsulates a request to modify the state of multiple lights.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
public final class LightsRequest {
|
||||
|
||||
/** Visible to {@link LightsManager.Session}. */
|
||||
final int[] mLightIds;
|
||||
|
||||
/** Visible to {@link LightsManager.Session}. */
|
||||
final LightState[] mLightStates;
|
||||
|
||||
/**
|
||||
* Can only be constructed via {@link LightsRequest.Builder#build()}.
|
||||
*/
|
||||
private LightsRequest(SparseArray<LightState> changes) {
|
||||
final int n = changes.size();
|
||||
mLightIds = new int[n];
|
||||
mLightStates = new LightState[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
mLightIds[i] = changes.keyAt(i);
|
||||
mLightStates[i] = changes.valueAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating device light change requests.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final SparseArray<LightState> mChanges = new SparseArray<>();
|
||||
|
||||
/**
|
||||
* Overrides the color and intensity of a given light.
|
||||
*
|
||||
* @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) {
|
||||
Preconditions.checkNotNull(light);
|
||||
Preconditions.checkNotNull(state);
|
||||
mChanges.put(light.getId(), state);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the override for the color and intensity of a given light.
|
||||
*
|
||||
* @param light the light to modify
|
||||
*/
|
||||
public @NonNull Builder clearLight(@NonNull Light light) {
|
||||
Preconditions.checkNotNull(light);
|
||||
mChanges.put(light.getId(), null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a LightsRequest object used to override lights on the device.
|
||||
*
|
||||
* <p>The generated {@link LightsRequest} should be used in
|
||||
* {@link LightsManager.Session#setLights(LightsLightsRequest).
|
||||
*/
|
||||
public @NonNull LightsRequest build() {
|
||||
return new LightsRequest(mChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3647,6 +3647,13 @@
|
||||
<permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- Allows an application to control the lights on the device.
|
||||
@hide
|
||||
@SystemApi
|
||||
@TestApi -->
|
||||
<permission android:name="android.permission.CONTROL_DEVICE_LIGHTS"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- Allows an application to control the color saturation of the display.
|
||||
@hide
|
||||
@SystemApi -->
|
||||
|
||||
@@ -15,32 +15,217 @@
|
||||
|
||||
package com.android.server.lights;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.hardware.light.HwLight;
|
||||
import android.hardware.light.HwLightState;
|
||||
import android.hardware.light.ILights;
|
||||
import android.hardware.lights.ILightsManager;
|
||||
import android.hardware.lights.Light;
|
||||
import android.hardware.lights.LightState;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.Trace;
|
||||
import android.provider.Settings;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class LightsService extends SystemService {
|
||||
static final String TAG = "LightsService";
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
private LightImpl[] mLights = null;
|
||||
private SparseArray<LightImpl> mLightsById = null;
|
||||
|
||||
private ILights mVintfLights = null;
|
||||
|
||||
@VisibleForTesting
|
||||
final LightsManagerBinderService mManagerService;
|
||||
|
||||
private Handler mH;
|
||||
|
||||
private final class LightsManagerBinderService extends ILightsManager.Stub {
|
||||
|
||||
private final class Session {
|
||||
final IBinder mToken;
|
||||
final SparseArray<LightState> mRequests = new SparseArray<>();
|
||||
|
||||
Session(IBinder token) {
|
||||
mToken = token;
|
||||
}
|
||||
|
||||
void setRequest(int lightId, LightState state) {
|
||||
if (state != null) {
|
||||
mRequests.put(lightId, state);
|
||||
} else {
|
||||
mRequests.remove(lightId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("LightsService.this")
|
||||
private final List<Session> mSessions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Returns the lights available for apps to control on the device. Only lights that aren't
|
||||
* reserved for system use are available to apps.
|
||||
*/
|
||||
@Override
|
||||
public List<Light> getLights() {
|
||||
getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
|
||||
"getLights requires CONTROL_DEVICE_LIGHTS_PERMISSION");
|
||||
|
||||
synchronized (LightsService.this) {
|
||||
final List<Light> lights = new ArrayList<Light>();
|
||||
for (int i = 0; i < mLightsById.size(); i++) {
|
||||
HwLight hwLight = mLightsById.valueAt(i).getHwLight();
|
||||
if (!isSystemLight(hwLight)) {
|
||||
lights.add(new Light(hwLight.id, hwLight.ordinal, hwLight.type));
|
||||
}
|
||||
}
|
||||
return lights;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the set of light requests for {@param token} with additions and removals from
|
||||
* {@param lightIds} and {@param lightStates}.
|
||||
*
|
||||
* <p>Null values mean that the request should be removed, and the light turned off if it
|
||||
* is not being used by anything else.
|
||||
*/
|
||||
@Override
|
||||
public void setLightStates(IBinder token, int[] lightIds, LightState[] lightStates) {
|
||||
getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
|
||||
"setLightStates requires CONTROL_DEVICE_LIGHTS permission");
|
||||
Preconditions.checkState(lightIds.length == lightStates.length);
|
||||
|
||||
synchronized (LightsService.this) {
|
||||
Session session = getSessionLocked(Preconditions.checkNotNull(token));
|
||||
Preconditions.checkState(session != null, "not registered");
|
||||
|
||||
checkRequestIsValid(lightIds);
|
||||
|
||||
for (int i = 0; i < lightIds.length; i++) {
|
||||
session.setRequest(lightIds[i], lightStates[i]);
|
||||
}
|
||||
invalidateLightStatesLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LightState getLightState(int lightId) {
|
||||
getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
|
||||
"getLightState(@TestApi) requires CONTROL_DEVICE_LIGHTS permission");
|
||||
|
||||
synchronized (LightsService.this) {
|
||||
final LightImpl light = mLightsById.get(lightId);
|
||||
if (light == null || isSystemLight(light.getHwLight())) {
|
||||
throw new IllegalArgumentException("Invalid light: " + lightId);
|
||||
}
|
||||
return new LightState(light.getColor());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openSession(IBinder token) {
|
||||
getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
|
||||
"openSession requires CONTROL_DEVICE_LIGHTS permission");
|
||||
Preconditions.checkNotNull(token);
|
||||
|
||||
synchronized (LightsService.this) {
|
||||
Preconditions.checkState(getSessionLocked(token) == null, "already registered");
|
||||
try {
|
||||
token.linkToDeath(() -> closeSessionInternal(token), 0);
|
||||
mSessions.add(new Session(token));
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Couldn't open session, client already died" , e);
|
||||
throw new IllegalArgumentException("Client is already dead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeSession(IBinder token) {
|
||||
getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
|
||||
"closeSession requires CONTROL_DEVICE_LIGHTS permission");
|
||||
Preconditions.checkNotNull(token);
|
||||
closeSessionInternal(token);
|
||||
}
|
||||
|
||||
private void closeSessionInternal(IBinder token) {
|
||||
synchronized (LightsService.this) {
|
||||
final Session session = getSessionLocked(token);
|
||||
if (session != null) {
|
||||
mSessions.remove(session);
|
||||
invalidateLightStatesLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRequestIsValid(int[] lightIds) {
|
||||
for (int i = 0; i < lightIds.length; i++) {
|
||||
final LightImpl light = mLightsById.get(lightIds[i]);
|
||||
final HwLight hwLight = light.getHwLight();
|
||||
Preconditions.checkState(light != null && !isSystemLight(hwLight),
|
||||
"invalid lightId " + hwLight.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply light state requests for all light IDs.
|
||||
*
|
||||
* <p>In case of conflict, the session that started earliest wins.
|
||||
*/
|
||||
private void invalidateLightStatesLocked() {
|
||||
final Map<Integer, LightState> states = new HashMap<>();
|
||||
for (int i = mSessions.size() - 1; i >= 0; i--) {
|
||||
SparseArray<LightState> requests = mSessions.get(i).mRequests;
|
||||
for (int j = 0; j < requests.size(); j++) {
|
||||
states.put(requests.keyAt(j), requests.valueAt(j));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < mLightsById.size(); i++) {
|
||||
LightImpl light = mLightsById.valueAt(i);
|
||||
HwLight hwLight = light.getHwLight();
|
||||
if (!isSystemLight(hwLight)) {
|
||||
LightState state = states.get(hwLight.id);
|
||||
if (state != null) {
|
||||
light.setColor(state.getColor());
|
||||
} else {
|
||||
light.turnOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Session getSessionLocked(IBinder token) {
|
||||
for (int i = 0; i < mSessions.size(); i++) {
|
||||
if (token.equals(mSessions.get(i).mToken)) {
|
||||
return mSessions.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final class LightImpl extends LogicalLight {
|
||||
private final IBinder mDisplayToken;
|
||||
private final int mSurfaceControlMaximumBrightness;
|
||||
@@ -126,7 +311,7 @@ public class LightsService extends SystemService {
|
||||
setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000,
|
||||
BRIGHTNESS_MODE_USER);
|
||||
mColor = 0;
|
||||
mH.sendMessageDelayed(Message.obtain(mH, 1, this), onMS);
|
||||
mH.postDelayed(this::stopFlashing, onMS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,6 +404,14 @@ public class LightsService extends SystemService {
|
||||
return mVrModeEnabled && mUseLowPersistenceForVR;
|
||||
}
|
||||
|
||||
private HwLight getHwLight() {
|
||||
return mHwLight;
|
||||
}
|
||||
|
||||
private int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
private HwLight mHwLight;
|
||||
private int mColor;
|
||||
private int mMode;
|
||||
@@ -234,16 +427,25 @@ public class LightsService extends SystemService {
|
||||
}
|
||||
|
||||
public LightsService(Context context) {
|
||||
this(context,
|
||||
ILights.Stub.asInterface(
|
||||
ServiceManager.getService("android.hardware.light.ILights/default")),
|
||||
Looper.myLooper());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
LightsService(Context context, ILights service, Looper looper) {
|
||||
super(context);
|
||||
|
||||
IBinder service = ServiceManager.getService("android.hardware.light.ILights/default");
|
||||
mVintfLights = ILights.Stub.asInterface(service);
|
||||
|
||||
mH = new Handler(looper);
|
||||
mVintfLights = service;
|
||||
mManagerService = new LightsManagerBinderService();
|
||||
populateAvailableLights(context);
|
||||
}
|
||||
|
||||
private void populateAvailableLights(Context context) {
|
||||
mLights = new LightImpl[LightsManager.LIGHT_ID_COUNT];
|
||||
mLightsById = new SparseArray<>();
|
||||
|
||||
if (mVintfLights != null) {
|
||||
try {
|
||||
for (HwLight availableLight : mVintfLights.getLights()) {
|
||||
@@ -252,6 +454,7 @@ public class LightsService extends SystemService {
|
||||
if (0 <= type && type < mLights.length && mLights[type] == null) {
|
||||
mLights[type] = light;
|
||||
}
|
||||
mLightsById.put(availableLight.id, light);
|
||||
}
|
||||
} catch (RemoteException ex) {
|
||||
Slog.e(TAG, "Unable to get lights for initialization", ex);
|
||||
@@ -268,6 +471,7 @@ public class LightsService extends SystemService {
|
||||
light.type = (byte) i;
|
||||
|
||||
mLights[i] = new LightImpl(context, light);
|
||||
mLightsById.put(i, mLights[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,6 +479,7 @@ public class LightsService extends SystemService {
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishLocalService(LightsManager.class, mService);
|
||||
publishBinderService(Context.LIGHTS_SERVICE, mManagerService);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -292,7 +497,7 @@ public class LightsService extends SystemService {
|
||||
private final LightsManager mService = new LightsManager() {
|
||||
@Override
|
||||
public LogicalLight getLight(int lightType) {
|
||||
if (mLights != null && 0 <= lightType && lightType < LIGHT_ID_COUNT) {
|
||||
if (mLights != null && 0 <= lightType && lightType < mLights.length) {
|
||||
return mLights[lightType];
|
||||
} else {
|
||||
return null;
|
||||
@@ -300,13 +505,16 @@ public class LightsService extends SystemService {
|
||||
}
|
||||
};
|
||||
|
||||
private Handler mH = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
LightImpl light = (LightImpl)msg.obj;
|
||||
light.stopFlashing();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Returns whether a light is system-use-only or should be accessible to
|
||||
* applications using the {@link android.hardware.lights.LightsManager} API.
|
||||
*/
|
||||
private static boolean isSystemLight(HwLight light) {
|
||||
// LIGHT_ID_COUNT comes from the 2.0 HIDL HAL and only contains system
|
||||
// lights. Newly added lights will be made available via the
|
||||
// LightsManager API.
|
||||
return 0 <= light.type && light.type < LightsManager.LIGHT_ID_COUNT;
|
||||
}
|
||||
|
||||
static native void setLight_native(int light, int color, int mode,
|
||||
int onMS, int offMS, int brightnessMode);
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.lights;
|
||||
|
||||
import static android.hardware.lights.LightsRequest.Builder;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.light.HwLight;
|
||||
import android.hardware.light.HwLightState;
|
||||
import android.hardware.light.ILights;
|
||||
import android.hardware.lights.Light;
|
||||
import android.hardware.lights.LightState;
|
||||
import android.hardware.lights.LightsManager;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class LightsServiceTest {
|
||||
|
||||
private final ILights mHal = new ILights.Stub() {
|
||||
@Override
|
||||
public void setLightState(int id, HwLightState state) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HwLight[] getLights() {
|
||||
return new HwLight[] {
|
||||
fakeHwLight(101, 3, 1),
|
||||
fakeHwLight(102, LightsManager.LIGHT_TYPE_MICROPHONE, 4),
|
||||
fakeHwLight(103, LightsManager.LIGHT_TYPE_MICROPHONE, 3),
|
||||
fakeHwLight(104, LightsManager.LIGHT_TYPE_MICROPHONE, 1),
|
||||
fakeHwLight(105, LightsManager.LIGHT_TYPE_MICROPHONE, 2)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private static HwLight fakeHwLight(int id, int type, int ordinal) {
|
||||
HwLight light = new HwLight();
|
||||
light.id = id;
|
||||
light.type = (byte) type;
|
||||
light.ordinal = ordinal;
|
||||
return light;
|
||||
}
|
||||
|
||||
@Mock
|
||||
Context mContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLights_filtersSystemLights() {
|
||||
LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
|
||||
LightsManager manager = new LightsManager(mContext, service.mManagerService);
|
||||
|
||||
// When lights are listed, only the 4 MICROPHONE lights should be visible.
|
||||
assertThat(manager.getLights().size()).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControlMultipleLights() {
|
||||
LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
|
||||
LightsManager manager = new LightsManager(mContext, service.mManagerService);
|
||||
|
||||
// When the session requests to turn 3/4 lights on:
|
||||
LightsManager.LightsSession session = manager.openSession();
|
||||
session.setLights(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))
|
||||
.build());
|
||||
|
||||
// Then all 3 should turn on.
|
||||
assertThat(manager.getLightState(manager.getLights().get(0)).getColor()).isEqualTo(0xf1);
|
||||
assertThat(manager.getLightState(manager.getLights().get(1)).getColor()).isEqualTo(0xf2);
|
||||
assertThat(manager.getLightState(manager.getLights().get(2)).getColor()).isEqualTo(0xf3);
|
||||
|
||||
// And the 4th should remain off.
|
||||
assertThat(manager.getLightState(manager.getLights().get(3)).getColor()).isEqualTo(0x00);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControlLights_onlyEffectiveForLifetimeOfClient() {
|
||||
LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
|
||||
LightsManager manager = new LightsManager(mContext, service.mManagerService);
|
||||
Light micLight = manager.getLights().get(0);
|
||||
|
||||
// The light should begin by being off.
|
||||
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0x00000000);
|
||||
|
||||
// When a session commits changes:
|
||||
LightsManager.LightsSession session = manager.openSession();
|
||||
session.setLights(new Builder().setLight(micLight, new LightState(0xff00ff00)).build());
|
||||
// Then the light should turn on.
|
||||
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xff00ff00);
|
||||
|
||||
// When the session goes away:
|
||||
session.close();
|
||||
// Then the light should turn off.
|
||||
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0x00000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControlLights_firstCallerWinsContention() {
|
||||
LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
|
||||
LightsManager manager = new LightsManager(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.setLights(new Builder().setLight(micLight, new LightState(0xff0000ff)).build());
|
||||
session2.setLights(new Builder().setLight(micLight, new LightState(0xffffffff)).build());
|
||||
// Then session1 should win because it was created first.
|
||||
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xff0000ff);
|
||||
|
||||
// When session1 goes away:
|
||||
session1.close();
|
||||
// Then session2 should have its request go into effect.
|
||||
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xffffffff);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearLight() {
|
||||
LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper());
|
||||
LightsManager manager = new LightsManager(mContext, service.mManagerService);
|
||||
Light micLight = manager.getLights().get(0);
|
||||
|
||||
// When the session turns a light on:
|
||||
LightsManager.LightsSession session = manager.openSession();
|
||||
session.setLights(new Builder().setLight(micLight, new LightState(0xffffffff)).build());
|
||||
|
||||
// And then the session clears it again:
|
||||
session.setLights(new Builder().clearLight(micLight).build());
|
||||
|
||||
// Then the light should turn back off.
|
||||
assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user