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.util.Log; 29 import android.view.InsetsFlags; 30 import android.view.ViewDebug; 31 import android.view.WindowInsetsController.Appearance; 32 33 import androidx.annotation.NonNull; 34 35 import com.android.internal.colorextraction.ColorExtractor.GradientColors; 36 import com.android.internal.view.AppearanceRegion; 37 import com.android.systemui.Dumpable; 38 import com.android.systemui.R; 39 import com.android.systemui.dagger.SysUISingleton; 40 import com.android.systemui.dump.DumpManager; 41 import com.android.systemui.navigationbar.NavigationModeController; 42 import com.android.systemui.plugins.DarkIconDispatcher; 43 import com.android.systemui.settings.DisplayTracker; 44 import com.android.systemui.statusbar.policy.BatteryController; 45 import com.android.systemui.util.Compile; 46 47 import java.io.PrintWriter; 48 import java.util.ArrayList; 49 50 import javax.inject.Inject; 51 52 /** 53 * Controls how light status bar flag applies to the icons. 54 */ 55 @SysUISingleton 56 public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable { 57 58 private static final String TAG = "LightBarController"; 59 private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG; 60 private static final boolean DEBUG_LOGS = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); 61 62 private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f; 63 64 private final SysuiDarkIconDispatcher mStatusBarIconController; 65 private final BatteryController mBatteryController; 66 private BiometricUnlockController mBiometricUnlockController; 67 68 private LightBarTransitionsController mNavigationBarController; 69 private @Appearance int mAppearance; 70 private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0]; 71 private int mStatusBarMode; 72 private int mNavigationBarMode; 73 private int mNavigationMode; 74 private final int mDarkIconColor; 75 private final int mLightIconColor; 76 77 /** 78 * Whether the navigation bar should be light factoring in already how much alpha the scrim has. 79 * "Light" refers to the background color of the navigation bar, so when this is true, 80 * it's referring to a state where the navigation bar icons are tinted dark. 81 */ 82 private boolean mNavigationLight; 83 84 /** 85 * Whether the flags indicate that a light navigation bar is requested. 86 * "Light" refers to the background color of the navigation bar, so when this is true, 87 * it's referring to a state where the navigation bar icons would be tinted dark. 88 * This doesn't factor in the scrim alpha yet. 89 */ 90 private boolean mHasLightNavigationBar; 91 92 /** 93 * {@code true} if {@link #mHasLightNavigationBar} should be ignored and forcefully make 94 * {@link #mNavigationLight} {@code false}. 95 */ 96 private boolean mForceDarkForScrim; 97 /** 98 * {@code true} if {@link #mHasLightNavigationBar} should be ignored and forcefully make 99 * {@link #mNavigationLight} {@code true}. 100 */ 101 private boolean mForceLightForScrim; 102 103 private boolean mQsCustomizing; 104 private boolean mQsExpanded; 105 private boolean mBouncerVisible; 106 private boolean mGlobalActionsVisible; 107 108 private boolean mDirectReplying; 109 private boolean mNavbarColorManagedByIme; 110 111 private boolean mIsCustomizingForBackNav; 112 113 private String mLastSetScrimStateLog; 114 private String mLastNavigationBarAppearanceChangedLog; 115 private StringBuilder mLogStringBuilder = null; 116 117 @Inject LightBarController( Context ctx, DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, DumpManager dumpManager, DisplayTracker displayTracker)118 public LightBarController( 119 Context ctx, 120 DarkIconDispatcher darkIconDispatcher, 121 BatteryController batteryController, 122 NavigationModeController navModeController, 123 DumpManager dumpManager, 124 DisplayTracker displayTracker) { 125 mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone); 126 mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone); 127 mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher; 128 mBatteryController = batteryController; 129 mBatteryController.addCallback(this); 130 mNavigationMode = navModeController.addListener((mode) -> { 131 mNavigationMode = mode; 132 }); 133 134 if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) { 135 dumpManager.registerDumpable(getClass().getSimpleName(), this); 136 } 137 } 138 139 @ColorInt getLightAppearanceIconColor()140 int getLightAppearanceIconColor() { 141 return mDarkIconColor; 142 } 143 144 @ColorInt getDarkAppearanceIconColor()145 int getDarkAppearanceIconColor() { 146 return mLightIconColor; 147 } 148 setNavigationBar(LightBarTransitionsController navigationBar)149 public void setNavigationBar(LightBarTransitionsController navigationBar) { 150 mNavigationBarController = navigationBar; 151 updateNavigation(); 152 } 153 setBiometricUnlockController( BiometricUnlockController biometricUnlockController)154 public void setBiometricUnlockController( 155 BiometricUnlockController biometricUnlockController) { 156 mBiometricUnlockController = biometricUnlockController; 157 } 158 onStatusBarAppearanceChanged(AppearanceRegion[] appearanceRegions, boolean sbModeChanged, int statusBarMode, boolean navbarColorManagedByIme)159 void onStatusBarAppearanceChanged(AppearanceRegion[] appearanceRegions, boolean sbModeChanged, 160 int statusBarMode, boolean navbarColorManagedByIme) { 161 final int numStacks = appearanceRegions.length; 162 boolean stackAppearancesChanged = mAppearanceRegions.length != numStacks; 163 for (int i = 0; i < numStacks && !stackAppearancesChanged; i++) { 164 stackAppearancesChanged |= !appearanceRegions[i].equals(mAppearanceRegions[i]); 165 } 166 if (stackAppearancesChanged || sbModeChanged || mIsCustomizingForBackNav) { 167 mAppearanceRegions = appearanceRegions; 168 onStatusBarModeChanged(statusBarMode); 169 mIsCustomizingForBackNav = false; 170 } 171 mNavbarColorManagedByIme = navbarColorManagedByIme; 172 } 173 onStatusBarModeChanged(int newBarMode)174 void onStatusBarModeChanged(int newBarMode) { 175 mStatusBarMode = newBarMode; 176 updateStatus(mAppearanceRegions); 177 } 178 onNavigationBarAppearanceChanged(@ppearance int appearance, boolean nbModeChanged, int navigationBarMode, boolean navbarColorManagedByIme)179 public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged, 180 int navigationBarMode, boolean navbarColorManagedByIme) { 181 int diff = appearance ^ mAppearance; 182 if ((diff & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0 || nbModeChanged) { 183 final boolean last = mNavigationLight; 184 mHasLightNavigationBar = isLight(appearance, navigationBarMode, 185 APPEARANCE_LIGHT_NAVIGATION_BARS); 186 final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme; 187 final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce; 188 final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce; 189 final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible; 190 final boolean darkForTop = darkForQs || mGlobalActionsVisible; 191 mNavigationLight = 192 ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop; 193 if (DEBUG_NAVBAR) { 194 mLastNavigationBarAppearanceChangedLog = getLogStringBuilder() 195 .append("onNavigationBarAppearanceChanged()") 196 .append(" appearance=").append(appearance) 197 .append(" nbModeChanged=").append(nbModeChanged) 198 .append(" navigationBarMode=").append(navigationBarMode) 199 .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme) 200 .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar) 201 .append(" ignoreScrimForce=").append(ignoreScrimForce) 202 .append(" darkForScrim=").append(darkForScrim) 203 .append(" lightForScrim=").append(lightForScrim) 204 .append(" darkForQs=").append(darkForQs) 205 .append(" darkForTop=").append(darkForTop) 206 .append(" mNavigationLight=").append(mNavigationLight) 207 .append(" last=").append(last) 208 .append(" timestamp=").append(System.currentTimeMillis()) 209 .toString(); 210 if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog); 211 } 212 if (mNavigationLight != last) { 213 updateNavigation(); 214 } 215 } 216 mAppearance = appearance; 217 mNavigationBarMode = navigationBarMode; 218 mNavbarColorManagedByIme = navbarColorManagedByIme; 219 } 220 onNavigationBarModeChanged(int newBarMode)221 public void onNavigationBarModeChanged(int newBarMode) { 222 mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS); 223 } 224 reevaluate()225 private void reevaluate() { 226 onStatusBarAppearanceChanged(mAppearanceRegions, true /* sbModeChange */, mStatusBarMode, 227 mNavbarColorManagedByIme); 228 onNavigationBarAppearanceChanged(mAppearance, true /* nbModeChanged */, 229 mNavigationBarMode, mNavbarColorManagedByIme); 230 } 231 setQsCustomizing(boolean customizing)232 public void setQsCustomizing(boolean customizing) { 233 if (mQsCustomizing == customizing) return; 234 mQsCustomizing = customizing; 235 reevaluate(); 236 } 237 238 /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */ setQsExpanded(boolean expanded)239 public void setQsExpanded(boolean expanded) { 240 if (mQsExpanded == expanded) return; 241 mQsExpanded = expanded; 242 reevaluate(); 243 } 244 245 /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */ setGlobalActionsVisible(boolean visible)246 public void setGlobalActionsVisible(boolean visible) { 247 if (mGlobalActionsVisible == visible) return; 248 mGlobalActionsVisible = visible; 249 reevaluate(); 250 } 251 252 /** 253 * Controls the light status bar temporarily for back navigation. 254 * @param appearance the custmoized appearance. 255 */ customizeStatusBarAppearance(AppearanceRegion appearance)256 public void customizeStatusBarAppearance(AppearanceRegion appearance) { 257 if (appearance != null) { 258 final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>(); 259 appearancesList.add(appearance); 260 for (int i = 0; i < mAppearanceRegions.length; i++) { 261 final AppearanceRegion ar = mAppearanceRegions[i]; 262 if (appearance.getBounds().contains(ar.getBounds())) { 263 continue; 264 } 265 appearancesList.add(ar); 266 } 267 268 final AppearanceRegion[] newAppearances = new AppearanceRegion[appearancesList.size()]; 269 updateStatus(appearancesList.toArray(newAppearances)); 270 mIsCustomizingForBackNav = true; 271 } else { 272 mIsCustomizingForBackNav = false; 273 updateStatus(mAppearanceRegions); 274 } 275 } 276 277 /** 278 * Sets whether the direct-reply is in use or not. 279 * @param directReplying {@code true} when the direct-reply is in-use. 280 */ setDirectReplying(boolean directReplying)281 public void setDirectReplying(boolean directReplying) { 282 if (mDirectReplying == directReplying) return; 283 mDirectReplying = directReplying; 284 reevaluate(); 285 } 286 setScrimState(ScrimState scrimState, float scrimBehindAlpha, GradientColors scrimInFrontColor)287 public void setScrimState(ScrimState scrimState, float scrimBehindAlpha, 288 GradientColors scrimInFrontColor) { 289 boolean bouncerVisibleLast = mBouncerVisible; 290 boolean forceDarkForScrimLast = mForceDarkForScrim; 291 boolean forceLightForScrimLast = mForceLightForScrim; 292 mBouncerVisible = 293 scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED; 294 final boolean forceForScrim = mBouncerVisible 295 || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD; 296 final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText(); 297 298 mForceDarkForScrim = forceForScrim && !scrimColorIsLight; 299 mForceLightForScrim = forceForScrim && scrimColorIsLight; 300 if (mBouncerVisible != bouncerVisibleLast) { 301 reevaluate(); 302 } else if (mHasLightNavigationBar) { 303 if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate(); 304 } else { 305 if (mForceLightForScrim != forceLightForScrimLast) reevaluate(); 306 } 307 if (DEBUG_NAVBAR) { 308 mLastSetScrimStateLog = getLogStringBuilder() 309 .append("setScrimState()") 310 .append(" scrimState=").append(scrimState) 311 .append(" scrimBehindAlpha=").append(scrimBehindAlpha) 312 .append(" scrimInFrontColor=").append(scrimInFrontColor) 313 .append(" forceForScrim=").append(forceForScrim) 314 .append(" scrimColorIsLight=").append(scrimColorIsLight) 315 .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar) 316 .append(" mBouncerVisible=").append(mBouncerVisible) 317 .append(" mForceDarkForScrim=").append(mForceDarkForScrim) 318 .append(" mForceLightForScrim=").append(mForceLightForScrim) 319 .append(" timestamp=").append(System.currentTimeMillis()) 320 .toString(); 321 if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog); 322 } 323 } 324 325 @NonNull getLogStringBuilder()326 private StringBuilder getLogStringBuilder() { 327 if (mLogStringBuilder == null) { 328 mLogStringBuilder = new StringBuilder(); 329 } 330 mLogStringBuilder.setLength(0); 331 return mLogStringBuilder; 332 } 333 isLight(int appearance, int barMode, int flag)334 private static boolean isLight(int appearance, int barMode, int flag) { 335 final boolean isTransparentBar = (barMode == MODE_TRANSPARENT 336 || barMode == MODE_LIGHTS_OUT_TRANSPARENT); 337 final boolean light = (appearance & flag) != 0; 338 return isTransparentBar && light; 339 } 340 animateChange()341 private boolean animateChange() { 342 if (mBiometricUnlockController == null) { 343 return false; 344 } 345 int unlockMode = mBiometricUnlockController.getMode(); 346 return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING 347 && unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK; 348 } 349 updateStatus(AppearanceRegion[] appearanceRegions)350 private void updateStatus(AppearanceRegion[] appearanceRegions) { 351 final int numStacks = appearanceRegions.length; 352 final ArrayList<Rect> lightBarBounds = new ArrayList<>(); 353 354 for (int i = 0; i < numStacks; i++) { 355 final AppearanceRegion ar = appearanceRegions[i]; 356 if (isLight(ar.getAppearance(), mStatusBarMode, APPEARANCE_LIGHT_STATUS_BARS)) { 357 lightBarBounds.add(ar.getBounds()); 358 } 359 } 360 361 // If no one is light, all icons become white. 362 if (lightBarBounds.isEmpty()) { 363 mStatusBarIconController.getTransitionsController().setIconsDark( 364 false, animateChange()); 365 } 366 367 // If all stacks are light, all icons get dark. 368 else if (lightBarBounds.size() == numStacks) { 369 mStatusBarIconController.setIconsDarkArea(null); 370 mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange()); 371 } 372 373 // Not the same for every stack, magic! 374 else { 375 mStatusBarIconController.setIconsDarkArea(lightBarBounds); 376 mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange()); 377 } 378 } 379 updateNavigation()380 private void updateNavigation() { 381 if (mNavigationBarController != null 382 && mNavigationBarController.supportsIconTintForNavMode(mNavigationMode)) { 383 mNavigationBarController.setIconsDark(mNavigationLight, animateChange()); 384 } 385 } 386 387 @Override onPowerSaveChanged(boolean isPowerSave)388 public void onPowerSaveChanged(boolean isPowerSave) { 389 reevaluate(); 390 } 391 392 @Override dump(PrintWriter pw, String[] args)393 public void dump(PrintWriter pw, String[] args) { 394 pw.println("LightBarController: "); 395 pw.print(" mAppearance="); pw.println(ViewDebug.flagsToString( 396 InsetsFlags.class, "appearance", mAppearance)); 397 final int numStacks = mAppearanceRegions.length; 398 for (int i = 0; i < numStacks; i++) { 399 final boolean isLight = isLight(mAppearanceRegions[i].getAppearance(), mStatusBarMode, 400 APPEARANCE_LIGHT_STATUS_BARS); 401 pw.print(" stack #"); pw.print(i); pw.print(": "); 402 pw.print(mAppearanceRegions[i].toString()); pw.print(" isLight="); pw.println(isLight); 403 } 404 405 pw.print(" mNavigationLight="); pw.println(mNavigationLight); 406 pw.print(" mHasLightNavigationBar="); pw.println(mHasLightNavigationBar); 407 pw.println(); 408 pw.print(" mStatusBarMode="); pw.print(mStatusBarMode); 409 pw.print(" mNavigationBarMode="); pw.println(mNavigationBarMode); 410 pw.println(); 411 pw.print(" mForceDarkForScrim="); pw.println(mForceDarkForScrim); 412 pw.print(" mForceLightForScrim="); pw.println(mForceLightForScrim); 413 pw.println(); 414 pw.print(" mQsCustomizing="); pw.println(mQsCustomizing); 415 pw.print(" mQsExpanded="); pw.println(mQsExpanded); 416 pw.print(" mBouncerVisible="); pw.println(mBouncerVisible); 417 pw.print(" mGlobalActionsVisible="); pw.println(mGlobalActionsVisible); 418 pw.print(" mDirectReplying="); pw.println(mDirectReplying); 419 pw.print(" mNavbarColorManagedByIme="); pw.println(mNavbarColorManagedByIme); 420 pw.println(); 421 pw.println(" Recent Calculation Logs:"); 422 pw.print(" "); pw.println(mLastSetScrimStateLog); 423 pw.print(" "); pw.println(mLastNavigationBarAppearanceChangedLog); 424 425 pw.println(); 426 427 LightBarTransitionsController transitionsController = 428 mStatusBarIconController.getTransitionsController(); 429 if (transitionsController != null) { 430 pw.println(" StatusBarTransitionsController:"); 431 transitionsController.dump(pw, args); 432 pw.println(); 433 } 434 435 if (mNavigationBarController != null) { 436 pw.println(" NavigationBarTransitionsController:"); 437 mNavigationBarController.dump(pw, args); 438 pw.println(); 439 } 440 } 441 442 /** 443 * Injectable factory for creating a {@link LightBarController}. 444 */ 445 public static class Factory { 446 private final DarkIconDispatcher mDarkIconDispatcher; 447 private final BatteryController mBatteryController; 448 private final NavigationModeController mNavModeController; 449 private final DumpManager mDumpManager; 450 private final DisplayTracker mDisplayTracker; 451 452 @Inject Factory( DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, DumpManager dumpManager, DisplayTracker displayTracker)453 public Factory( 454 DarkIconDispatcher darkIconDispatcher, 455 BatteryController batteryController, 456 NavigationModeController navModeController, 457 DumpManager dumpManager, 458 DisplayTracker displayTracker) { 459 460 mDarkIconDispatcher = darkIconDispatcher; 461 mBatteryController = batteryController; 462 mNavModeController = navModeController; 463 mDumpManager = dumpManager; 464 mDisplayTracker = displayTracker; 465 } 466 467 /** Create an {@link LightBarController} */ create(Context context)468 public LightBarController create(Context context) { 469 return new LightBarController(context, mDarkIconDispatcher, mBatteryController, 470 mNavModeController, mDumpManager, mDisplayTracker); 471 } 472 } 473 } 474