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