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.Color;
27 import android.view.InsetsFlags;
28 import android.view.ViewDebug;
29 import android.view.WindowInsetsController.Appearance;
30 
31 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
32 import com.android.internal.view.AppearanceRegion;
33 import com.android.systemui.Dumpable;
34 import com.android.systemui.R;
35 import com.android.systemui.plugins.DarkIconDispatcher;
36 import com.android.systemui.shared.system.QuickStepContract;
37 import com.android.systemui.statusbar.policy.BatteryController;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 
42 import javax.inject.Inject;
43 import javax.inject.Singleton;
44 
45 /**
46  * Controls how light status bar flag applies to the icons.
47  */
48 @Singleton
49 public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {
50 
51     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
52 
53     private final SysuiDarkIconDispatcher mStatusBarIconController;
54     private final BatteryController mBatteryController;
55     private BiometricUnlockController mBiometricUnlockController;
56 
57     private LightBarTransitionsController mNavigationBarController;
58     private @Appearance int mAppearance;
59     private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
60     private int mStatusBarMode;
61     private int mNavigationBarMode;
62     private int mNavigationMode;
63     private final Color mDarkModeColor;
64 
65     /**
66      * Whether the navigation bar should be light factoring in already how much alpha the scrim has
67      */
68     private boolean mNavigationLight;
69 
70     /**
71      * Whether the flags indicate that a light status bar is requested. This doesn't factor in the
72      * scrim alpha yet.
73      */
74     private boolean mHasLightNavigationBar;
75 
76     /**
77      * {@code true} if {@link #mHasLightNavigationBar} should be ignored and forcefully make
78      * {@link #mNavigationLight} {@code false}.
79      */
80     private boolean mForceDarkForScrim;
81 
82     private boolean mQsCustomizing;
83 
84     private boolean mDirectReplying;
85     private boolean mNavbarColorManagedByIme;
86 
87     @Inject
LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController)88     public LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher,
89             BatteryController batteryController, NavigationModeController navModeController) {
90         mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
91         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
92         mBatteryController = batteryController;
93         mBatteryController.addCallback(this);
94         mNavigationMode = navModeController.addListener((mode) -> {
95             mNavigationMode = mode;
96         });
97     }
98 
setNavigationBar(LightBarTransitionsController navigationBar)99     public void setNavigationBar(LightBarTransitionsController navigationBar) {
100         mNavigationBarController = navigationBar;
101         updateNavigation();
102     }
103 
setBiometricUnlockController( BiometricUnlockController biometricUnlockController)104     public void setBiometricUnlockController(
105             BiometricUnlockController biometricUnlockController) {
106         mBiometricUnlockController = biometricUnlockController;
107     }
108 
onStatusBarAppearanceChanged(AppearanceRegion[] appearanceRegions, boolean sbModeChanged, int statusBarMode, boolean navbarColorManagedByIme)109     void onStatusBarAppearanceChanged(AppearanceRegion[] appearanceRegions, boolean sbModeChanged,
110             int statusBarMode, boolean navbarColorManagedByIme) {
111         final int numStacks = appearanceRegions.length;
112         boolean stackAppearancesChanged = mAppearanceRegions.length != numStacks;
113         for (int i = 0; i < numStacks && !stackAppearancesChanged; i++) {
114             stackAppearancesChanged |= !appearanceRegions[i].equals(mAppearanceRegions[i]);
115         }
116         if (stackAppearancesChanged || sbModeChanged) {
117             mAppearanceRegions = appearanceRegions;
118             onStatusBarModeChanged(statusBarMode);
119         }
120         mNavbarColorManagedByIme = navbarColorManagedByIme;
121     }
122 
onStatusBarModeChanged(int newBarMode)123     void onStatusBarModeChanged(int newBarMode) {
124         mStatusBarMode = newBarMode;
125         updateStatus();
126     }
127 
onNavigationBarAppearanceChanged(@ppearance int appearance, boolean nbModeChanged, int navigationBarMode, boolean navbarColorManagedByIme)128     void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
129             int navigationBarMode, boolean navbarColorManagedByIme) {
130         int diff = appearance ^ mAppearance;
131         if ((diff & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0 || nbModeChanged) {
132             final boolean last = mNavigationLight;
133             mHasLightNavigationBar = isLight(appearance, navigationBarMode,
134                     APPEARANCE_LIGHT_NAVIGATION_BARS);
135             mNavigationLight = mHasLightNavigationBar
136                     && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
137                     && !mQsCustomizing;
138             if (mNavigationLight != last) {
139                 updateNavigation();
140             }
141         }
142         mAppearance = appearance;
143         mNavigationBarMode = navigationBarMode;
144         mNavbarColorManagedByIme = navbarColorManagedByIme;
145     }
146 
onNavigationBarModeChanged(int newBarMode)147     void onNavigationBarModeChanged(int newBarMode) {
148         mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
149     }
150 
reevaluate()151     private void reevaluate() {
152         onStatusBarAppearanceChanged(mAppearanceRegions, true /* sbModeChange */, mStatusBarMode,
153                 mNavbarColorManagedByIme);
154         onNavigationBarAppearanceChanged(mAppearance, true /* nbModeChanged */,
155                 mNavigationBarMode, mNavbarColorManagedByIme);
156     }
157 
setQsCustomizing(boolean customizing)158     public void setQsCustomizing(boolean customizing) {
159         if (mQsCustomizing == customizing) return;
160         mQsCustomizing = customizing;
161         reevaluate();
162     }
163 
164     /**
165      * Sets whether the direct-reply is in use or not.
166      * @param directReplying {@code true} when the direct-reply is in-use.
167      */
setDirectReplying(boolean directReplying)168     public void setDirectReplying(boolean directReplying) {
169         if (mDirectReplying == directReplying) return;
170         mDirectReplying = directReplying;
171         reevaluate();
172     }
173 
setScrimState(ScrimState scrimState, float scrimBehindAlpha, GradientColors scrimInFrontColor)174     public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
175             GradientColors scrimInFrontColor) {
176         boolean forceDarkForScrimLast = mForceDarkForScrim;
177         // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
178         // This enables IMEs to control the navigation bar color.
179         // For other cases, scrim should be able to veto the light navigation bar.
180         mForceDarkForScrim = scrimState != ScrimState.BOUNCER
181                 && scrimState != ScrimState.BOUNCER_SCRIMMED
182                 && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD
183                 && !scrimInFrontColor.supportsDarkText();
184         if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
185             reevaluate();
186         }
187     }
188 
isLight(int appearance, int barMode, int flag)189     private static boolean isLight(int appearance, int barMode, int flag) {
190         final boolean isTransparentBar = (barMode == MODE_TRANSPARENT
191                 || barMode == MODE_LIGHTS_OUT_TRANSPARENT);
192         final boolean light = (appearance & flag) != 0;
193         return isTransparentBar && light;
194     }
195 
animateChange()196     private boolean animateChange() {
197         if (mBiometricUnlockController == null) {
198             return false;
199         }
200         int unlockMode = mBiometricUnlockController.getMode();
201         return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
202                 && unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
203     }
204 
updateStatus()205     private void updateStatus() {
206         final int numStacks = mAppearanceRegions.length;
207         int numLightStacks = 0;
208 
209         // We can only have maximum one light stack.
210         int indexLightStack = -1;
211 
212         for (int i = 0; i < numStacks; i++) {
213             if (isLight(mAppearanceRegions[i].getAppearance(), mStatusBarMode,
214                     APPEARANCE_LIGHT_STATUS_BARS)) {
215                 numLightStacks++;
216                 indexLightStack = i;
217             }
218         }
219 
220         // If all stacks are light, all icons get dark.
221         if (numLightStacks == numStacks) {
222             mStatusBarIconController.setIconsDarkArea(null);
223             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
224 
225         }
226 
227         // If no one is light, all icons become white.
228         else if (numLightStacks == 0) {
229             mStatusBarIconController.getTransitionsController().setIconsDark(
230                     false, animateChange());
231         }
232 
233         // Not the same for every stack, magic!
234         else {
235             mStatusBarIconController.setIconsDarkArea(
236                     mAppearanceRegions[indexLightStack].getBounds());
237             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
238         }
239     }
240 
updateNavigation()241     private void updateNavigation() {
242         if (mNavigationBarController != null
243                 && !QuickStepContract.isGesturalMode(mNavigationMode)) {
244             mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
245         }
246     }
247 
248     @Override
onPowerSaveChanged(boolean isPowerSave)249     public void onPowerSaveChanged(boolean isPowerSave) {
250         reevaluate();
251     }
252 
253     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)254     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
255         pw.println("LightBarController: ");
256         pw.print(" mAppearance="); pw.println(ViewDebug.flagsToString(
257                 InsetsFlags.class, "appearance", mAppearance));
258         final int numStacks = mAppearanceRegions.length;
259         for (int i = 0; i < numStacks; i++) {
260             final boolean isLight = isLight(mAppearanceRegions[i].getAppearance(), mStatusBarMode,
261                     APPEARANCE_LIGHT_STATUS_BARS);
262             pw.print(" stack #"); pw.print(i); pw.print(": ");
263             pw.print(mAppearanceRegions[i].toString()); pw.print(" isLight="); pw.println(isLight);
264         }
265 
266         pw.print(" mNavigationLight="); pw.print(mNavigationLight);
267         pw.print(" mHasLightNavigationBar="); pw.println(mHasLightNavigationBar);
268 
269         pw.print(" mStatusBarMode="); pw.print(mStatusBarMode);
270         pw.print(" mNavigationBarMode="); pw.println(mNavigationBarMode);
271 
272         pw.print(" mForceDarkForScrim="); pw.print(mForceDarkForScrim);
273         pw.print(" mQsCustomizing="); pw.print(mQsCustomizing);
274         pw.print(" mDirectReplying="); pw.println(mDirectReplying);
275         pw.print(" mNavbarColorManagedByIme="); pw.println(mNavbarColorManagedByIme);
276 
277         pw.println();
278 
279         LightBarTransitionsController transitionsController =
280                 mStatusBarIconController.getTransitionsController();
281         if (transitionsController != null) {
282             pw.println(" StatusBarTransitionsController:");
283             transitionsController.dump(fd, pw, args);
284             pw.println();
285         }
286 
287         if (mNavigationBarController != null) {
288             pw.println(" NavigationBarTransitionsController:");
289             mNavigationBarController.dump(fd, pw, args);
290             pw.println();
291         }
292     }
293 }
294