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