1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.accessibility; 18 19 import static android.view.accessibility.AccessibilityManager.FLASH_REASON_ALARM; 20 import static android.view.accessibility.AccessibilityManager.FLASH_REASON_PREVIEW; 21 22 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 23 24 import android.animation.ObjectAnimator; 25 import android.annotation.ColorInt; 26 import android.annotation.IntDef; 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.app.ActivityManager; 30 import android.content.BroadcastReceiver; 31 import android.content.ContentResolver; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.database.ContentObserver; 36 import android.graphics.Color; 37 import android.graphics.PixelFormat; 38 import android.hardware.camera2.CameraAccessException; 39 import android.hardware.camera2.CameraCharacteristics; 40 import android.hardware.camera2.CameraManager; 41 import android.hardware.display.DisplayManager; 42 import android.media.AudioAttributes; 43 import android.media.AudioManager; 44 import android.media.AudioPlaybackConfiguration; 45 import android.net.Uri; 46 import android.os.Binder; 47 import android.os.Handler; 48 import android.os.HandlerThread; 49 import android.os.IBinder; 50 import android.os.PowerManager; 51 import android.os.Process; 52 import android.os.RemoteException; 53 import android.os.SystemClock; 54 import android.os.UserHandle; 55 import android.provider.Settings; 56 import android.util.FeatureFlagUtils; 57 import android.util.Log; 58 import android.view.Display; 59 import android.view.View; 60 import android.view.WindowManager; 61 import android.view.accessibility.AccessibilityManager.FlashNotificationReason; 62 import android.view.animation.AccelerateInterpolator; 63 import android.widget.FrameLayout; 64 65 import com.android.internal.annotations.GuardedBy; 66 import com.android.internal.annotations.VisibleForTesting; 67 68 import java.lang.annotation.Retention; 69 import java.lang.annotation.RetentionPolicy; 70 import java.util.LinkedList; 71 import java.util.List; 72 import java.util.ListIterator; 73 74 class FlashNotificationsController { 75 private static final String LOG_TAG = "FlashNotifController"; 76 private static final boolean DEBUG = true; 77 78 private static final String WAKE_LOCK_TAG = "a11y:FlashNotificationsController"; 79 80 /** The tag for flash notification which is triggered by short/long preview. */ 81 private static final String TAG_PREVIEW = "preview"; 82 /** The tag for flash notification which is triggered by alarm. */ 83 private static final String TAG_ALARM = "alarm"; 84 85 /** The default flashing type: triggered by an event. It'll flash 2 times in a short period. */ 86 private static final int TYPE_DEFAULT = 1; 87 /** 88 * The sequence flashing type: usually triggered by call/alarm. It'll flash infinitely until the 89 * call/alarm ends. 90 */ 91 private static final int TYPE_SEQUENCE = 2; 92 /** 93 * The long preview flashing type: it's only for screen flash preview. It'll flash only 1 time 94 * with a long period to show the screen flash effect more clearly. 95 */ 96 private static final int TYPE_LONG_PREVIEW = 3; 97 98 /** @hide */ 99 @Retention(RetentionPolicy.SOURCE) 100 @IntDef(prefix = { "TYPE_" }, value = { 101 TYPE_DEFAULT, 102 TYPE_SEQUENCE, 103 TYPE_LONG_PREVIEW 104 }) 105 @interface FlashNotificationType {} 106 107 private static final int TYPE_DEFAULT_ON_MS = 350; 108 private static final int TYPE_DEFAULT_OFF_MS = 250; 109 private static final int TYPE_SEQUENCE_ON_MS = 700; 110 private static final int TYPE_SEQUENCE_OFF_MS = 700; 111 private static final int TYPE_LONG_PREVIEW_ON_MS = 5000; 112 private static final int TYPE_LONG_PREVIEW_OFF_MS = 1000; 113 private static final int TYPE_DEFAULT_SCREEN_DELAY_MS = 300; 114 115 private static final int SCREEN_FADE_DURATION_MS = 200; 116 private static final int SCREEN_FADE_OUT_TIMEOUT_MS = 10; 117 118 @ColorInt 119 private static final int SCREEN_DEFAULT_COLOR = 0x00FFFF00; 120 @ColorInt 121 private static final int SCREEN_DEFAULT_ALPHA = 0x66000000; 122 @ColorInt 123 private static final int SCREEN_DEFAULT_COLOR_WITH_ALPHA = 124 SCREEN_DEFAULT_COLOR | SCREEN_DEFAULT_ALPHA; 125 126 @VisibleForTesting 127 static final String ACTION_FLASH_NOTIFICATION_START_PREVIEW = 128 "com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW"; 129 @VisibleForTesting 130 static final String ACTION_FLASH_NOTIFICATION_STOP_PREVIEW = 131 "com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW"; 132 @VisibleForTesting 133 static final String EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR = 134 "com.android.internal.intent.extra.FLASH_NOTIFICATION_PREVIEW_COLOR"; 135 @VisibleForTesting 136 static final String EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE = 137 "com.android.internal.intent.extra.FLASH_NOTIFICATION_PREVIEW_TYPE"; 138 139 @VisibleForTesting 140 static final int PREVIEW_TYPE_SHORT = 0; 141 @VisibleForTesting 142 static final int PREVIEW_TYPE_LONG = 1; 143 144 @VisibleForTesting 145 static final String SETTING_KEY_CAMERA_FLASH_NOTIFICATION = "camera_flash_notification"; 146 @VisibleForTesting 147 static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION = "screen_flash_notification"; 148 @VisibleForTesting 149 static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR = 150 "screen_flash_notification_color_global"; 151 152 /** 153 * Timeout of the wake lock (5 minutes). It should normally never triggered, the wakelock 154 * should be released after the flashing notification is completed. 155 */ 156 private static final long WAKE_LOCK_TIMEOUT_MS = 5 * 60 * 1000; 157 158 private final Context mContext; 159 private final DisplayManager mDisplayManager; 160 private final PowerManager.WakeLock mWakeLock; 161 @GuardedBy("mFlashNotifications") 162 private final LinkedList<FlashNotification> mFlashNotifications = new LinkedList<>(); 163 private final Handler mMainHandler; 164 private final Handler mCallbackHandler; 165 private boolean mIsTorchTouched = false; 166 private boolean mIsTorchOn = false; 167 private boolean mIsCameraFlashNotificationEnabled = false; 168 private boolean mIsScreenFlashNotificationEnabled = false; 169 private boolean mIsAlarming = false; 170 private int mDisplayState = Display.STATE_OFF; 171 private boolean mIsCameraOpened = false; 172 private CameraManager mCameraManager; 173 private String mCameraId = null; 174 175 private final CameraManager.TorchCallback mTorchCallback = new CameraManager.TorchCallback() { 176 @Override 177 public void onTorchModeChanged(String cameraId, boolean enabled) { 178 if (mCameraId != null && mCameraId.equals(cameraId)) { 179 mIsTorchOn = enabled; 180 if (DEBUG) Log.d(LOG_TAG, "onTorchModeChanged, set mIsTorchOn=" + enabled); 181 } 182 } 183 }; 184 @VisibleForTesting 185 final CameraManager.AvailabilityCallback mTorchAvailabilityCallback = 186 new CameraManager.AvailabilityCallback() { 187 @Override 188 public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) { 189 if (mCameraId != null && mCameraId.equals(cameraId)) { 190 mIsCameraOpened = true; 191 } 192 } 193 194 @Override 195 public void onCameraClosed(@NonNull String cameraId) { 196 if (mCameraId != null && mCameraId.equals(cameraId)) { 197 mIsCameraOpened = false; 198 } 199 } 200 }; 201 private View mScreenFlashNotificationOverlayView; 202 private FlashNotification mCurrentFlashNotification; 203 204 private final AudioManager.AudioPlaybackCallback mAudioPlaybackCallback = 205 new AudioManager.AudioPlaybackCallback() { 206 @Override 207 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { 208 boolean isAlarmActive = false; 209 if (configs != null) { 210 isAlarmActive = configs.stream() 211 .anyMatch(config -> config.isActive() 212 && config.getAudioAttributes().getUsage() 213 == AudioAttributes.USAGE_ALARM); 214 } 215 216 if (mIsAlarming != isAlarmActive) { 217 if (DEBUG) Log.d(LOG_TAG, "alarm state changed: " + isAlarmActive); 218 if (isAlarmActive) { 219 startFlashNotificationSequenceForAlarm(); 220 } else { 221 stopFlashNotificationSequenceForAlarm(); 222 } 223 mIsAlarming = isAlarmActive; 224 } 225 } 226 }; 227 private volatile FlashNotificationThread mThread; 228 private final Handler mFlashNotificationHandler; 229 @VisibleForTesting 230 final FlashBroadcastReceiver mFlashBroadcastReceiver; 231 232 FlashNotificationsController(Context context)233 FlashNotificationsController(Context context) { 234 this(context, getStartedHandler("FlashNotificationThread"), getStartedHandler(LOG_TAG)); 235 } 236 237 @VisibleForTesting FlashNotificationsController(Context context, Handler flashNotificationHandler, Handler callbackHandler)238 FlashNotificationsController(Context context, Handler flashNotificationHandler, 239 Handler callbackHandler) { 240 mContext = context; 241 mMainHandler = new Handler(mContext.getMainLooper()); 242 mFlashNotificationHandler = flashNotificationHandler; 243 mCallbackHandler = callbackHandler; 244 245 new FlashContentObserver(mMainHandler).register(mContext.getContentResolver()); 246 247 final IntentFilter broadcastFilter = new IntentFilter(); 248 broadcastFilter.addAction(Intent.ACTION_BOOT_COMPLETED); 249 broadcastFilter.addAction(ACTION_FLASH_NOTIFICATION_START_PREVIEW); 250 broadcastFilter.addAction(ACTION_FLASH_NOTIFICATION_STOP_PREVIEW); 251 mFlashBroadcastReceiver = new FlashBroadcastReceiver(); 252 mContext.registerReceiver( 253 mFlashBroadcastReceiver, broadcastFilter, Context.RECEIVER_NOT_EXPORTED); 254 255 final PowerManager powerManager = mContext.getSystemService(PowerManager.class); 256 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG); 257 258 mDisplayManager = mContext.getSystemService(DisplayManager.class); 259 final DisplayManager.DisplayListener displayListener = 260 new DisplayManager.DisplayListener() { 261 @Override 262 public void onDisplayAdded(int displayId) { 263 } 264 265 @Override 266 public void onDisplayRemoved(int displayId) { 267 } 268 269 @Override 270 public void onDisplayChanged(int displayId) { 271 if (mDisplayManager != null) { 272 Display display = mDisplayManager.getDisplay(displayId); 273 if (display != null) { 274 mDisplayState = display.getState(); 275 } 276 } 277 } 278 }; 279 280 if (mDisplayManager != null) { 281 mDisplayManager.registerDisplayListener(displayListener, null); 282 } 283 } 284 getStartedHandler(String tag)285 private static Handler getStartedHandler(String tag) { 286 HandlerThread handlerThread = new HandlerThread(tag); 287 handlerThread.start(); 288 return handlerThread.getThreadHandler(); 289 } 290 291 startFlashNotificationSequence(String opPkg, @FlashNotificationReason int reason, IBinder token)292 boolean startFlashNotificationSequence(String opPkg, 293 @FlashNotificationReason int reason, IBinder token) { 294 final FlashNotification flashNotification = new FlashNotification(opPkg, TYPE_SEQUENCE, 295 getScreenFlashColorPreference(reason), 296 token, () -> stopFlashNotification(opPkg)); 297 298 if (!flashNotification.tryLinkToDeath()) return false; 299 300 requestStartFlashNotification(flashNotification); 301 return true; 302 } 303 stopFlashNotificationSequence(String opPkg)304 boolean stopFlashNotificationSequence(String opPkg) { 305 stopFlashNotification(opPkg); 306 return true; 307 } 308 startFlashNotificationEvent(String opPkg, int reason, String reasonPkg)309 boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg) { 310 requestStartFlashNotification(new FlashNotification(opPkg, TYPE_DEFAULT, 311 getScreenFlashColorPreference(reason, reasonPkg))); 312 return true; 313 } 314 startFlashNotificationShortPreview()315 private void startFlashNotificationShortPreview() { 316 requestStartFlashNotification(new FlashNotification(TAG_PREVIEW, TYPE_DEFAULT, 317 getScreenFlashColorPreference(FLASH_REASON_PREVIEW))); 318 } 319 startFlashNotificationLongPreview(@olorInt int color)320 private void startFlashNotificationLongPreview(@ColorInt int color) { 321 requestStartFlashNotification(new FlashNotification(TAG_PREVIEW, TYPE_LONG_PREVIEW, 322 color)); 323 } 324 stopFlashNotificationLongPreview()325 private void stopFlashNotificationLongPreview() { 326 stopFlashNotification(TAG_PREVIEW); 327 } 328 startFlashNotificationSequenceForAlarm()329 private void startFlashNotificationSequenceForAlarm() { 330 requestStartFlashNotification(new FlashNotification(TAG_ALARM, TYPE_SEQUENCE, 331 getScreenFlashColorPreference(FLASH_REASON_ALARM))); 332 } 333 stopFlashNotificationSequenceForAlarm()334 private void stopFlashNotificationSequenceForAlarm() { 335 stopFlashNotification(TAG_ALARM); 336 } 337 requestStartFlashNotification(FlashNotification flashNotification)338 private void requestStartFlashNotification(FlashNotification flashNotification) { 339 if (DEBUG) Log.d(LOG_TAG, "requestStartFlashNotification"); 340 341 boolean isFeatureOn = FeatureFlagUtils.isEnabled(mContext, 342 FeatureFlagUtils.SETTINGS_FLASH_NOTIFICATIONS); 343 mIsCameraFlashNotificationEnabled = isFeatureOn && Settings.System.getIntForUser( 344 mContext.getContentResolver(), SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0, 345 UserHandle.USER_CURRENT) != 0; 346 mIsScreenFlashNotificationEnabled = isFeatureOn && Settings.System.getIntForUser( 347 mContext.getContentResolver(), SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0, 348 UserHandle.USER_CURRENT) != 0; 349 350 // To prevent unexpectedly screen flash when screen is off, delays the TYPE_DEFAULT screen 351 // flash since mDisplayState is not refreshed to STATE_OFF immediately after screen is 352 // turned off. No need to delay TYPE_SEQUENCE screen flash as calls and alarms will always 353 // wake up the screen. 354 // TODO(b/267121704) refactor the logic to remove delay workaround 355 if (flashNotification.mType == TYPE_DEFAULT && mIsScreenFlashNotificationEnabled) { 356 mMainHandler.sendMessageDelayed( 357 obtainMessage(FlashNotificationsController::startFlashNotification, this, 358 flashNotification), TYPE_DEFAULT_SCREEN_DELAY_MS); 359 if (DEBUG) Log.i(LOG_TAG, "give some delay for flash notification"); 360 } else { 361 startFlashNotification(flashNotification); 362 } 363 } 364 stopFlashNotification(String tag)365 private void stopFlashNotification(String tag) { 366 if (DEBUG) Log.i(LOG_TAG, "stopFlashNotification: tag=" + tag); 367 synchronized (mFlashNotifications) { 368 final FlashNotification notification = removeFlashNotificationLocked(tag); 369 if (mCurrentFlashNotification != null && notification == mCurrentFlashNotification) { 370 stopFlashNotificationLocked(); 371 startNextFlashNotificationLocked(); 372 } 373 } 374 } 375 prepareForCameraFlashNotification()376 private void prepareForCameraFlashNotification() { 377 mCameraManager = mContext.getSystemService(CameraManager.class); 378 379 if (mCameraManager != null) { 380 try { 381 mCameraId = getCameraId(); 382 } catch (CameraAccessException e) { 383 Log.e(LOG_TAG, "CameraAccessException", e); 384 } 385 mCameraManager.registerTorchCallback(mTorchCallback, null); 386 } 387 } 388 getCameraId()389 private String getCameraId() throws CameraAccessException { 390 String[] ids = mCameraManager.getCameraIdList(); 391 392 for (String id : ids) { 393 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id); 394 Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); 395 Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); 396 if (flashAvailable != null && lensFacing != null 397 && flashAvailable && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { 398 if (DEBUG) Log.d(LOG_TAG, "Found valid camera, cameraId=" + id); 399 return id; 400 } 401 } 402 return null; 403 } 404 showScreenNotificationOverlayView(@olorInt int color)405 private void showScreenNotificationOverlayView(@ColorInt int color) { 406 mMainHandler.sendMessage(obtainMessage( 407 FlashNotificationsController::showScreenNotificationOverlayViewMainThread, 408 this, color)); 409 } 410 hideScreenNotificationOverlayView()411 private void hideScreenNotificationOverlayView() { 412 mMainHandler.sendMessage(obtainMessage( 413 FlashNotificationsController::fadeOutScreenNotificationOverlayViewMainThread, 414 this)); 415 mMainHandler.sendMessageDelayed(obtainMessage( 416 FlashNotificationsController::hideScreenNotificationOverlayViewMainThread, 417 this), SCREEN_FADE_DURATION_MS + SCREEN_FADE_OUT_TIMEOUT_MS); 418 } 419 showScreenNotificationOverlayViewMainThread(@olorInt int color)420 private void showScreenNotificationOverlayViewMainThread(@ColorInt int color) { 421 if (DEBUG) Log.d(LOG_TAG, "showScreenNotificationOverlayViewMainThread"); 422 WindowManager.LayoutParams params = new WindowManager.LayoutParams( 423 WindowManager.LayoutParams.MATCH_PARENT, 424 WindowManager.LayoutParams.MATCH_PARENT, 425 WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, 426 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 427 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 428 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 429 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, 430 PixelFormat.TRANSLUCENT); 431 params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; 432 params.layoutInDisplayCutoutMode = 433 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 434 params.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; 435 436 // Main display 437 if (mScreenFlashNotificationOverlayView == null) { 438 mScreenFlashNotificationOverlayView = getScreenNotificationOverlayView(color); 439 mContext.getSystemService(WindowManager.class).addView( 440 mScreenFlashNotificationOverlayView, params); 441 fadeScreenNotificationOverlayViewMainThread(mScreenFlashNotificationOverlayView, true); 442 } 443 } 444 fadeOutScreenNotificationOverlayViewMainThread()445 private void fadeOutScreenNotificationOverlayViewMainThread() { 446 if (DEBUG) Log.d(LOG_TAG, "fadeOutScreenNotificationOverlayViewMainThread"); 447 if (mScreenFlashNotificationOverlayView != null) { 448 fadeScreenNotificationOverlayViewMainThread(mScreenFlashNotificationOverlayView, false); 449 } 450 } 451 fadeScreenNotificationOverlayViewMainThread(View view, boolean in)452 private void fadeScreenNotificationOverlayViewMainThread(View view, boolean in) { 453 ObjectAnimator fade = ObjectAnimator.ofFloat(view, "alpha", in ? 0.0f : 1.0f, 454 in ? 1.0f : 0.0f); 455 fade.setInterpolator(new AccelerateInterpolator()); 456 fade.setAutoCancel(true); 457 fade.setDuration(SCREEN_FADE_DURATION_MS); 458 fade.start(); 459 } 460 hideScreenNotificationOverlayViewMainThread()461 private void hideScreenNotificationOverlayViewMainThread() { 462 if (DEBUG) Log.d(LOG_TAG, "hideScreenNotificationOverlayViewMainThread"); 463 if (mScreenFlashNotificationOverlayView != null) { 464 mScreenFlashNotificationOverlayView.setVisibility(View.GONE); 465 mContext.getSystemService(WindowManager.class).removeView( 466 mScreenFlashNotificationOverlayView); 467 mScreenFlashNotificationOverlayView = null; 468 } 469 } 470 getScreenNotificationOverlayView(@olorInt int color)471 private View getScreenNotificationOverlayView(@ColorInt int color) { 472 View screenNotificationOverlayView = new FrameLayout(mContext); 473 screenNotificationOverlayView.setBackgroundColor(color); 474 screenNotificationOverlayView.setAlpha(0.0f); 475 return screenNotificationOverlayView; 476 } 477 478 @ColorInt getScreenFlashColorPreference(@lashNotificationReason int reason, String reasonPkg)479 private int getScreenFlashColorPreference(@FlashNotificationReason int reason, 480 String reasonPkg) { 481 // TODO(b/267121466) implement getting color per reason, reasonPkg basis 482 return getScreenFlashColorPreference(); 483 } 484 485 @ColorInt getScreenFlashColorPreference(@lashNotificationReason int reason)486 private int getScreenFlashColorPreference(@FlashNotificationReason int reason) { 487 // TODO(b/267121466) implement getting color per reason basis 488 return getScreenFlashColorPreference(); 489 } 490 491 @ColorInt getScreenFlashColorPreference()492 private int getScreenFlashColorPreference() { 493 return Settings.System.getIntForUser(mContext.getContentResolver(), 494 SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, SCREEN_DEFAULT_COLOR_WITH_ALPHA, 495 UserHandle.USER_CURRENT); 496 } 497 startFlashNotification(@onNull FlashNotification flashNotification)498 private void startFlashNotification(@NonNull FlashNotification flashNotification) { 499 final int type = flashNotification.mType; 500 final String tag = flashNotification.mTag; 501 if (DEBUG) Log.i(LOG_TAG, "startFlashNotification: type=" + type + ", tag=" + tag); 502 503 if (!(mIsCameraFlashNotificationEnabled 504 || mIsScreenFlashNotificationEnabled 505 || flashNotification.mForceStartScreenFlash)) { 506 if (DEBUG) Log.d(LOG_TAG, "Flash notification is disabled"); 507 return; 508 } 509 510 if (mIsCameraOpened) { 511 if (DEBUG) Log.d(LOG_TAG, "Since camera for torch is opened, block notification."); 512 return; 513 } 514 515 if (mIsCameraFlashNotificationEnabled && mCameraId == null) { 516 prepareForCameraFlashNotification(); 517 } 518 519 final long identity = Binder.clearCallingIdentity(); 520 try { 521 synchronized (mFlashNotifications) { 522 if (type == TYPE_DEFAULT || type == TYPE_LONG_PREVIEW) { 523 if (mCurrentFlashNotification != null) { 524 if (DEBUG) { 525 Log.i(LOG_TAG, 526 "Default type of flash notification can not work because " 527 + "previous flash notification is working"); 528 } 529 } else { 530 startFlashNotificationLocked(flashNotification); 531 } 532 } else if (type == TYPE_SEQUENCE) { 533 if (mCurrentFlashNotification != null) { 534 removeFlashNotificationLocked(tag); 535 stopFlashNotificationLocked(); 536 } 537 mFlashNotifications.addFirst(flashNotification); 538 startNextFlashNotificationLocked(); 539 } else { 540 Log.e(LOG_TAG, "Unavailable flash notification type"); 541 } 542 } 543 } finally { 544 Binder.restoreCallingIdentity(identity); 545 } 546 } 547 548 @GuardedBy("mFlashNotifications") removeFlashNotificationLocked(String tag)549 private FlashNotification removeFlashNotificationLocked(String tag) { 550 ListIterator<FlashNotification> iterator = mFlashNotifications.listIterator(0); 551 while (iterator.hasNext()) { 552 FlashNotification notification = iterator.next(); 553 if (notification != null && notification.mTag.equals(tag)) { 554 iterator.remove(); 555 notification.tryUnlinkToDeath(); 556 if (DEBUG) { 557 Log.i(LOG_TAG, 558 "removeFlashNotificationLocked: tag=" + notification.mTag); 559 } 560 return notification; 561 } 562 } 563 if (mCurrentFlashNotification != null && mCurrentFlashNotification.mTag.equals(tag)) { 564 mCurrentFlashNotification.tryUnlinkToDeath(); 565 return mCurrentFlashNotification; 566 } 567 return null; 568 } 569 570 @GuardedBy("mFlashNotifications") stopFlashNotificationLocked()571 private void stopFlashNotificationLocked() { 572 if (mThread != null) { 573 if (DEBUG) { 574 Log.i(LOG_TAG, 575 "stopFlashNotificationLocked: tag=" + mThread.mFlashNotification.mTag); 576 } 577 mThread.cancel(); 578 mThread = null; 579 } 580 doCameraFlashNotificationOff(); 581 doScreenFlashNotificationOff(); 582 } 583 584 @GuardedBy("mFlashNotifications") startNextFlashNotificationLocked()585 private void startNextFlashNotificationLocked() { 586 if (DEBUG) Log.i(LOG_TAG, "startNextFlashNotificationLocked"); 587 if (mFlashNotifications.size() <= 0) { 588 mCurrentFlashNotification = null; 589 return; 590 } 591 startFlashNotificationLocked(mFlashNotifications.getFirst()); 592 } 593 594 @GuardedBy("mFlashNotifications") startFlashNotificationLocked(@onNull final FlashNotification notification)595 private void startFlashNotificationLocked(@NonNull final FlashNotification notification) { 596 if (DEBUG) { 597 Log.i(LOG_TAG, "startFlashNotificationLocked: type=" + notification.mType + ", tag=" 598 + notification.mTag); 599 } 600 mCurrentFlashNotification = notification; 601 mThread = new FlashNotificationThread(notification); 602 mFlashNotificationHandler.post(mThread); 603 } 604 isDozeMode()605 private boolean isDozeMode() { 606 return mDisplayState == Display.STATE_DOZE || mDisplayState == Display.STATE_DOZE_SUSPEND; 607 } 608 doCameraFlashNotificationOn()609 private void doCameraFlashNotificationOn() { 610 if (mIsCameraFlashNotificationEnabled && !mIsTorchOn) { 611 doCameraFlashNotification(true); 612 } 613 if (DEBUG) { 614 Log.i(LOG_TAG, "doCameraFlashNotificationOn: " 615 + "isCameraFlashNotificationEnabled=" + mIsCameraFlashNotificationEnabled 616 + ", isTorchOn=" + mIsTorchOn 617 + ", isTorchTouched=" + mIsTorchTouched); 618 } 619 } 620 doCameraFlashNotificationOff()621 private void doCameraFlashNotificationOff() { 622 if (mIsTorchTouched) { 623 doCameraFlashNotification(false); 624 } 625 if (DEBUG) { 626 Log.i(LOG_TAG, "doCameraFlashNotificationOff: " 627 + "isCameraFlashNotificationEnabled=" + mIsCameraFlashNotificationEnabled 628 + ", isTorchOn=" + mIsTorchOn 629 + ", isTorchTouched=" + mIsTorchTouched); 630 } 631 } 632 doScreenFlashNotificationOn(@olorInt int color, boolean forceStartScreenFlash)633 private void doScreenFlashNotificationOn(@ColorInt int color, boolean forceStartScreenFlash) { 634 final boolean isDoze = isDozeMode(); 635 if ((mIsScreenFlashNotificationEnabled || forceStartScreenFlash) && !isDoze) { 636 showScreenNotificationOverlayView(color); 637 } 638 if (DEBUG) { 639 Log.i(LOG_TAG, "doScreenFlashNotificationOn: " 640 + "isScreenFlashNotificationEnabled=" + mIsScreenFlashNotificationEnabled 641 + ", isDozeMode=" + isDoze 642 + ", color=" + Integer.toHexString(color)); 643 } 644 } 645 doScreenFlashNotificationOff()646 private void doScreenFlashNotificationOff() { 647 hideScreenNotificationOverlayView(); 648 if (DEBUG) { 649 Log.i(LOG_TAG, "doScreenFlashNotificationOff: " 650 + "isScreenFlashNotificationEnabled=" + mIsScreenFlashNotificationEnabled); 651 } 652 } 653 doCameraFlashNotification(boolean on)654 private void doCameraFlashNotification(boolean on) { 655 if (DEBUG) Log.d(LOG_TAG, "doCameraFlashNotification: " + on + " mCameraId : " + mCameraId); 656 if (mCameraManager != null && mCameraId != null) { 657 try { 658 mCameraManager.setTorchMode(mCameraId, on); 659 mIsTorchTouched = on; 660 } catch (CameraAccessException e) { 661 Log.e(LOG_TAG, "Failed to setTorchMode: " + e); 662 } 663 } else { 664 Log.e(LOG_TAG, "Can not use camera flash notification, please check CameraManager!"); 665 } 666 } 667 668 private static class FlashNotification { 669 // Tag could be the requesting package name or constants like TAG_PREVIEW and TAG_ALARM. 670 private final String mTag; 671 @FlashNotificationType 672 private final int mType; 673 private final int mOnDuration; 674 private final int mOffDuration; 675 @ColorInt 676 private final int mColor; 677 private int mRepeat; 678 @Nullable 679 private final IBinder mToken; 680 @Nullable 681 private final IBinder.DeathRecipient mDeathRecipient; 682 private final boolean mForceStartScreenFlash; 683 FlashNotification(String tag, @FlashNotificationType int type, @ColorInt int color)684 private FlashNotification(String tag, @FlashNotificationType int type, 685 @ColorInt int color) { 686 this(tag, type, color, null, null); 687 } 688 FlashNotification(String tag, @FlashNotificationType int type, @ColorInt int color, IBinder token, IBinder.DeathRecipient deathRecipient)689 private FlashNotification(String tag, @FlashNotificationType int type, @ColorInt int color, 690 IBinder token, IBinder.DeathRecipient deathRecipient) { 691 mType = type; 692 mTag = tag; 693 mColor = color; 694 mToken = token; 695 mDeathRecipient = deathRecipient; 696 697 switch (type) { 698 case TYPE_SEQUENCE: 699 mOnDuration = TYPE_SEQUENCE_ON_MS; 700 mOffDuration = TYPE_SEQUENCE_OFF_MS; 701 mRepeat = 0; // indefinite 702 mForceStartScreenFlash = false; 703 break; 704 case TYPE_LONG_PREVIEW: 705 mOnDuration = TYPE_LONG_PREVIEW_ON_MS; 706 mOffDuration = TYPE_LONG_PREVIEW_OFF_MS; 707 mRepeat = 1; 708 mForceStartScreenFlash = true; 709 break; 710 case TYPE_DEFAULT: 711 default: 712 mOnDuration = TYPE_DEFAULT_ON_MS; 713 mOffDuration = TYPE_DEFAULT_OFF_MS; 714 mRepeat = 2; 715 mForceStartScreenFlash = false; 716 break; 717 } 718 } 719 tryLinkToDeath()720 boolean tryLinkToDeath() { 721 if (mToken == null || mDeathRecipient == null) return false; 722 723 try { 724 mToken.linkToDeath(mDeathRecipient, 0); 725 return true; 726 } catch (RemoteException e) { 727 Log.e(LOG_TAG, "RemoteException", e); 728 return false; 729 } 730 } 731 tryUnlinkToDeath()732 boolean tryUnlinkToDeath() { 733 if (mToken == null || mDeathRecipient == null) return false; 734 try { 735 mToken.unlinkToDeath(mDeathRecipient, 0); 736 return true; 737 } catch (Exception ignored) { 738 return false; 739 } 740 } 741 } 742 743 private class FlashNotificationThread extends Thread { 744 private final FlashNotification mFlashNotification; 745 private boolean mForceStop; 746 @ColorInt 747 private int mColor = Color.TRANSPARENT; 748 private boolean mShouldDoScreenFlash = false; 749 private boolean mShouldDoCameraFlash = false; 750 FlashNotificationThread(@onNull FlashNotification flashNotification)751 private FlashNotificationThread(@NonNull FlashNotification flashNotification) { 752 mFlashNotification = flashNotification; 753 mForceStop = false; 754 } 755 756 @Override run()757 public void run() { 758 if (DEBUG) Log.d(LOG_TAG, "run started: " + mFlashNotification.mTag); 759 Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); 760 mColor = mFlashNotification.mColor; 761 mShouldDoScreenFlash = (Color.alpha(mColor) != Color.TRANSPARENT) 762 || mFlashNotification.mForceStartScreenFlash; 763 mShouldDoCameraFlash = mFlashNotification.mType != TYPE_LONG_PREVIEW; 764 synchronized (this) { 765 mWakeLock.acquire(WAKE_LOCK_TIMEOUT_MS); 766 try { 767 startFlashNotification(); 768 } finally { 769 doScreenFlashNotificationOff(); 770 doCameraFlashNotificationOff(); 771 try { 772 mWakeLock.release(); 773 } catch (RuntimeException e) { 774 Log.e(LOG_TAG, "Error while releasing FlashNotificationsController" 775 + " wakelock (already released by the system?)"); 776 } 777 } 778 } 779 synchronized (mFlashNotifications) { 780 if (mThread == this) { 781 mThread = null; 782 } 783 // Unlink to death recipient for not interrupted flash notification. For flash 784 // notification interrupted and stopped by stopFlashNotification(), unlink to 785 // death is already handled in stopFlashNotification(). 786 if (!mForceStop) { 787 mFlashNotification.tryUnlinkToDeath(); 788 mCurrentFlashNotification = null; 789 } 790 } 791 if (DEBUG) Log.d(LOG_TAG, "run finished: " + mFlashNotification.mTag); 792 } 793 startFlashNotification()794 private void startFlashNotification() { 795 synchronized (this) { 796 while (!mForceStop) { 797 if (mFlashNotification.mType != TYPE_SEQUENCE 798 && mFlashNotification.mRepeat >= 0) { 799 if (mFlashNotification.mRepeat-- == 0) { 800 break; 801 } 802 } 803 if (mShouldDoScreenFlash) { 804 doScreenFlashNotificationOn(mColor, 805 mFlashNotification.mForceStartScreenFlash); 806 } 807 if (mShouldDoCameraFlash) { 808 doCameraFlashNotificationOn(); 809 } 810 delay(mFlashNotification.mOnDuration); 811 doScreenFlashNotificationOff(); 812 doCameraFlashNotificationOff(); 813 if (mForceStop) { 814 break; 815 } 816 delay(mFlashNotification.mOffDuration); 817 } 818 } 819 } 820 cancel()821 void cancel() { 822 if (DEBUG) Log.d(LOG_TAG, "run canceled: " + mFlashNotification.mTag); 823 synchronized (this) { 824 mThread.mForceStop = true; 825 mThread.notify(); 826 } 827 } 828 delay(long duration)829 private void delay(long duration) { 830 if (duration > 0) { 831 long bedtime = duration + SystemClock.uptimeMillis(); 832 do { 833 try { 834 this.wait(duration); 835 } catch (InterruptedException ignored) { 836 } 837 if (mForceStop) { 838 break; 839 } 840 duration = bedtime - SystemClock.uptimeMillis(); 841 } while (duration > 0); 842 } 843 } 844 } 845 846 @VisibleForTesting 847 class FlashBroadcastReceiver extends BroadcastReceiver { 848 @Override onReceive(Context context, Intent intent)849 public void onReceive(Context context, Intent intent) { 850 final String action = intent.getAction(); 851 // Some system services not properly initiated before boot complete. Should do the 852 // initialization after receiving ACTION_BOOT_COMPLETED. 853 if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { 854 if (UserHandle.myUserId() != ActivityManager.getCurrentUser()) { 855 return; 856 } 857 858 mIsCameraFlashNotificationEnabled = Settings.System.getIntForUser( 859 mContext.getContentResolver(), SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0, 860 UserHandle.USER_CURRENT) != 0; 861 if (mIsCameraFlashNotificationEnabled) { 862 prepareForCameraFlashNotification(); 863 } else { 864 if (mCameraManager != null) { 865 mCameraManager.unregisterTorchCallback(mTorchCallback); 866 } 867 } 868 869 final AudioManager audioManager = mContext.getSystemService(AudioManager.class); 870 if (audioManager != null) { 871 audioManager.registerAudioPlaybackCallback(mAudioPlaybackCallback, 872 mCallbackHandler); 873 } 874 875 mCameraManager = mContext.getSystemService(CameraManager.class); 876 mCameraManager.registerAvailabilityCallback(mTorchAvailabilityCallback, 877 mCallbackHandler); 878 879 } else if (ACTION_FLASH_NOTIFICATION_START_PREVIEW.equals(intent.getAction())) { 880 if (DEBUG) Log.i(LOG_TAG, "ACTION_FLASH_NOTIFICATION_START_PREVIEW"); 881 final int color = intent.getIntExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR, 882 Color.TRANSPARENT); 883 final int type = intent.getIntExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE, 884 PREVIEW_TYPE_SHORT); 885 if (type == PREVIEW_TYPE_LONG) { 886 startFlashNotificationLongPreview(color); 887 } else if (type == PREVIEW_TYPE_SHORT) { 888 startFlashNotificationShortPreview(); 889 } 890 } else if (ACTION_FLASH_NOTIFICATION_STOP_PREVIEW.equals(intent.getAction())) { 891 if (DEBUG) Log.i(LOG_TAG, "ACTION_FLASH_NOTIFICATION_STOP_PREVIEW"); 892 stopFlashNotificationLongPreview(); 893 } 894 } 895 } 896 897 private final class FlashContentObserver extends ContentObserver { 898 private final Uri mCameraFlashNotificationUri = Settings.System.getUriFor( 899 SETTING_KEY_CAMERA_FLASH_NOTIFICATION); 900 private final Uri mScreenFlashNotificationUri = Settings.System.getUriFor( 901 SETTING_KEY_SCREEN_FLASH_NOTIFICATION); 902 FlashContentObserver(Handler handler)903 FlashContentObserver(Handler handler) { 904 super(handler); 905 } 906 register(ContentResolver contentResolver)907 void register(ContentResolver contentResolver) { 908 contentResolver.registerContentObserver(mCameraFlashNotificationUri, false, this, 909 UserHandle.USER_ALL); 910 contentResolver.registerContentObserver(mScreenFlashNotificationUri, false, this, 911 UserHandle.USER_ALL); 912 } 913 914 @Override onChange(boolean selfChange, Uri uri)915 public void onChange(boolean selfChange, Uri uri) { 916 if (mCameraFlashNotificationUri.equals(uri)) { 917 mIsCameraFlashNotificationEnabled = Settings.System.getIntForUser( 918 mContext.getContentResolver(), SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0, 919 UserHandle.USER_CURRENT) != 0; 920 if (mIsCameraFlashNotificationEnabled) { 921 prepareForCameraFlashNotification(); 922 } else { 923 mIsTorchOn = false; 924 if (mCameraManager != null) { 925 mCameraManager.unregisterTorchCallback(mTorchCallback); 926 } 927 } 928 } else if (mScreenFlashNotificationUri.equals(uri)) { 929 mIsScreenFlashNotificationEnabled = Settings.System.getIntForUser( 930 mContext.getContentResolver(), SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0, 931 UserHandle.USER_CURRENT) != 0; 932 } 933 } 934 } 935 } 936