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