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