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