1 /* * Copyright (C) 2008 The Android Open Source Project
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 package com.android.server.lights;
17 
18 import android.Manifest;
19 import android.annotation.Nullable;
20 import android.app.ActivityManager;
21 import android.content.Context;
22 import android.hardware.light.HwLight;
23 import android.hardware.light.HwLightState;
24 import android.hardware.light.ILights;
25 import android.hardware.lights.ILightsManager;
26 import android.hardware.lights.Light;
27 import android.hardware.lights.LightState;
28 import android.os.Binder;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.Trace;
35 import android.provider.Settings;
36 import android.util.Slog;
37 import android.util.SparseArray;
38 
39 import com.android.internal.annotations.GuardedBy;
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.internal.display.BrightnessSynchronizer;
42 import com.android.internal.util.DumpUtils;
43 import com.android.internal.util.Preconditions;
44 import com.android.server.SystemService;
45 
46 import java.io.FileDescriptor;
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.function.Supplier;
54 
55 public class LightsService extends SystemService {
56     static final String TAG = "LightsService";
57     static final boolean DEBUG = false;
58 
59     private final LightImpl[] mLightsByType = new LightImpl[LightsManager.LIGHT_ID_COUNT];
60     private final SparseArray<LightImpl> mLightsById = new SparseArray<>();
61 
62     @Nullable
63     private final Supplier<ILights> mVintfLights;
64 
65     @VisibleForTesting
66     final LightsManagerBinderService mManagerService;
67 
68     private Handler mH;
69 
70     private final class LightsManagerBinderService extends ILightsManager.Stub {
71 
72         private final class Session implements Comparable<Session> {
73             final IBinder mToken;
74             final SparseArray<LightState> mRequests = new SparseArray<>();
75             final int mPriority;
76 
Session(IBinder token, int priority)77             Session(IBinder token, int priority) {
78                 mToken = token;
79                 mPriority = priority;
80             }
81 
setRequest(int lightId, LightState state)82             void setRequest(int lightId, LightState state) {
83                 if (state != null) {
84                     mRequests.put(lightId, state);
85                 } else {
86                     mRequests.remove(lightId);
87                 }
88             }
89 
90             @Override
compareTo(Session otherSession)91             public int compareTo(Session otherSession) {
92                 // Sort descending by priority
93                 return Integer.compare(otherSession.mPriority, mPriority);
94             }
95         }
96 
97         @GuardedBy("LightsService.this")
98         private final List<Session> mSessions = new ArrayList<>();
99 
100         /**
101          * Returns the lights available for apps to control on the device. Only lights that aren't
102          * reserved for system use are available to apps.
103          */
104         @Override
getLights()105         public List<Light> getLights() {
106             getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
107                     "getLights requires CONTROL_DEVICE_LIGHTS_PERMISSION");
108 
109             synchronized (LightsService.this) {
110                 final List<Light> lights = new ArrayList<Light>();
111                 for (int i = 0; i < mLightsById.size(); i++) {
112                     if (!mLightsById.valueAt(i).isSystemLight()) {
113                         HwLight hwLight = mLightsById.valueAt(i).mHwLight;
114                         lights.add(new Light(hwLight.id, hwLight.ordinal, hwLight.type));
115                     }
116                 }
117                 return lights;
118             }
119         }
120 
121         /**
122          * Updates the set of light requests for {@param token} with additions and removals from
123          * {@param lightIds} and {@param lightStates}.
124          *
125          * <p>Null values mean that the request should be removed, and the light turned off if it
126          * is not being used by anything else.
127          */
128         @Override
setLightStates(IBinder token, int[] lightIds, LightState[] lightStates)129         public void setLightStates(IBinder token, int[] lightIds, LightState[] lightStates) {
130             getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
131                     "setLightStates requires CONTROL_DEVICE_LIGHTS permission");
132             Preconditions.checkState(lightIds.length == lightStates.length);
133 
134             synchronized (LightsService.this) {
135                 Session session = getSessionLocked(Preconditions.checkNotNull(token));
136                 Preconditions.checkState(session != null, "not registered");
137 
138                 checkRequestIsValid(lightIds);
139 
140                 for (int i = 0; i < lightIds.length; i++) {
141                     session.setRequest(lightIds[i], lightStates[i]);
142                 }
143                 invalidateLightStatesLocked();
144             }
145         }
146 
147         @Override
getLightState(int lightId)148         public @Nullable LightState getLightState(int lightId) {
149             getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
150                     "getLightState(@TestApi) requires CONTROL_DEVICE_LIGHTS permission");
151 
152             synchronized (LightsService.this) {
153                 final LightImpl light = mLightsById.get(lightId);
154                 if (light == null || light.isSystemLight()) {
155                     throw new IllegalArgumentException("Invalid light: " + lightId);
156                 }
157                 return new LightState(light.getColor());
158             }
159         }
160 
161         @Override
openSession(IBinder token, int priority)162         public void openSession(IBinder token, int priority) {
163             getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
164                     "openSession requires CONTROL_DEVICE_LIGHTS permission");
165             Preconditions.checkNotNull(token);
166 
167             synchronized (LightsService.this) {
168                 Preconditions.checkState(getSessionLocked(token) == null, "already registered");
169                 try {
170                     token.linkToDeath(() -> closeSessionInternal(token), 0);
171                     mSessions.add(new Session(token, priority));
172                     Collections.sort(mSessions);
173                 } catch (RemoteException e) {
174                     Slog.e(TAG, "Couldn't open session, client already died" , e);
175                     throw new IllegalArgumentException("Client is already dead.");
176                 }
177             }
178         }
179 
180         @Override
closeSession(IBinder token)181         public void closeSession(IBinder token) {
182             getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
183                     "closeSession requires CONTROL_DEVICE_LIGHTS permission");
184             Preconditions.checkNotNull(token);
185             closeSessionInternal(token);
186         }
187 
188         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)189         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
190             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
191 
192             synchronized (LightsService.this) {
193                 if (mVintfLights != null) {
194                     pw.println("Service: aidl (" + mVintfLights.get() + ")");
195                 } else {
196                     pw.println("Service: hidl");
197                 }
198 
199                 pw.println("Lights:");
200                 for (int i = 0; i < mLightsById.size(); i++) {
201                     final LightImpl light = mLightsById.valueAt(i);
202                     pw.println(String.format("  Light id=%d ordinal=%d color=%08x",
203                             light.mHwLight.id, light.mHwLight.ordinal, light.getColor()));
204                 }
205 
206                 pw.println("Session clients:");
207                 for (Session session : mSessions) {
208                     pw.println("  Session token=" + session.mToken);
209                     for (int i = 0; i < session.mRequests.size(); i++) {
210                         pw.println(String.format("    Request id=%d color=%08x",
211                                 session.mRequests.keyAt(i),
212                                 session.mRequests.valueAt(i).getColor()));
213                     }
214                 }
215             }
216         }
217 
closeSessionInternal(IBinder token)218         private void closeSessionInternal(IBinder token) {
219             synchronized (LightsService.this) {
220                 final Session session = getSessionLocked(token);
221                 if (session != null) {
222                     mSessions.remove(session);
223                     invalidateLightStatesLocked();
224                 }
225             }
226         }
227 
checkRequestIsValid(int[] lightIds)228         private void checkRequestIsValid(int[] lightIds) {
229             for (int lightId : lightIds) {
230                 final LightImpl light = mLightsById.get(lightId);
231                 Preconditions.checkState(light != null && !light.isSystemLight(),
232                         "Invalid lightId " + lightId);
233             }
234         }
235 
236         /**
237          * Apply light state requests for all light IDs.
238          *
239          * <p>In case of conflict, the session that started earliest wins.
240          */
invalidateLightStatesLocked()241         private void invalidateLightStatesLocked() {
242             final Map<Integer, LightState> states = new HashMap<>();
243             for (int i = mSessions.size() - 1; i >= 0; i--) {
244                 SparseArray<LightState> requests = mSessions.get(i).mRequests;
245                 for (int j = 0; j < requests.size(); j++) {
246                     states.put(requests.keyAt(j), requests.valueAt(j));
247                 }
248             }
249             for (int i = 0; i < mLightsById.size(); i++) {
250                 LightImpl light = mLightsById.valueAt(i);
251                 if (!light.isSystemLight()) {
252                     LightState state = states.get(light.mHwLight.id);
253                     if (state != null) {
254                         light.setColor(state.getColor());
255                     } else {
256                         light.turnOff();
257                     }
258                 }
259             }
260         }
261 
getSessionLocked(IBinder token)262         private @Nullable Session getSessionLocked(IBinder token) {
263             for (int i = 0; i < mSessions.size(); i++) {
264                 if (token.equals(mSessions.get(i).mToken)) {
265                     return mSessions.get(i);
266                 }
267             }
268             return null;
269         }
270     }
271 
272     private final class LightImpl extends LogicalLight {
273 
LightImpl(Context context, HwLight hwLight)274         private LightImpl(Context context, HwLight hwLight) {
275             mHwLight = hwLight;
276         }
277 
278         @Override
setBrightness(float brightness)279         public void setBrightness(float brightness) {
280             setBrightness(brightness, BRIGHTNESS_MODE_USER);
281         }
282 
283         @Override
setBrightness(float brightness, int brightnessMode)284         public void setBrightness(float brightness, int brightnessMode) {
285             if (Float.isNaN(brightness)) {
286                 Slog.w(TAG, "Brightness is not valid: " + brightness);
287                 return;
288             }
289             synchronized (this) {
290                 // LOW_PERSISTENCE cannot be manually set
291                 if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
292                     Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mHwLight.id
293                             + ": brightness=" + brightness);
294                     return;
295                 }
296                 int brightnessInt = BrightnessSynchronizer.brightnessFloatToInt(brightness);
297                 int color = brightnessInt & 0x000000ff;
298                 color = 0xff000000 | (color << 16) | (color << 8) | color;
299                 setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
300             }
301         }
302 
303         @Override
setColor(int color)304         public void setColor(int color) {
305             synchronized (this) {
306                 setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, 0);
307             }
308         }
309 
310         @Override
setFlashing(int color, int mode, int onMS, int offMS)311         public void setFlashing(int color, int mode, int onMS, int offMS) {
312             synchronized (this) {
313                 setLightLocked(color, mode, onMS, offMS, BRIGHTNESS_MODE_USER);
314             }
315         }
316 
317         @Override
pulse()318         public void pulse() {
319             pulse(0x00ffffff, 7);
320         }
321 
322         @Override
pulse(int color, int onMS)323         public void pulse(int color, int onMS) {
324             synchronized (this) {
325                 if (mColor == 0 && !mFlashing) {
326                     setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000,
327                             BRIGHTNESS_MODE_USER);
328                     mColor = 0;
329                     mH.postDelayed(this::stopFlashing, onMS);
330                 }
331             }
332         }
333 
334         @Override
turnOff()335         public void turnOff() {
336             synchronized (this) {
337                 setLightLocked(0, LIGHT_FLASH_NONE, 0, 0, 0);
338             }
339         }
340 
341         @Override
setVrMode(boolean enabled)342         public void setVrMode(boolean enabled) {
343             synchronized (this) {
344                 if (mVrModeEnabled != enabled) {
345                     mVrModeEnabled = enabled;
346 
347                     mUseLowPersistenceForVR =
348                             (getVrDisplayMode() == Settings.Secure.VR_DISPLAY_MODE_LOW_PERSISTENCE);
349                     if (shouldBeInLowPersistenceMode()) {
350                         mLastBrightnessMode = mBrightnessMode;
351                     }
352 
353                     // NOTE: We do not trigger a call to setLightLocked here.  We do not know the
354                     // current brightness or other values when leaving VR so we avoid any incorrect
355                     // jumps. The code that calls this method will immediately issue a brightness
356                     // update which is when the change will occur.
357                 }
358             }
359         }
360 
stopFlashing()361         private void stopFlashing() {
362             synchronized (this) {
363                 setLightLocked(mColor, LIGHT_FLASH_NONE, 0, 0, BRIGHTNESS_MODE_USER);
364             }
365         }
366 
setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode)367         private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
368             if (shouldBeInLowPersistenceMode()) {
369                 brightnessMode = BRIGHTNESS_MODE_LOW_PERSISTENCE;
370             } else if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
371                 brightnessMode = mLastBrightnessMode;
372             }
373 
374             if (!mInitialized || color != mColor || mode != mMode || onMS != mOnMS ||
375                     offMS != mOffMS || mBrightnessMode != brightnessMode) {
376                 if (DEBUG) {
377                     Slog.v(TAG, "setLight #" + mHwLight.id + ": color=#"
378                             + Integer.toHexString(color) + ": brightnessMode=" + brightnessMode);
379                 }
380                 mInitialized = true;
381                 mLastColor = mColor;
382                 mColor = color;
383                 mMode = mode;
384                 mOnMS = onMS;
385                 mOffMS = offMS;
386                 mBrightnessMode = brightnessMode;
387                 setLightUnchecked(color, mode, onMS, offMS, brightnessMode);
388             }
389         }
390 
setLightUnchecked(int color, int mode, int onMS, int offMS, int brightnessMode)391         private void setLightUnchecked(int color, int mode, int onMS, int offMS,
392                 int brightnessMode) {
393             Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLightState(" + mHwLight.id + ", 0x"
394                     + Integer.toHexString(color) + ")");
395             try {
396                 if (mVintfLights != null) {
397                     HwLightState lightState = new HwLightState();
398                     lightState.color = color;
399                     lightState.flashMode = (byte) mode;
400                     lightState.flashOnMs = onMS;
401                     lightState.flashOffMs = offMS;
402                     lightState.brightnessMode = (byte) brightnessMode;
403                     mVintfLights.get().setLightState(mHwLight.id, lightState);
404                 } else {
405                     setLight_native(mHwLight.id, color, mode, onMS, offMS, brightnessMode);
406                 }
407             } catch (RemoteException | UnsupportedOperationException ex) {
408                 Slog.e(TAG, "Failed issuing setLightState", ex);
409             } finally {
410                 Trace.traceEnd(Trace.TRACE_TAG_POWER);
411             }
412         }
413 
shouldBeInLowPersistenceMode()414         private boolean shouldBeInLowPersistenceMode() {
415             return mVrModeEnabled && mUseLowPersistenceForVR;
416         }
417 
418         /**
419          * Returns whether a light is system-use-only or should be accessible to
420          * applications using the {@link android.hardware.lights.LightsManager} API.
421          */
isSystemLight()422         private boolean isSystemLight() {
423             // LIGHT_ID_COUNT comes from the 2.0 HIDL HAL and only contains system lights.
424             // Newly-added lights are made available via the public LightsManager API.
425             return (0 <= mHwLight.type && mHwLight.type < LightsManager.LIGHT_ID_COUNT);
426         }
427 
getColor()428         private int getColor() {
429             return mColor;
430         }
431 
432         private HwLight mHwLight;
433         private int mColor;
434         private int mMode;
435         private int mOnMS;
436         private int mOffMS;
437         private boolean mFlashing;
438         private int mBrightnessMode;
439         private int mLastBrightnessMode;
440         private int mLastColor;
441         private boolean mVrModeEnabled;
442         private boolean mUseLowPersistenceForVR;
443         private boolean mInitialized;
444     }
445 
LightsService(Context context)446     public LightsService(Context context) {
447         this(context, new VintfHalCache(), Looper.myLooper());
448     }
449 
450     @VisibleForTesting
LightsService(Context context, Supplier<ILights> service, Looper looper)451     LightsService(Context context, Supplier<ILights> service, Looper looper) {
452         super(context);
453         mH = new Handler(looper);
454         mVintfLights = service.get() != null ? service : null;
455 
456         populateAvailableLights(context);
457         mManagerService = new LightsManagerBinderService();
458     }
459 
populateAvailableLights(Context context)460     private void populateAvailableLights(Context context) {
461         if (mVintfLights != null) {
462             populateAvailableLightsFromAidl(context);
463         } else {
464             populateAvailableLightsFromHidl(context);
465         }
466 
467         for (int i = mLightsById.size() - 1; i >= 0; i--) {
468             final int type = mLightsById.keyAt(i);
469             if (0 <= type && type < mLightsByType.length) {
470                 mLightsByType[type] = mLightsById.valueAt(i);
471             }
472         }
473     }
474 
populateAvailableLightsFromAidl(Context context)475     private void populateAvailableLightsFromAidl(Context context) {
476         try {
477             for (HwLight hwLight : mVintfLights.get().getLights()) {
478                 mLightsById.put(hwLight.id, new LightImpl(context, hwLight));
479             }
480         } catch (RemoteException ex) {
481             Slog.e(TAG, "Unable to get lights from HAL", ex);
482         }
483     }
484 
populateAvailableLightsFromHidl(Context context)485     private void populateAvailableLightsFromHidl(Context context) {
486         for (int i = 0; i < mLightsByType.length; i++) {
487             HwLight hwLight = new HwLight();
488             hwLight.id = (byte) i;
489             hwLight.ordinal = 1;
490             hwLight.type = (byte) i;
491             mLightsById.put(hwLight.id, new LightImpl(context, hwLight));
492         }
493     }
494 
495     @Override
onStart()496     public void onStart() {
497         publishLocalService(LightsManager.class, mService);
498         publishBinderService(Context.LIGHTS_SERVICE, mManagerService);
499     }
500 
501     @Override
onBootPhase(int phase)502     public void onBootPhase(int phase) {
503     }
504 
getVrDisplayMode()505     private int getVrDisplayMode() {
506         int currentUser = ActivityManager.getCurrentUser();
507         return Settings.Secure.getIntForUser(getContext().getContentResolver(),
508                 Settings.Secure.VR_DISPLAY_MODE,
509                 /*default*/Settings.Secure.VR_DISPLAY_MODE_LOW_PERSISTENCE,
510                 currentUser);
511     }
512 
513     private final LightsManager mService = new LightsManager() {
514         @Override
515         public LogicalLight getLight(int lightType) {
516             if (mLightsByType != null && 0 <= lightType && lightType < mLightsByType.length) {
517                 return mLightsByType[lightType];
518             } else {
519                 return null;
520             }
521         }
522     };
523 
524     private static class VintfHalCache implements Supplier<ILights>, IBinder.DeathRecipient {
525         @GuardedBy("this")
526         private ILights mInstance = null;
527 
528         @Override
get()529         public synchronized ILights get() {
530             if (mInstance == null) {
531                 IBinder binder = Binder.allowBlocking(
532                         ServiceManager.waitForDeclaredService(ILights.DESCRIPTOR + "/default"));
533                 if (binder != null) {
534                     mInstance = ILights.Stub.asInterface(binder);
535                     try {
536                         binder.linkToDeath(this, 0);
537                     } catch (RemoteException e) {
538                         Slog.e(TAG, "Unable to register DeathRecipient for " + mInstance);
539                     }
540                 }
541             }
542             return mInstance;
543         }
544 
545         @Override
binderDied()546         public synchronized void binderDied() {
547             mInstance = null;
548         }
549     }
550 
setLight_native(int light, int color, int mode, int onMS, int offMS, int brightnessMode)551     static native void setLight_native(int light, int color, int mode,
552             int onMS, int offMS, int brightnessMode);
553 }
554