• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.statusbar.phone;
18 
19 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
20 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
21 
22 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
23 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
24 
25 import android.content.Context;
26 import android.graphics.Rect;
27 import android.util.Log;
28 import android.view.InsetsFlags;
29 import android.view.ViewDebug;
30 import android.view.WindowInsetsController.Appearance;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 
35 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
36 import com.android.internal.view.AppearanceRegion;
37 import com.android.systemui.CoreStartable;
38 import com.android.systemui.Dumpable;
39 import com.android.systemui.dagger.SysUISingleton;
40 import com.android.systemui.dump.DumpManager;
41 import com.android.systemui.navigationbar.NavigationModeController;
42 import com.android.systemui.plugins.DarkIconDispatcher;
43 import com.android.systemui.settings.DisplayTracker;
44 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
45 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
46 import com.android.systemui.statusbar.policy.BatteryController;
47 import com.android.systemui.util.Compile;
48 import com.android.systemui.util.kotlin.JavaAdapter;
49 
50 import java.io.PrintWriter;
51 import java.util.ArrayList;
52 
53 import javax.inject.Inject;
54 
55 /**
56  * Controls how light status bar flag applies to the icons.
57  */
58 @SysUISingleton
59 public class LightBarController implements
60         BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
61 
62     private static final String TAG = "LightBarController";
63     private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
64     private static final boolean DEBUG_LOGS = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
65 
66     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
67 
68     private final JavaAdapter mJavaAdapter;
69     private final SysuiDarkIconDispatcher mStatusBarIconController;
70     private final BatteryController mBatteryController;
71     private final StatusBarModeRepositoryStore mStatusBarModeRepository;
72     private BiometricUnlockController mBiometricUnlockController;
73 
74     private LightBarTransitionsController mNavigationBarController;
75     private @Appearance int mAppearance;
76     private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
77     private int mStatusBarMode;
78     private BoundsPair mStatusBarBounds = new BoundsPair(new Rect(), new Rect());
79     private int mNavigationBarMode;
80     private int mNavigationMode;
81 
82     /**
83      * Whether the navigation bar should be light factoring in already how much alpha the scrim has.
84      * "Light" refers to the background color of the navigation bar, so when this is true,
85      * it's referring to a state where the navigation bar icons are tinted dark.
86      */
87     private boolean mNavigationLight;
88 
89     /**
90      * Whether the flags indicate that a light navigation bar is requested.
91      * "Light" refers to the background color of the navigation bar, so when this is true,
92      * it's referring to a state where the navigation bar icons would be tinted dark.
93      * This doesn't factor in the scrim alpha yet.
94      */
95     private boolean mHasLightNavigationBar;
96 
97     /**
98      * {@code true} if {@link #mHasLightNavigationBar} should be ignored and forcefully make
99      * {@link #mNavigationLight} {@code false}.
100      */
101     private boolean mForceDarkForScrim;
102     /**
103      * {@code true} if {@link #mHasLightNavigationBar} should be ignored and forcefully make
104      * {@link #mNavigationLight} {@code true}.
105      */
106     private boolean mForceLightForScrim;
107 
108     private boolean mQsCustomizing;
109     private boolean mQsExpanded;
110     private boolean mBouncerVisible;
111     private boolean mGlobalActionsVisible;
112 
113     private boolean mDirectReplying;
114     private boolean mNavbarColorManagedByIme;
115 
116     private boolean mIsCustomizingForBackNav;
117 
118     private String mLastSetScrimStateLog;
119     private String mLastNavigationBarAppearanceChangedLog;
120     private StringBuilder mLogStringBuilder = null;
121 
122     @Inject
LightBarController( Context ctx, JavaAdapter javaAdapter, DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, StatusBarModeRepositoryStore statusBarModeRepository, DumpManager dumpManager, DisplayTracker displayTracker)123     public LightBarController(
124             Context ctx,
125             JavaAdapter javaAdapter,
126             DarkIconDispatcher darkIconDispatcher,
127             BatteryController batteryController,
128             NavigationModeController navModeController,
129             StatusBarModeRepositoryStore statusBarModeRepository,
130             DumpManager dumpManager,
131             DisplayTracker displayTracker) {
132         mJavaAdapter = javaAdapter;
133         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
134         mBatteryController = batteryController;
135         mBatteryController.addCallback(this);
136         mStatusBarModeRepository = statusBarModeRepository;
137         mNavigationMode = navModeController.addListener((mode) -> {
138             mNavigationMode = mode;
139         });
140 
141         if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) {
142             dumpManager.registerDumpable(getClass().getSimpleName(), this);
143         }
144     }
145 
146     @Override
start()147     public void start() {
148         mJavaAdapter.alwaysCollectFlow(
149                 mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
150                 this::onStatusBarAppearanceChanged);
151     }
152 
setNavigationBar(LightBarTransitionsController navigationBar)153     public void setNavigationBar(LightBarTransitionsController navigationBar) {
154         mNavigationBarController = navigationBar;
155         updateNavigation();
156     }
157 
setBiometricUnlockController( BiometricUnlockController biometricUnlockController)158     public void setBiometricUnlockController(
159             BiometricUnlockController biometricUnlockController) {
160         mBiometricUnlockController = biometricUnlockController;
161     }
162 
onStatusBarAppearanceChanged(@ullable StatusBarAppearance params)163     private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) {
164         if (params == null) {
165             return;
166         }
167         int newStatusBarMode = params.getMode().toTransitionModeInt();
168         boolean sbModeChanged = mStatusBarMode != newStatusBarMode;
169         mStatusBarMode = newStatusBarMode;
170 
171         boolean sbBoundsChanged = !mStatusBarBounds.equals(params.getBounds());
172         mStatusBarBounds = params.getBounds();
173 
174         onStatusBarAppearanceChanged(
175                 params.getAppearanceRegions().toArray(new AppearanceRegion[0]),
176                 sbModeChanged,
177                 sbBoundsChanged,
178                 params.getNavbarColorManagedByIme());
179     }
180 
onStatusBarAppearanceChanged( AppearanceRegion[] appearanceRegions, boolean sbModeChanged, boolean sbBoundsChanged, boolean navbarColorManagedByIme)181     private void onStatusBarAppearanceChanged(
182             AppearanceRegion[] appearanceRegions,
183             boolean sbModeChanged,
184             boolean sbBoundsChanged,
185             boolean navbarColorManagedByIme) {
186         final int numStacks = appearanceRegions.length;
187         boolean stackAppearancesChanged = mAppearanceRegions.length != numStacks;
188         for (int i = 0; i < numStacks && !stackAppearancesChanged; i++) {
189             stackAppearancesChanged |= !appearanceRegions[i].equals(mAppearanceRegions[i]);
190         }
191 
192         if (stackAppearancesChanged
193                 || sbModeChanged
194                 // Be sure to re-draw when the status bar bounds have changed because the status bar
195                 // icons may have moved to be part of a different appearance region. See b/301605450
196                 || sbBoundsChanged
197                 || mIsCustomizingForBackNav) {
198             mAppearanceRegions = appearanceRegions;
199             updateStatus(mAppearanceRegions);
200             mIsCustomizingForBackNav = false;
201         }
202         mNavbarColorManagedByIme = navbarColorManagedByIme;
203     }
204 
onNavigationBarAppearanceChanged(@ppearance int appearance, boolean nbModeChanged, int navigationBarMode, boolean navbarColorManagedByIme)205     public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
206             int navigationBarMode, boolean navbarColorManagedByIme) {
207         int diff = appearance ^ mAppearance;
208         if ((diff & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0 || nbModeChanged) {
209             final boolean last = mNavigationLight;
210             mHasLightNavigationBar = isLight(appearance, navigationBarMode,
211                     APPEARANCE_LIGHT_NAVIGATION_BARS);
212             final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
213             final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
214             final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
215             final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
216             final boolean darkForTop = darkForQs || mGlobalActionsVisible;
217             mNavigationLight =
218                     ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
219             if (DEBUG_NAVBAR) {
220                 mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
221                         .append("onNavigationBarAppearanceChanged()")
222                         .append(" appearance=").append(appearance)
223                         .append(" nbModeChanged=").append(nbModeChanged)
224                         .append(" navigationBarMode=").append(navigationBarMode)
225                         .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
226                         .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
227                         .append(" ignoreScrimForce=").append(ignoreScrimForce)
228                         .append(" darkForScrim=").append(darkForScrim)
229                         .append(" lightForScrim=").append(lightForScrim)
230                         .append(" darkForQs=").append(darkForQs)
231                         .append(" darkForTop=").append(darkForTop)
232                         .append(" mNavigationLight=").append(mNavigationLight)
233                         .append(" last=").append(last)
234                         .append(" timestamp=").append(System.currentTimeMillis())
235                         .toString();
236                 if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
237             }
238             if (mNavigationLight != last) {
239                 updateNavigation();
240             }
241         }
242         mAppearance = appearance;
243         mNavigationBarMode = navigationBarMode;
244         mNavbarColorManagedByIme = navbarColorManagedByIme;
245     }
246 
onNavigationBarModeChanged(int newBarMode)247     public void onNavigationBarModeChanged(int newBarMode) {
248         mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
249     }
250 
reevaluate()251     private void reevaluate() {
252         onStatusBarAppearanceChanged(
253                 mAppearanceRegions,
254                 /* sbModeChanged= */ true,
255                 /* sbBoundsChanged= */ true,
256                 mNavbarColorManagedByIme);
257         onNavigationBarAppearanceChanged(mAppearance, true /* nbModeChanged */,
258                 mNavigationBarMode, mNavbarColorManagedByIme);
259     }
260 
setQsCustomizing(boolean customizing)261     public void setQsCustomizing(boolean customizing) {
262         if (mQsCustomizing == customizing) return;
263         mQsCustomizing = customizing;
264         reevaluate();
265     }
266 
267     /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */
setQsExpanded(boolean expanded)268     public void setQsExpanded(boolean expanded) {
269         if (mQsExpanded == expanded) return;
270         mQsExpanded = expanded;
271         reevaluate();
272     }
273 
274     /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */
setGlobalActionsVisible(boolean visible)275     public void setGlobalActionsVisible(boolean visible) {
276         if (mGlobalActionsVisible == visible) return;
277         mGlobalActionsVisible = visible;
278         reevaluate();
279     }
280 
281     /**
282      * Controls the light status bar temporarily for back navigation.
283      * @param appearance the custmoized appearance.
284      */
customizeStatusBarAppearance(AppearanceRegion appearance)285     public void customizeStatusBarAppearance(AppearanceRegion appearance) {
286         if (appearance != null) {
287             final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>();
288             appearancesList.add(appearance);
289             for (int i = 0; i < mAppearanceRegions.length; i++) {
290                 final AppearanceRegion ar = mAppearanceRegions[i];
291                 if (appearance.getBounds().contains(ar.getBounds())) {
292                     continue;
293                 }
294                 appearancesList.add(ar);
295             }
296 
297             final AppearanceRegion[] newAppearances = new AppearanceRegion[appearancesList.size()];
298             updateStatus(appearancesList.toArray(newAppearances));
299             mIsCustomizingForBackNav = true;
300         } else {
301             mIsCustomizingForBackNav = false;
302             updateStatus(mAppearanceRegions);
303         }
304     }
305 
306     /**
307      * Sets whether the direct-reply is in use or not.
308      * @param directReplying {@code true} when the direct-reply is in-use.
309      */
setDirectReplying(boolean directReplying)310     public void setDirectReplying(boolean directReplying) {
311         if (mDirectReplying == directReplying) return;
312         mDirectReplying = directReplying;
313         reevaluate();
314     }
315 
setScrimState(ScrimState scrimState, float scrimBehindAlpha, GradientColors scrimInFrontColor)316     public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
317             GradientColors scrimInFrontColor) {
318         boolean bouncerVisibleLast = mBouncerVisible;
319         boolean forceDarkForScrimLast = mForceDarkForScrim;
320         boolean forceLightForScrimLast = mForceLightForScrim;
321         mBouncerVisible =
322                 scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
323         final boolean forceForScrim = mBouncerVisible
324                 || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
325         final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
326 
327         mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
328         mForceLightForScrim = forceForScrim && scrimColorIsLight;
329         if (mBouncerVisible != bouncerVisibleLast) {
330             reevaluate();
331         } else if (mHasLightNavigationBar) {
332             if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
333         } else {
334             if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
335         }
336         if (DEBUG_NAVBAR) {
337             mLastSetScrimStateLog = getLogStringBuilder()
338                     .append("setScrimState()")
339                     .append(" scrimState=").append(scrimState)
340                     .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
341                     .append(" scrimInFrontColor=").append(scrimInFrontColor)
342                     .append(" forceForScrim=").append(forceForScrim)
343                     .append(" scrimColorIsLight=").append(scrimColorIsLight)
344                     .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
345                     .append(" mBouncerVisible=").append(mBouncerVisible)
346                     .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
347                     .append(" mForceLightForScrim=").append(mForceLightForScrim)
348                     .append(" timestamp=").append(System.currentTimeMillis())
349                     .toString();
350             if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
351         }
352     }
353 
354     @NonNull
getLogStringBuilder()355     private StringBuilder getLogStringBuilder() {
356         if (mLogStringBuilder == null) {
357             mLogStringBuilder = new StringBuilder();
358         }
359         mLogStringBuilder.setLength(0);
360         return mLogStringBuilder;
361     }
362 
isLight(int appearance, int barMode, int flag)363     private static boolean isLight(int appearance, int barMode, int flag) {
364         final boolean isTransparentBar = (barMode == MODE_TRANSPARENT
365                 || barMode == MODE_LIGHTS_OUT_TRANSPARENT);
366         final boolean light = (appearance & flag) != 0;
367         return isTransparentBar && light;
368     }
369 
animateChange()370     private boolean animateChange() {
371         if (mBiometricUnlockController == null) {
372             return false;
373         }
374         int unlockMode = mBiometricUnlockController.getMode();
375         return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
376                 && unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
377     }
378 
updateStatus(AppearanceRegion[] appearanceRegions)379     private void updateStatus(AppearanceRegion[] appearanceRegions) {
380         final int numStacks = appearanceRegions.length;
381         final ArrayList<Rect> lightBarBounds = new ArrayList<>();
382 
383         for (int i = 0; i < numStacks; i++) {
384             final AppearanceRegion ar = appearanceRegions[i];
385             if (isLight(ar.getAppearance(), mStatusBarMode, APPEARANCE_LIGHT_STATUS_BARS)) {
386                 lightBarBounds.add(ar.getBounds());
387             }
388         }
389 
390         // If no one is light, all icons become white.
391         if (lightBarBounds.isEmpty()) {
392             mStatusBarIconController.getTransitionsController().setIconsDark(
393                     false, animateChange());
394         }
395 
396         // If all stacks are light, all icons get dark.
397         else if (lightBarBounds.size() == numStacks) {
398             mStatusBarIconController.setIconsDarkArea(null);
399             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
400         }
401 
402         // Not the same for every stack, magic!
403         else {
404             mStatusBarIconController.setIconsDarkArea(lightBarBounds);
405             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
406         }
407     }
408 
updateNavigation()409     private void updateNavigation() {
410         if (mNavigationBarController != null
411                 && mNavigationBarController.supportsIconTintForNavMode(mNavigationMode)) {
412             mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
413         }
414     }
415 
416     @Override
onPowerSaveChanged(boolean isPowerSave)417     public void onPowerSaveChanged(boolean isPowerSave) {
418         reevaluate();
419     }
420 
421     @Override
dump(PrintWriter pw, String[] args)422     public void dump(PrintWriter pw, String[] args) {
423         pw.println("LightBarController: ");
424         pw.print(" mAppearance="); pw.println(ViewDebug.flagsToString(
425                 InsetsFlags.class, "appearance", mAppearance));
426         final int numStacks = mAppearanceRegions.length;
427         for (int i = 0; i < numStacks; i++) {
428             final boolean isLight = isLight(mAppearanceRegions[i].getAppearance(), mStatusBarMode,
429                     APPEARANCE_LIGHT_STATUS_BARS);
430             pw.print(" stack #"); pw.print(i); pw.print(": ");
431             pw.print(mAppearanceRegions[i].toString()); pw.print(" isLight="); pw.println(isLight);
432         }
433 
434         pw.print(" mNavigationLight="); pw.println(mNavigationLight);
435         pw.print(" mHasLightNavigationBar="); pw.println(mHasLightNavigationBar);
436         pw.println();
437         pw.print(" mStatusBarMode="); pw.print(mStatusBarMode);
438         pw.print(" mNavigationBarMode="); pw.println(mNavigationBarMode);
439         pw.println();
440         pw.print(" mForceDarkForScrim="); pw.println(mForceDarkForScrim);
441         pw.print(" mForceLightForScrim="); pw.println(mForceLightForScrim);
442         pw.println();
443         pw.print(" mQsCustomizing="); pw.println(mQsCustomizing);
444         pw.print(" mQsExpanded="); pw.println(mQsExpanded);
445         pw.print(" mBouncerVisible="); pw.println(mBouncerVisible);
446         pw.print(" mGlobalActionsVisible="); pw.println(mGlobalActionsVisible);
447         pw.print(" mDirectReplying="); pw.println(mDirectReplying);
448         pw.print(" mNavbarColorManagedByIme="); pw.println(mNavbarColorManagedByIme);
449         pw.println();
450         pw.println(" Recent Calculation Logs:");
451         pw.print("   "); pw.println(mLastSetScrimStateLog);
452         pw.print("   "); pw.println(mLastNavigationBarAppearanceChangedLog);
453 
454         pw.println();
455 
456         LightBarTransitionsController transitionsController =
457                 mStatusBarIconController.getTransitionsController();
458         if (transitionsController != null) {
459             pw.println(" StatusBarTransitionsController:");
460             transitionsController.dump(pw, args);
461             pw.println();
462         }
463 
464         if (mNavigationBarController != null) {
465             pw.println(" NavigationBarTransitionsController:");
466             mNavigationBarController.dump(pw, args);
467             pw.println();
468         }
469     }
470 
471     /**
472      * Injectable factory for creating a {@link LightBarController}.
473      */
474     public static class Factory {
475         private final JavaAdapter mJavaAdapter;
476         private final DarkIconDispatcher mDarkIconDispatcher;
477         private final BatteryController mBatteryController;
478         private final NavigationModeController mNavModeController;
479         private final StatusBarModeRepositoryStore mStatusBarModeRepository;
480         private final DumpManager mDumpManager;
481         private final DisplayTracker mDisplayTracker;
482 
483         @Inject
Factory( JavaAdapter javaAdapter, DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, StatusBarModeRepositoryStore statusBarModeRepository, DumpManager dumpManager, DisplayTracker displayTracker)484         public Factory(
485                 JavaAdapter javaAdapter,
486                 DarkIconDispatcher darkIconDispatcher,
487                 BatteryController batteryController,
488                 NavigationModeController navModeController,
489                 StatusBarModeRepositoryStore statusBarModeRepository,
490                 DumpManager dumpManager,
491                 DisplayTracker displayTracker) {
492             mJavaAdapter = javaAdapter;
493             mDarkIconDispatcher = darkIconDispatcher;
494             mBatteryController = batteryController;
495             mNavModeController = navModeController;
496             mStatusBarModeRepository = statusBarModeRepository;
497             mDumpManager = dumpManager;
498             mDisplayTracker = displayTracker;
499         }
500 
501         /** Create an {@link LightBarController} */
create(Context context)502         public LightBarController create(Context context) {
503             return new LightBarController(
504                     context,
505                     mJavaAdapter,
506                     mDarkIconDispatcher,
507                     mBatteryController,
508                     mNavModeController,
509                     mStatusBarModeRepository,
510                     mDumpManager,
511                     mDisplayTracker);
512         }
513     }
514 }
515