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