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