1 /*
2  * Copyright (C) 2022 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.systemui.shade;
18 
19 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
20 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
21 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
22 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
23 
24 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
25 
26 import android.app.IActivityManager;
27 import android.content.Context;
28 import android.content.pm.ActivityInfo;
29 import android.content.res.Configuration;
30 import android.graphics.PixelFormat;
31 import android.graphics.Region;
32 import android.os.Binder;
33 import android.os.Build;
34 import android.os.RemoteException;
35 import android.os.Trace;
36 import android.util.Log;
37 import android.view.Display;
38 import android.view.Gravity;
39 import android.view.IWindow;
40 import android.view.IWindowSession;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.view.WindowInsets;
44 import android.view.WindowManager;
45 import android.view.WindowManager.LayoutParams;
46 import android.view.WindowManagerGlobal;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.keyguard.KeyguardUpdateMonitor;
50 import com.android.systemui.Dumpable;
51 import com.android.systemui.R;
52 import com.android.systemui.biometrics.AuthController;
53 import com.android.systemui.colorextraction.SysuiColorExtractor;
54 import com.android.systemui.dagger.SysUISingleton;
55 import com.android.systemui.dagger.qualifiers.Background;
56 import com.android.systemui.dump.DumpManager;
57 import com.android.systemui.dump.DumpsysTableLogger;
58 import com.android.systemui.keyguard.KeyguardViewMediator;
59 import com.android.systemui.plugins.statusbar.StatusBarStateController;
60 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
61 import com.android.systemui.statusbar.NotificationShadeWindowController;
62 import com.android.systemui.statusbar.StatusBarState;
63 import com.android.systemui.statusbar.SysuiStatusBarStateController;
64 import com.android.systemui.statusbar.phone.DozeParameters;
65 import com.android.systemui.statusbar.phone.KeyguardBypassController;
66 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
67 import com.android.systemui.statusbar.phone.ScrimController;
68 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
69 import com.android.systemui.statusbar.policy.ConfigurationController;
70 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
71 import com.android.systemui.statusbar.policy.KeyguardStateController;
72 
73 import java.io.PrintWriter;
74 import java.lang.ref.Reference;
75 import java.lang.ref.WeakReference;
76 import java.util.ArrayList;
77 import java.util.List;
78 import java.util.Objects;
79 import java.util.concurrent.Executor;
80 import java.util.function.Consumer;
81 import java.util.stream.Collectors;
82 
83 import javax.inject.Inject;
84 
85 /**
86  * Encapsulates all logic for the notification shade window state management.
87  */
88 @SysUISingleton
89 public class NotificationShadeWindowControllerImpl implements NotificationShadeWindowController,
90         Dumpable, ConfigurationListener {
91 
92     private static final String TAG = "NotificationShadeWindowController";
93     private static final int MAX_STATE_CHANGES_BUFFER_SIZE = 100;
94 
95     private final Context mContext;
96     private final WindowManager mWindowManager;
97     private final IActivityManager mActivityManager;
98     private final DozeParameters mDozeParameters;
99     private final KeyguardStateController mKeyguardStateController;
100     private final ShadeWindowLogger mLogger;
101     private final LayoutParams mLpChanged;
102     private final long mLockScreenDisplayTimeout;
103     private final float mKeyguardPreferredRefreshRate; // takes precedence over max
104     private final float mKeyguardMaxRefreshRate;
105     private final KeyguardViewMediator mKeyguardViewMediator;
106     private final KeyguardBypassController mKeyguardBypassController;
107     private final Executor mBackgroundExecutor;
108     private final AuthController mAuthController;
109     private ViewGroup mWindowRootView;
110     private LayoutParams mLp;
111     private boolean mHasTopUi;
112     private boolean mHasTopUiChanged;
113     private float mScreenBrightnessDoze;
114     private final NotificationShadeWindowState mCurrentState = new NotificationShadeWindowState();
115     private OtherwisedCollapsedListener mListener;
116     private ForcePluginOpenListener mForcePluginOpenListener;
117     private Consumer<Integer> mScrimsVisibilityListener;
118     private final ArrayList<WeakReference<StatusBarWindowCallback>>
119             mCallbacks = new ArrayList<>();
120 
121     private final SysuiColorExtractor mColorExtractor;
122     private final ScreenOffAnimationController mScreenOffAnimationController;
123     /**
124      * Layout params would be aggregated and dispatched all at once if this is > 0.
125      *
126      * @see #batchApplyWindowLayoutParams(Runnable)
127      */
128     private int mDeferWindowLayoutParams;
129     private boolean mLastKeyguardRotationAllowed;
130 
131     private final NotificationShadeWindowState.Buffer mStateBuffer =
132             new NotificationShadeWindowState.Buffer(MAX_STATE_CHANGES_BUFFER_SIZE);
133 
134     @Inject
NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager, IActivityManager activityManager, DozeParameters dozeParameters, StatusBarStateController statusBarStateController, ConfigurationController configurationController, KeyguardViewMediator keyguardViewMediator, KeyguardBypassController keyguardBypassController, @Background Executor backgroundExecutor, SysuiColorExtractor colorExtractor, DumpManager dumpManager, KeyguardStateController keyguardStateController, ScreenOffAnimationController screenOffAnimationController, AuthController authController, ShadeExpansionStateManager shadeExpansionStateManager, ShadeWindowLogger logger)135     public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
136             IActivityManager activityManager, DozeParameters dozeParameters,
137             StatusBarStateController statusBarStateController,
138             ConfigurationController configurationController,
139             KeyguardViewMediator keyguardViewMediator,
140             KeyguardBypassController keyguardBypassController,
141             @Background Executor backgroundExecutor,
142             SysuiColorExtractor colorExtractor,
143             DumpManager dumpManager,
144             KeyguardStateController keyguardStateController,
145             ScreenOffAnimationController screenOffAnimationController,
146             AuthController authController,
147             ShadeExpansionStateManager shadeExpansionStateManager,
148             ShadeWindowLogger logger) {
149         mContext = context;
150         mWindowManager = windowManager;
151         mActivityManager = activityManager;
152         mDozeParameters = dozeParameters;
153         mKeyguardStateController = keyguardStateController;
154         mLogger = logger;
155         mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
156         mLpChanged = new LayoutParams();
157         mKeyguardViewMediator = keyguardViewMediator;
158         mKeyguardBypassController = keyguardBypassController;
159         mBackgroundExecutor = backgroundExecutor;
160         mColorExtractor = colorExtractor;
161         mScreenOffAnimationController = screenOffAnimationController;
162         dumpManager.registerDumpable(getClass().getName(), this);
163         mAuthController = authController;
164         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
165         mLockScreenDisplayTimeout = context.getResources()
166                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
167         ((SysuiStatusBarStateController) statusBarStateController)
168                 .addCallback(mStateListener,
169                         SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
170         configurationController.addCallback(this);
171         shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
172         shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
173 
174         float desiredPreferredRefreshRate = context.getResources()
175                 .getInteger(R.integer.config_keyguardRefreshRate);
176         float actualPreferredRefreshRate = -1;
177         if (desiredPreferredRefreshRate > -1) {
178             for (Display.Mode displayMode : context.getDisplay().getSupportedModes()) {
179                 if (Math.abs(displayMode.getRefreshRate() - desiredPreferredRefreshRate) <= .1) {
180                     actualPreferredRefreshRate = displayMode.getRefreshRate();
181                     break;
182                 }
183             }
184         }
185 
186         mKeyguardPreferredRefreshRate = actualPreferredRefreshRate;
187 
188         // Running on the highest frame rate available can be expensive.
189         // Let's specify a preferred refresh rate, and allow higher FPS only when we
190         // know that we're not falsing (because we unlocked.)
191         mKeyguardMaxRefreshRate = context.getResources()
192                 .getInteger(R.integer.config_keyguardMaxRefreshRate);
193     }
194 
195     /**
196      * Register to receive notifications about status bar window state changes.
197      */
198     @Override
registerCallback(StatusBarWindowCallback callback)199     public void registerCallback(StatusBarWindowCallback callback) {
200         // Prevent adding duplicate callbacks
201         for (int i = 0; i < mCallbacks.size(); i++) {
202             if (mCallbacks.get(i).get() == callback) {
203                 return;
204             }
205         }
206         mCallbacks.add(new WeakReference<>(callback));
207     }
208 
209     @Override
unregisterCallback(StatusBarWindowCallback callback)210     public void unregisterCallback(StatusBarWindowCallback callback) {
211         for (int i = 0; i < mCallbacks.size(); i++) {
212             if (mCallbacks.get(i).get() == callback) {
213                 mCallbacks.remove(i);
214                 return;
215             }
216         }
217     }
218 
219     @VisibleForTesting
onShadeExpansionFullyChanged(Boolean isExpanded)220     void onShadeExpansionFullyChanged(Boolean isExpanded) {
221         if (mCurrentState.panelExpanded != isExpanded) {
222             mCurrentState.panelExpanded = isExpanded;
223             apply(mCurrentState);
224         }
225     }
226 
227     /**
228      * Register a listener to monitor scrims visibility
229      * @param listener A listener to monitor scrims visibility
230      */
231     @Override
setScrimsVisibilityListener(Consumer<Integer> listener)232     public void setScrimsVisibilityListener(Consumer<Integer> listener) {
233         if (listener != null && mScrimsVisibilityListener != listener) {
234             mScrimsVisibilityListener = listener;
235         }
236     }
237 
238     /**
239      * Adds the notification shade view to the window manager.
240      */
241     @Override
attach()242     public void attach() {
243         // Now that the notification shade encompasses the sliding panel and its
244         // translucent backdrop, the entire thing is made TRANSLUCENT and is
245         // hardware-accelerated.
246         mLp = new LayoutParams(
247                 ViewGroup.LayoutParams.MATCH_PARENT,
248                 ViewGroup.LayoutParams.MATCH_PARENT,
249                 LayoutParams.TYPE_NOTIFICATION_SHADE,
250                 LayoutParams.FLAG_NOT_FOCUSABLE
251                         | LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
252                         | LayoutParams.FLAG_SPLIT_TOUCH
253                         | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
254                         | LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
255                 PixelFormat.TRANSLUCENT);
256         mLp.token = new Binder();
257         mLp.gravity = Gravity.TOP;
258         mLp.setFitInsetsTypes(0 /* types */);
259         mLp.setTitle("NotificationShade");
260         mLp.packageName = mContext.getPackageName();
261         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
262         mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
263 
264         // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
265         // window manager which disables the transient show behavior.
266         // TODO: Clean this up once that behavior moves into the Shell.
267         mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
268         mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
269 
270         mWindowManager.addView(mWindowRootView, mLp);
271 
272         mLpChanged.copyFrom(mLp);
273         onThemeChanged();
274 
275         // Make the state consistent with KeyguardViewMediator#setupLocked during initialization.
276         if (mKeyguardViewMediator.isShowingAndNotOccluded()) {
277             setKeyguardShowing(true);
278         }
279     }
280 
281     @Override
setWindowRootView(ViewGroup view)282     public void setWindowRootView(ViewGroup view) {
283         mWindowRootView = view;
284     }
285 
286     @Override
getWindowRootView()287     public ViewGroup getWindowRootView() {
288         return mWindowRootView;
289     }
290 
291     @Override
setDozeScreenBrightness(int value)292     public void setDozeScreenBrightness(int value) {
293         mScreenBrightnessDoze = value / 255f;
294     }
295 
setKeyguardDark(boolean dark)296     private void setKeyguardDark(boolean dark) {
297         int vis = mWindowRootView.getSystemUiVisibility();
298         if (dark) {
299             vis = vis | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
300             vis = vis | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
301         } else {
302             vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
303             vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
304         }
305         mWindowRootView.setSystemUiVisibility(vis);
306     }
307 
applyKeyguardFlags(NotificationShadeWindowState state)308     private void applyKeyguardFlags(NotificationShadeWindowState state) {
309         final boolean keyguardOrAod = state.keyguardShowing
310                 || (state.dozing && mDozeParameters.getAlwaysOn());
311         if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
312                 || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
313             // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
314             // solid backdrop. Also, show it if we are currently animating between the
315             // keyguard and the surface behind the keyguard - we want to use the wallpaper as a
316             // backdrop for this animation.
317             mLpChanged.flags |= LayoutParams.FLAG_SHOW_WALLPAPER;
318         } else {
319             mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;
320         }
321 
322         if (state.dozing) {
323             mLpChanged.privateFlags |= LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
324         } else {
325             mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
326         }
327 
328         if (mKeyguardPreferredRefreshRate > 0) {
329             boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
330                     && !state.keyguardFadingAway && !state.keyguardGoingAway;
331             if (onKeyguard
332                     && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
333                 // both max and min display refresh rate must be set to take effect:
334                 mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardPreferredRefreshRate;
335                 mLpChanged.preferredMinDisplayRefreshRate = mKeyguardPreferredRefreshRate;
336             } else {
337                 mLpChanged.preferredMaxDisplayRefreshRate = 0;
338                 mLpChanged.preferredMinDisplayRefreshRate = 0;
339             }
340             Trace.setCounter("display_set_preferred_refresh_rate",
341                     (long) mLpChanged.preferredMaxDisplayRefreshRate);
342         } else if (mKeyguardMaxRefreshRate > 0) {
343             boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
344                     && state.statusBarState == StatusBarState.KEYGUARD
345                     && !state.keyguardFadingAway && !state.keyguardGoingAway;
346             if (state.dozing || bypassOnKeyguard) {
347                 mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardMaxRefreshRate;
348             } else {
349                 mLpChanged.preferredMaxDisplayRefreshRate = 0;
350             }
351             Trace.setCounter("display_max_refresh_rate",
352                     (long) mLpChanged.preferredMaxDisplayRefreshRate);
353         }
354 
355         if (state.bouncerShowing && !isDebuggable()) {
356             mLpChanged.flags |= LayoutParams.FLAG_SECURE;
357         } else {
358             mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
359         }
360     }
361 
isDebuggable()362     protected boolean isDebuggable() {
363         return Build.IS_DEBUGGABLE;
364     }
365 
adjustScreenOrientation(NotificationShadeWindowState state)366     private void adjustScreenOrientation(NotificationShadeWindowState state) {
367         if (state.bouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.dozing) {
368             if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
369                 mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
370             } else {
371                 mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
372             }
373         } else {
374             mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
375         }
376     }
377 
applyFocusableFlag(NotificationShadeWindowState state)378     private void applyFocusableFlag(NotificationShadeWindowState state) {
379         boolean panelFocusable = state.notificationShadeFocusable && state.panelExpanded;
380         if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
381                 || ENABLE_REMOTE_INPUT && state.remoteInputActive
382                 // Make the panel focusable if we're doing the screen off animation, since the light
383                 // reveal scrim is drawing in the panel and should consume touch events so that they
384                 // don't go to the app behind.
385                 || mScreenOffAnimationController.shouldIgnoreKeyguardTouches()) {
386             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
387             mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
388         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
389             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
390             // Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
391             if (state.keyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
392                 mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
393             } else {
394                 mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
395             }
396         } else {
397             mLpChanged.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
398             mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
399         }
400     }
401 
applyForceShowNavigationFlag(NotificationShadeWindowState state)402     private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
403         if (state.panelExpanded || state.bouncerShowing
404                 || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
405             mLpChanged.forciblyShownTypes |= WindowInsets.Type.navigationBars();
406         } else {
407             mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.navigationBars();
408         }
409     }
410 
applyVisibility(NotificationShadeWindowState state)411     private void applyVisibility(NotificationShadeWindowState state) {
412         boolean visible = isExpanded(state);
413         mLogger.logApplyVisibility(visible);
414         if (state.forcePluginOpen) {
415             if (mListener != null) {
416                 mListener.setWouldOtherwiseCollapse(visible);
417             }
418             visible = true;
419             mLogger.d("Visibility forced to be true");
420         }
421         if (mWindowRootView != null) {
422             if (visible) {
423                 mWindowRootView.setVisibility(View.VISIBLE);
424             } else {
425                 mWindowRootView.setVisibility(View.INVISIBLE);
426             }
427         }
428     }
429 
isExpanded(NotificationShadeWindowState state)430     private boolean isExpanded(NotificationShadeWindowState state) {
431         boolean isExpanded = !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded()
432                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
433                 || state.headsUpNotificationShowing
434                 || state.scrimsVisibility != ScrimController.TRANSPARENT)
435                 || state.backgroundBlurRadius > 0
436                 || state.launchingActivityFromNotification;
437         mLogger.logIsExpanded(isExpanded, state.forceWindowCollapsed,
438                 state.isKeyguardShowingAndNotOccluded(), state.panelVisible,
439                 state.keyguardFadingAway, state.bouncerShowing, state.headsUpNotificationShowing,
440                 state.scrimsVisibility != ScrimController.TRANSPARENT,
441                 state.backgroundBlurRadius > 0, state.launchingActivityFromNotification);
442         return isExpanded;
443     }
444 
applyFitsSystemWindows(NotificationShadeWindowState state)445     private void applyFitsSystemWindows(NotificationShadeWindowState state) {
446         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
447         if (mWindowRootView != null
448                 && mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) {
449             mWindowRootView.setFitsSystemWindows(fitsSystemWindows);
450             mWindowRootView.requestApplyInsets();
451         }
452     }
453 
applyUserActivityTimeout(NotificationShadeWindowState state)454     private void applyUserActivityTimeout(NotificationShadeWindowState state) {
455         if (state.isKeyguardShowingAndNotOccluded()
456                 && state.statusBarState == StatusBarState.KEYGUARD
457                 && !state.qsExpanded) {
458             mLpChanged.userActivityTimeout = state.bouncerShowing
459                     ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
460         } else {
461             mLpChanged.userActivityTimeout = -1;
462         }
463     }
464 
applyInputFeatures(NotificationShadeWindowState state)465     private void applyInputFeatures(NotificationShadeWindowState state) {
466         if (state.isKeyguardShowingAndNotOccluded()
467                 && state.statusBarState == StatusBarState.KEYGUARD
468                 && !state.qsExpanded && !state.forceUserActivity) {
469             mLpChanged.inputFeatures |=
470                     LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
471         } else {
472             mLpChanged.inputFeatures &=
473                     ~LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
474         }
475     }
476 
applyStatusBarColorSpaceAgnosticFlag(NotificationShadeWindowState state)477     private void applyStatusBarColorSpaceAgnosticFlag(NotificationShadeWindowState state) {
478         if (!isExpanded(state)) {
479             mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
480         } else {
481             mLpChanged.privateFlags &=
482                     ~LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
483         }
484     }
485 
applyWindowLayoutParams()486     private void applyWindowLayoutParams() {
487         if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
488             Trace.beginSection("updateViewLayout");
489             mWindowManager.updateViewLayout(mWindowRootView, mLp);
490             Trace.endSection();
491         }
492     }
493 
494     @Override
batchApplyWindowLayoutParams(Runnable scope)495     public void batchApplyWindowLayoutParams(Runnable scope) {
496         mDeferWindowLayoutParams++;
497         scope.run();
498         mDeferWindowLayoutParams--;
499         applyWindowLayoutParams();
500     }
501 
apply(NotificationShadeWindowState state)502     private void apply(NotificationShadeWindowState state) {
503         logState(state);
504         applyKeyguardFlags(state);
505         applyFocusableFlag(state);
506         applyForceShowNavigationFlag(state);
507         adjustScreenOrientation(state);
508         applyVisibility(state);
509         applyUserActivityTimeout(state);
510         applyInputFeatures(state);
511         applyFitsSystemWindows(state);
512         applyModalFlag(state);
513         applyBrightness(state);
514         applyHasTopUi(state);
515         applyNotTouchable(state);
516         applyStatusBarColorSpaceAgnosticFlag(state);
517         applyWindowLayoutParams();
518 
519         if (mHasTopUi != mHasTopUiChanged) {
520             mHasTopUi = mHasTopUiChanged;
521             mBackgroundExecutor.execute(() -> {
522                 try {
523                     mActivityManager.setHasTopUi(mHasTopUiChanged);
524                 } catch (RemoteException e) {
525                     Log.e(TAG, "Failed to call setHasTopUi", e);
526                 }
527 
528             });
529         }
530         notifyStateChangedCallbacks();
531     }
532 
logState(NotificationShadeWindowState state)533     private void logState(NotificationShadeWindowState state) {
534         mStateBuffer.insert(
535                 state.keyguardShowing,
536                 state.keyguardOccluded,
537                 state.keyguardNeedsInput,
538                 state.panelVisible,
539                 state.panelExpanded,
540                 state.notificationShadeFocusable,
541                 state.bouncerShowing,
542                 state.keyguardFadingAway,
543                 state.keyguardGoingAway,
544                 state.qsExpanded,
545                 state.headsUpNotificationShowing,
546                 state.lightRevealScrimOpaque,
547                 state.forceWindowCollapsed,
548                 state.forceDozeBrightness,
549                 state.forceUserActivity,
550                 state.launchingActivityFromNotification,
551                 state.mediaBackdropShowing,
552                 state.windowNotTouchable,
553                 state.componentsForcingTopUi,
554                 state.forceOpenTokens,
555                 state.statusBarState,
556                 state.remoteInputActive,
557                 state.forcePluginOpen,
558                 state.dozing,
559                 state.scrimsVisibility,
560                 state.backgroundBlurRadius
561         );
562     }
563 
564     @Override
notifyStateChangedCallbacks()565     public void notifyStateChangedCallbacks() {
566         // Copy callbacks to separate ArrayList to avoid concurrent modification
567         List<StatusBarWindowCallback> activeCallbacks = mCallbacks.stream()
568                 .map(Reference::get)
569                 .filter(Objects::nonNull)
570                 .collect(Collectors.toList());
571         for (StatusBarWindowCallback cb : activeCallbacks) {
572             cb.onStateChanged(mCurrentState.keyguardShowing,
573                     mCurrentState.keyguardOccluded,
574                     mCurrentState.keyguardGoingAway,
575                     mCurrentState.bouncerShowing,
576                     mCurrentState.dozing,
577                     mCurrentState.panelExpanded,
578                     mCurrentState.dreaming);
579         }
580     }
581 
applyModalFlag(NotificationShadeWindowState state)582     private void applyModalFlag(NotificationShadeWindowState state) {
583         if (state.headsUpNotificationShowing) {
584             mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCH_MODAL;
585         } else {
586             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCH_MODAL;
587         }
588     }
589 
applyBrightness(NotificationShadeWindowState state)590     private void applyBrightness(NotificationShadeWindowState state) {
591         if (state.forceDozeBrightness) {
592             mLpChanged.screenBrightness = mScreenBrightnessDoze;
593         } else {
594             mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
595         }
596     }
597 
applyHasTopUi(NotificationShadeWindowState state)598     private void applyHasTopUi(NotificationShadeWindowState state) {
599         mHasTopUiChanged = !state.componentsForcingTopUi.isEmpty() || isExpanded(state);
600     }
601 
applyNotTouchable(NotificationShadeWindowState state)602     private void applyNotTouchable(NotificationShadeWindowState state) {
603         if (state.windowNotTouchable) {
604             mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
605         } else {
606             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
607         }
608     }
609 
610     @Override
setTouchExclusionRegion(Region region)611     public void setTouchExclusionRegion(Region region) {
612         try {
613             final IWindowSession session = WindowManagerGlobal.getWindowSession();
614             session.updateTapExcludeRegion(
615                     IWindow.Stub.asInterface(getWindowRootView().getWindowToken()),
616                     region);
617         } catch (RemoteException e) {
618             Log.e(TAG, "could not update the tap exclusion region:" + e);
619         }
620     }
621 
622 
623     @Override
setKeyguardShowing(boolean showing)624     public void setKeyguardShowing(boolean showing) {
625         mCurrentState.keyguardShowing = showing;
626         apply(mCurrentState);
627     }
628 
629     @Override
setKeyguardOccluded(boolean occluded)630     public void setKeyguardOccluded(boolean occluded) {
631         mCurrentState.keyguardOccluded = occluded;
632         apply(mCurrentState);
633     }
634 
635     @Override
setKeyguardNeedsInput(boolean needsInput)636     public void setKeyguardNeedsInput(boolean needsInput) {
637         mCurrentState.keyguardNeedsInput = needsInput;
638         apply(mCurrentState);
639     }
640 
641     @Override
setPanelVisible(boolean visible)642     public void setPanelVisible(boolean visible) {
643         if (mCurrentState.panelVisible == visible
644                 && mCurrentState.notificationShadeFocusable == visible) {
645             return;
646         }
647         mLogger.logShadeVisibleAndFocusable(visible);
648         mCurrentState.panelVisible = visible;
649         mCurrentState.notificationShadeFocusable = visible;
650         apply(mCurrentState);
651     }
652 
653     @Override
setNotificationShadeFocusable(boolean focusable)654     public void setNotificationShadeFocusable(boolean focusable) {
655         mLogger.logShadeFocusable(focusable);
656         mCurrentState.notificationShadeFocusable = focusable;
657         apply(mCurrentState);
658     }
659 
660     @Override
setBouncerShowing(boolean showing)661     public void setBouncerShowing(boolean showing) {
662         mCurrentState.bouncerShowing = showing;
663         apply(mCurrentState);
664     }
665 
666     @Override
setBackdropShowing(boolean showing)667     public void setBackdropShowing(boolean showing) {
668         mCurrentState.mediaBackdropShowing = showing;
669         apply(mCurrentState);
670     }
671 
672     @Override
setKeyguardFadingAway(boolean keyguardFadingAway)673     public void setKeyguardFadingAway(boolean keyguardFadingAway) {
674         mCurrentState.keyguardFadingAway = keyguardFadingAway;
675         apply(mCurrentState);
676     }
677 
onQsExpansionChanged(Boolean expanded)678     private void onQsExpansionChanged(Boolean expanded) {
679         mCurrentState.qsExpanded = expanded;
680         apply(mCurrentState);
681     }
682 
683     @Override
setForceUserActivity(boolean forceUserActivity)684     public void setForceUserActivity(boolean forceUserActivity) {
685         mCurrentState.forceUserActivity = forceUserActivity;
686         apply(mCurrentState);
687     }
688 
689     @Override
setLaunchingActivity(boolean launching)690     public void setLaunchingActivity(boolean launching) {
691         mCurrentState.launchingActivityFromNotification = launching;
692         apply(mCurrentState);
693     }
694 
695     @Override
isLaunchingActivity()696     public boolean isLaunchingActivity() {
697         return mCurrentState.launchingActivityFromNotification;
698     }
699 
700     @Override
setScrimsVisibility(int scrimsVisibility)701     public void setScrimsVisibility(int scrimsVisibility) {
702         if (scrimsVisibility == mCurrentState.scrimsVisibility) {
703             return;
704         }
705         boolean wasExpanded = isExpanded(mCurrentState);
706         mCurrentState.scrimsVisibility = scrimsVisibility;
707         if (wasExpanded != isExpanded(mCurrentState)) {
708             apply(mCurrentState);
709         }
710         mScrimsVisibilityListener.accept(scrimsVisibility);
711     }
712 
713     /**
714      * Current blur level, controller by
715      * {@link com.android.systemui.statusbar.NotificationShadeDepthController}.
716      * @param backgroundBlurRadius Radius in pixels.
717      */
718     @Override
setBackgroundBlurRadius(int backgroundBlurRadius)719     public void setBackgroundBlurRadius(int backgroundBlurRadius) {
720         if (mCurrentState.backgroundBlurRadius == backgroundBlurRadius) {
721             return;
722         }
723         mCurrentState.backgroundBlurRadius = backgroundBlurRadius;
724         apply(mCurrentState);
725     }
726 
727     @Override
setHeadsUpShowing(boolean showing)728     public void setHeadsUpShowing(boolean showing) {
729         mCurrentState.headsUpNotificationShowing = showing;
730         apply(mCurrentState);
731     }
732 
733     @Override
setLightRevealScrimOpaque(boolean opaque)734     public void setLightRevealScrimOpaque(boolean opaque) {
735         if (mCurrentState.lightRevealScrimOpaque == opaque) {
736             return;
737         }
738         mCurrentState.lightRevealScrimOpaque = opaque;
739         apply(mCurrentState);
740     }
741 
742     /**
743      * @param state The {@link StatusBarStateController} of the status bar.
744      */
setStatusBarState(int state)745     private void setStatusBarState(int state) {
746         mCurrentState.statusBarState = state;
747         apply(mCurrentState);
748     }
749 
750     /**
751      * Force the window to be collapsed, even if it should theoretically be expanded.
752      * Used for when a heads-up comes in but we still need to wait for the touchable regions to
753      * be computed.
754      */
755     @Override
setForceWindowCollapsed(boolean force)756     public void setForceWindowCollapsed(boolean force) {
757         mCurrentState.forceWindowCollapsed = force;
758         apply(mCurrentState);
759     }
760 
761     @Override
onRemoteInputActive(boolean remoteInputActive)762     public void onRemoteInputActive(boolean remoteInputActive) {
763         mCurrentState.remoteInputActive = remoteInputActive;
764         apply(mCurrentState);
765     }
766 
767     /**
768      * Set whether the screen brightness is forced to the value we use for doze mode by the status
769      * bar window.
770      */
771     @Override
setForceDozeBrightness(boolean forceDozeBrightness)772     public void setForceDozeBrightness(boolean forceDozeBrightness) {
773         if (mCurrentState.forceDozeBrightness == forceDozeBrightness) {
774             return;
775         }
776         mCurrentState.forceDozeBrightness = forceDozeBrightness;
777         apply(mCurrentState);
778     }
779 
780     @Override
setDozing(boolean dozing)781     public void setDozing(boolean dozing) {
782         mCurrentState.dozing = dozing;
783         apply(mCurrentState);
784     }
785 
786     @Override
setDreaming(boolean dreaming)787     public void setDreaming(boolean dreaming) {
788         mCurrentState.dreaming = dreaming;
789         apply(mCurrentState);
790     }
791 
792     @Override
setForcePluginOpen(boolean forceOpen, Object token)793     public void setForcePluginOpen(boolean forceOpen, Object token) {
794         if (forceOpen) {
795             mCurrentState.forceOpenTokens.add(token);
796         } else {
797             mCurrentState.forceOpenTokens.remove(token);
798         }
799         final boolean previousForceOpenState = mCurrentState.forcePluginOpen;
800         mCurrentState.forcePluginOpen = !mCurrentState.forceOpenTokens.isEmpty();
801         if (previousForceOpenState != mCurrentState.forcePluginOpen) {
802             apply(mCurrentState);
803             if (mForcePluginOpenListener != null) {
804                 mForcePluginOpenListener.onChange(mCurrentState.forcePluginOpen);
805             }
806         }
807     }
808 
809     /**
810      * The forcePluginOpen state for the status bar.
811      */
812     @Override
getForcePluginOpen()813     public boolean getForcePluginOpen() {
814         return mCurrentState.forcePluginOpen;
815     }
816 
817     @Override
setNotTouchable(boolean notTouchable)818     public void setNotTouchable(boolean notTouchable) {
819         mCurrentState.windowNotTouchable = notTouchable;
820         apply(mCurrentState);
821     }
822 
823     /**
824      * Whether the status bar panel is expanded or not.
825      */
826     @Override
getPanelExpanded()827     public boolean getPanelExpanded() {
828         return mCurrentState.panelExpanded;
829     }
830 
831     @Override
setStateListener(OtherwisedCollapsedListener listener)832     public void setStateListener(OtherwisedCollapsedListener listener) {
833         mListener = listener;
834     }
835 
836     @Override
setForcePluginOpenListener(ForcePluginOpenListener listener)837     public void setForcePluginOpenListener(ForcePluginOpenListener listener) {
838         mForcePluginOpenListener = listener;
839     }
840 
841     @Override
dump(PrintWriter pw, String[] args)842     public void dump(PrintWriter pw, String[] args) {
843         pw.println(TAG + ":");
844         pw.println("  mKeyguardMaxRefreshRate=" + mKeyguardMaxRefreshRate);
845         pw.println("  mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate);
846         pw.println("  mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
847         pw.println(mCurrentState);
848         if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
849             Trace.beginSection("mWindowRootView.dump()");
850             mWindowRootView.getViewRootImpl().dump("  ", pw);
851             Trace.endSection();
852         }
853         Trace.beginSection("Table<State>");
854         new DumpsysTableLogger(
855                 TAG,
856                 NotificationShadeWindowState.TABLE_HEADERS,
857                 mStateBuffer.toList()
858         ).printTableData(pw);
859         Trace.endSection();
860     }
861 
862     @Override
isShowingWallpaper()863     public boolean isShowingWallpaper() {
864         return !mCurrentState.mediaBackdropShowing;
865     }
866 
867     @Override
onThemeChanged()868     public void onThemeChanged() {
869         if (mWindowRootView == null) {
870             return;
871         }
872 
873         final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
874         // Make sure we have the correct navbar/statusbar colors.
875         setKeyguardDark(useDarkText);
876     }
877 
878     @Override
onConfigChanged(Configuration newConfig)879     public void onConfigChanged(Configuration newConfig) {
880         final boolean newScreenRotationAllowed = mKeyguardStateController
881                 .isKeyguardScreenRotationAllowed();
882 
883         if (mLastKeyguardRotationAllowed != newScreenRotationAllowed) {
884             apply(mCurrentState);
885             mLastKeyguardRotationAllowed = newScreenRotationAllowed;
886         }
887     }
888 
889     /**
890      * When keyguard will be dismissed but didn't start animation yet.
891      */
892     @Override
setKeyguardGoingAway(boolean goingAway)893     public void setKeyguardGoingAway(boolean goingAway) {
894         mCurrentState.keyguardGoingAway = goingAway;
895         apply(mCurrentState);
896     }
897 
898     /**
899      * SystemUI may need top-ui to avoid jank when performing animations.  After the
900      * animation is performed, the component should remove itself from the list of features that
901      * are forcing SystemUI to be top-ui.
902      */
903     @Override
setRequestTopUi(boolean requestTopUi, String componentTag)904     public void setRequestTopUi(boolean requestTopUi, String componentTag) {
905         if (requestTopUi) {
906             mCurrentState.componentsForcingTopUi.add(componentTag);
907         } else {
908             mCurrentState.componentsForcingTopUi.remove(componentTag);
909         }
910         apply(mCurrentState);
911     }
912 
913     private final StateListener mStateListener = new StateListener() {
914         @Override
915         public void onStateChanged(int newState) {
916             setStatusBarState(newState);
917         }
918 
919         @Override
920         public void onDozingChanged(boolean isDozing) {
921             setDozing(isDozing);
922         }
923 
924         @Override
925         public void onDreamingChanged(boolean isDreaming) {
926             setDreaming(isDreaming);
927         }
928     };
929 }
930