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