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