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