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