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