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