1 /* 2 * Copyright (C) 2014 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 com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; 20 21 import android.annotation.ColorInt; 22 import android.content.Context; 23 import android.content.res.Configuration; 24 import android.content.res.Resources; 25 import android.graphics.Color; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.util.AttributeSet; 29 import android.util.Pair; 30 import android.util.TypedValue; 31 import android.view.DisplayCutout; 32 import android.view.Gravity; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.ViewTreeObserver; 36 import android.view.WindowInsets; 37 import android.widget.ImageView; 38 import android.widget.LinearLayout; 39 import android.widget.RelativeLayout; 40 import android.widget.TextView; 41 42 import com.android.settingslib.Utils; 43 import com.android.systemui.BatteryMeterView; 44 import com.android.systemui.Dependency; 45 import com.android.systemui.Interpolators; 46 import com.android.systemui.R; 47 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; 48 import com.android.systemui.qs.QSPanel; 49 import com.android.systemui.statusbar.CommandQueue; 50 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; 51 import com.android.systemui.statusbar.policy.BatteryController; 52 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; 53 import com.android.systemui.statusbar.policy.ConfigurationController; 54 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; 55 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; 56 import com.android.systemui.statusbar.policy.UserInfoController; 57 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; 58 import com.android.systemui.statusbar.policy.UserInfoControllerImpl; 59 import com.android.systemui.statusbar.policy.UserSwitcherController; 60 61 import java.io.FileDescriptor; 62 import java.io.PrintWriter; 63 64 /** 65 * The header group on Keyguard. 66 */ 67 public class KeyguardStatusBarView extends RelativeLayout 68 implements BatteryStateChangeCallback, OnUserInfoChangedListener, ConfigurationListener { 69 70 private static final int LAYOUT_NONE = 0; 71 private static final int LAYOUT_CUTOUT = 1; 72 private static final int LAYOUT_NO_CUTOUT = 2; 73 74 private final Rect mEmptyRect = new Rect(0, 0, 0, 0); 75 76 private boolean mShowPercentAvailable; 77 private boolean mBatteryCharging; 78 private boolean mKeyguardUserSwitcherShowing; 79 private boolean mBatteryListening; 80 81 private TextView mCarrierLabel; 82 private MultiUserSwitch mMultiUserSwitch; 83 private ImageView mMultiUserAvatar; 84 private BatteryMeterView mBatteryView; 85 private StatusIconContainer mStatusIconContainer; 86 87 private BatteryController mBatteryController; 88 private KeyguardUserSwitcher mKeyguardUserSwitcher; 89 private UserSwitcherController mUserSwitcherController; 90 91 private int mSystemIconsSwitcherHiddenExpandedMargin; 92 private int mSystemIconsBaseMargin; 93 private View mSystemIconsContainer; 94 private TintedIconManager mIconManager; 95 96 private View mCutoutSpace; 97 private ViewGroup mStatusIconArea; 98 private int mLayoutState = LAYOUT_NONE; 99 100 /** 101 * Draw this many pixels into the left/right side of the cutout to optimally use the space 102 */ 103 private int mCutoutSideNudge = 0; 104 105 private DisplayCutout mDisplayCutout; 106 private int mRoundedCornerPadding = 0; 107 // right and left padding applied to this view to account for cutouts and rounded corners 108 private Pair<Integer, Integer> mPadding = new Pair(0, 0); 109 KeyguardStatusBarView(Context context, AttributeSet attrs)110 public KeyguardStatusBarView(Context context, AttributeSet attrs) { 111 super(context, attrs); 112 } 113 114 @Override onFinishInflate()115 protected void onFinishInflate() { 116 super.onFinishInflate(); 117 mSystemIconsContainer = findViewById(R.id.system_icons_container); 118 mMultiUserSwitch = findViewById(R.id.multi_user_switch); 119 mMultiUserAvatar = findViewById(R.id.multi_user_avatar); 120 mCarrierLabel = findViewById(R.id.keyguard_carrier_text); 121 mBatteryView = mSystemIconsContainer.findViewById(R.id.battery); 122 mCutoutSpace = findViewById(R.id.cutout_space_view); 123 mStatusIconArea = findViewById(R.id.status_icon_area); 124 mStatusIconContainer = findViewById(R.id.statusIcons); 125 126 loadDimens(); 127 updateUserSwitcher(); 128 mBatteryController = Dependency.get(BatteryController.class); 129 } 130 131 @Override onConfigurationChanged(Configuration newConfig)132 protected void onConfigurationChanged(Configuration newConfig) { 133 super.onConfigurationChanged(newConfig); 134 135 MarginLayoutParams lp = (MarginLayoutParams) mMultiUserAvatar.getLayoutParams(); 136 lp.width = lp.height = getResources().getDimensionPixelSize( 137 R.dimen.multi_user_avatar_keyguard_size); 138 mMultiUserAvatar.setLayoutParams(lp); 139 140 // Multi-user switch 141 lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams(); 142 lp.width = getResources().getDimensionPixelSize( 143 R.dimen.multi_user_switch_width_keyguard); 144 lp.setMarginEnd(getResources().getDimensionPixelSize( 145 R.dimen.multi_user_switch_keyguard_margin)); 146 mMultiUserSwitch.setLayoutParams(lp); 147 148 // System icons 149 lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams(); 150 lp.setMarginStart(getResources().getDimensionPixelSize( 151 R.dimen.system_icons_super_container_margin_start)); 152 mSystemIconsContainer.setLayoutParams(lp); 153 mSystemIconsContainer.setPaddingRelative(mSystemIconsContainer.getPaddingStart(), 154 mSystemIconsContainer.getPaddingTop(), 155 getResources().getDimensionPixelSize(R.dimen.system_icons_keyguard_padding_end), 156 mSystemIconsContainer.getPaddingBottom()); 157 158 // Respect font size setting. 159 mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 160 getResources().getDimensionPixelSize( 161 com.android.internal.R.dimen.text_size_small_material)); 162 lp = (MarginLayoutParams) mCarrierLabel.getLayoutParams(); 163 164 int marginStart = calculateMargin( 165 getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin), 166 mPadding.first); 167 lp.setMarginStart(marginStart); 168 169 mCarrierLabel.setLayoutParams(lp); 170 updateKeyguardStatusBarHeight(); 171 } 172 updateKeyguardStatusBarHeight()173 private void updateKeyguardStatusBarHeight() { 174 final int waterfallTop = 175 mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top; 176 MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); 177 lp.height = getResources().getDimensionPixelSize( 178 R.dimen.status_bar_header_height_keyguard) + waterfallTop; 179 setLayoutParams(lp); 180 } 181 loadDimens()182 private void loadDimens() { 183 Resources res = getResources(); 184 mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize( 185 R.dimen.system_icons_switcher_hidden_expanded_margin); 186 mSystemIconsBaseMargin = res.getDimensionPixelSize( 187 R.dimen.system_icons_super_container_avatarless_margin_end); 188 mCutoutSideNudge = getResources().getDimensionPixelSize( 189 R.dimen.display_cutout_margin_consumption); 190 mShowPercentAvailable = getContext().getResources().getBoolean( 191 com.android.internal.R.bool.config_battery_percentage_setting_available); 192 mRoundedCornerPadding = res.getDimensionPixelSize( 193 R.dimen.rounded_corner_content_padding); 194 } 195 updateVisibilities()196 private void updateVisibilities() { 197 if (mMultiUserSwitch.getParent() != mStatusIconArea && !mKeyguardUserSwitcherShowing) { 198 if (mMultiUserSwitch.getParent() != null) { 199 getOverlay().remove(mMultiUserSwitch); 200 } 201 mStatusIconArea.addView(mMultiUserSwitch, 0); 202 } else if (mMultiUserSwitch.getParent() == mStatusIconArea && mKeyguardUserSwitcherShowing) { 203 mStatusIconArea.removeView(mMultiUserSwitch); 204 } 205 if (mKeyguardUserSwitcher == null) { 206 // If we have no keyguard switcher, the screen width is under 600dp. In this case, 207 // we only show the multi-user switch if it's enabled through UserManager as well as 208 // by the user. 209 if (mMultiUserSwitch.isMultiUserEnabled()) { 210 mMultiUserSwitch.setVisibility(View.VISIBLE); 211 } else { 212 mMultiUserSwitch.setVisibility(View.GONE); 213 } 214 } 215 mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable); 216 } 217 updateSystemIconsLayoutParams()218 private void updateSystemIconsLayoutParams() { 219 LinearLayout.LayoutParams lp = 220 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams(); 221 // If the avatar icon is gone, we need to have some end margin to display the system icons 222 // correctly. 223 int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE 224 ? mSystemIconsBaseMargin 225 : 0; 226 int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin : 227 baseMarginEnd; 228 marginEnd = calculateMargin(marginEnd, mPadding.second); 229 if (marginEnd != lp.getMarginEnd()) { 230 lp.setMarginEnd(marginEnd); 231 mSystemIconsContainer.setLayoutParams(lp); 232 } 233 } 234 235 @Override onApplyWindowInsets(WindowInsets insets)236 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 237 mLayoutState = LAYOUT_NONE; 238 if (updateLayoutConsideringCutout()) { 239 requestLayout(); 240 } 241 return super.onApplyWindowInsets(insets); 242 } 243 updateLayoutConsideringCutout()244 private boolean updateLayoutConsideringCutout() { 245 mDisplayCutout = getRootWindowInsets().getDisplayCutout(); 246 updateKeyguardStatusBarHeight(); 247 248 Pair<Integer, Integer> cornerCutoutMargins = 249 StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay()); 250 updatePadding(cornerCutoutMargins); 251 if (mDisplayCutout == null || cornerCutoutMargins != null) { 252 return updateLayoutParamsNoCutout(); 253 } else { 254 return updateLayoutParamsForCutout(); 255 } 256 } 257 updatePadding(Pair<Integer, Integer> cornerCutoutMargins)258 private void updatePadding(Pair<Integer, Integer> cornerCutoutMargins) { 259 final int waterfallTop = 260 mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top; 261 mPadding = 262 StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner( 263 mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding); 264 setPadding(mPadding.first, waterfallTop, mPadding.second, 0); 265 } 266 updateLayoutParamsNoCutout()267 private boolean updateLayoutParamsNoCutout() { 268 if (mLayoutState == LAYOUT_NO_CUTOUT) { 269 return false; 270 } 271 mLayoutState = LAYOUT_NO_CUTOUT; 272 273 if (mCutoutSpace != null) { 274 mCutoutSpace.setVisibility(View.GONE); 275 } 276 277 RelativeLayout.LayoutParams lp = (LayoutParams) mCarrierLabel.getLayoutParams(); 278 lp.addRule(RelativeLayout.START_OF, R.id.status_icon_area); 279 280 lp = (LayoutParams) mStatusIconArea.getLayoutParams(); 281 lp.removeRule(RelativeLayout.RIGHT_OF); 282 lp.width = LayoutParams.WRAP_CONTENT; 283 284 LinearLayout.LayoutParams llp = 285 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams(); 286 llp.setMarginStart(getResources().getDimensionPixelSize( 287 R.dimen.system_icons_super_container_margin_start)); 288 return true; 289 } 290 updateLayoutParamsForCutout()291 private boolean updateLayoutParamsForCutout() { 292 if (mLayoutState == LAYOUT_CUTOUT) { 293 return false; 294 } 295 mLayoutState = LAYOUT_CUTOUT; 296 297 if (mCutoutSpace == null) { 298 updateLayoutParamsNoCutout(); 299 } 300 301 Rect bounds = new Rect(); 302 boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds); 303 304 mCutoutSpace.setVisibility(View.VISIBLE); 305 RelativeLayout.LayoutParams lp = (LayoutParams) mCutoutSpace.getLayoutParams(); 306 bounds.left = bounds.left + mCutoutSideNudge; 307 bounds.right = bounds.right - mCutoutSideNudge; 308 lp.width = bounds.width(); 309 lp.height = bounds.height(); 310 lp.addRule(RelativeLayout.CENTER_IN_PARENT); 311 312 lp = (LayoutParams) mCarrierLabel.getLayoutParams(); 313 lp.addRule(RelativeLayout.START_OF, R.id.cutout_space_view); 314 315 lp = (LayoutParams) mStatusIconArea.getLayoutParams(); 316 lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view); 317 lp.width = LayoutParams.MATCH_PARENT; 318 319 LinearLayout.LayoutParams llp = 320 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams(); 321 llp.setMarginStart(0); 322 return true; 323 } 324 setListening(boolean listening)325 public void setListening(boolean listening) { 326 if (listening == mBatteryListening) { 327 return; 328 } 329 mBatteryListening = listening; 330 if (mBatteryListening) { 331 mBatteryController.addCallback(this); 332 } else { 333 mBatteryController.removeCallback(this); 334 } 335 } 336 updateUserSwitcher()337 private void updateUserSwitcher() { 338 boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null; 339 mMultiUserSwitch.setClickable(keyguardSwitcherAvailable); 340 mMultiUserSwitch.setFocusable(keyguardSwitcherAvailable); 341 mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable); 342 } 343 344 @Override onAttachedToWindow()345 protected void onAttachedToWindow() { 346 super.onAttachedToWindow(); 347 UserInfoController userInfoController = Dependency.get(UserInfoController.class); 348 userInfoController.addCallback(this); 349 mUserSwitcherController = Dependency.get(UserSwitcherController.class); 350 mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController); 351 userInfoController.reloadUserInfo(); 352 Dependency.get(ConfigurationController.class).addCallback(this); 353 mIconManager = new TintedIconManager(findViewById(R.id.statusIcons), 354 Dependency.get(CommandQueue.class)); 355 Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); 356 onThemeChanged(); 357 } 358 359 @Override onDetachedFromWindow()360 protected void onDetachedFromWindow() { 361 super.onDetachedFromWindow(); 362 Dependency.get(UserInfoController.class).removeCallback(this); 363 Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager); 364 Dependency.get(ConfigurationController.class).removeCallback(this); 365 } 366 367 @Override onUserInfoChanged(String name, Drawable picture, String userAccount)368 public void onUserInfoChanged(String name, Drawable picture, String userAccount) { 369 mMultiUserAvatar.setImageDrawable(picture); 370 } 371 setQSPanel(QSPanel qsp)372 public void setQSPanel(QSPanel qsp) { 373 mMultiUserSwitch.setQsPanel(qsp); 374 } 375 376 @Override onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging)377 public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { 378 if (mBatteryCharging != charging) { 379 mBatteryCharging = charging; 380 updateVisibilities(); 381 } 382 } 383 384 @Override onPowerSaveChanged(boolean isPowerSave)385 public void onPowerSaveChanged(boolean isPowerSave) { 386 // could not care less 387 } 388 setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher)389 public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { 390 mKeyguardUserSwitcher = keyguardUserSwitcher; 391 mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher); 392 updateUserSwitcher(); 393 } 394 setKeyguardUserSwitcherShowing(boolean showing, boolean animate)395 public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) { 396 mKeyguardUserSwitcherShowing = showing; 397 if (animate) { 398 animateNextLayoutChange(); 399 } 400 updateVisibilities(); 401 updateLayoutConsideringCutout(); 402 updateSystemIconsLayoutParams(); 403 } 404 animateNextLayoutChange()405 private void animateNextLayoutChange() { 406 final int systemIconsCurrentX = mSystemIconsContainer.getLeft(); 407 final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == mStatusIconArea; 408 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 409 @Override 410 public boolean onPreDraw() { 411 getViewTreeObserver().removeOnPreDrawListener(this); 412 boolean userSwitcherHiding = userSwitcherVisible 413 && mMultiUserSwitch.getParent() != mStatusIconArea; 414 mSystemIconsContainer.setX(systemIconsCurrentX); 415 mSystemIconsContainer.animate() 416 .translationX(0) 417 .setDuration(400) 418 .setStartDelay(userSwitcherHiding ? 300 : 0) 419 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 420 .start(); 421 if (userSwitcherHiding) { 422 getOverlay().add(mMultiUserSwitch); 423 mMultiUserSwitch.animate() 424 .alpha(0f) 425 .setDuration(300) 426 .setStartDelay(0) 427 .setInterpolator(Interpolators.ALPHA_OUT) 428 .withEndAction(() -> { 429 mMultiUserSwitch.setAlpha(1f); 430 getOverlay().remove(mMultiUserSwitch); 431 }) 432 .start(); 433 434 } else { 435 mMultiUserSwitch.setAlpha(0f); 436 mMultiUserSwitch.animate() 437 .alpha(1f) 438 .setDuration(300) 439 .setStartDelay(200) 440 .setInterpolator(Interpolators.ALPHA_IN); 441 } 442 return true; 443 } 444 }); 445 446 } 447 448 @Override setVisibility(int visibility)449 public void setVisibility(int visibility) { 450 super.setVisibility(visibility); 451 if (visibility != View.VISIBLE) { 452 mSystemIconsContainer.animate().cancel(); 453 mSystemIconsContainer.setTranslationX(0); 454 mMultiUserSwitch.animate().cancel(); 455 mMultiUserSwitch.setAlpha(1f); 456 } else { 457 updateVisibilities(); 458 updateSystemIconsLayoutParams(); 459 } 460 } 461 462 @Override hasOverlappingRendering()463 public boolean hasOverlappingRendering() { 464 return false; 465 } 466 onThemeChanged()467 public void onThemeChanged() { 468 mBatteryView.setColorsFromContext(mContext); 469 updateIconsAndTextColors(); 470 // Reload user avatar 471 ((UserInfoControllerImpl) Dependency.get(UserInfoController.class)) 472 .onDensityOrFontScaleChanged(); 473 } 474 475 @Override onDensityOrFontScaleChanged()476 public void onDensityOrFontScaleChanged() { 477 loadDimens(); 478 } 479 480 @Override onOverlayChanged()481 public void onOverlayChanged() { 482 mCarrierLabel.setTextAppearance( 483 Utils.getThemeAttr(mContext, com.android.internal.R.attr.textAppearanceSmall)); 484 onThemeChanged(); 485 mBatteryView.updatePercentView(); 486 } 487 updateIconsAndTextColors()488 private void updateIconsAndTextColors() { 489 @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext, 490 R.attr.wallpaperTextColor); 491 @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext, 492 Color.luminance(textColor) < 0.5 ? R.color.dark_mode_icon_color_single_tone : 493 R.color.light_mode_icon_color_single_tone); 494 float intensity = textColor == Color.WHITE ? 0 : 1; 495 mCarrierLabel.setTextColor(iconColor); 496 if (mIconManager != null) { 497 mIconManager.setTint(iconColor); 498 } 499 500 applyDarkness(R.id.battery, mEmptyRect, intensity, iconColor); 501 applyDarkness(R.id.clock, mEmptyRect, intensity, iconColor); 502 } 503 applyDarkness(int id, Rect tintArea, float intensity, int color)504 private void applyDarkness(int id, Rect tintArea, float intensity, int color) { 505 View v = findViewById(id); 506 if (v instanceof DarkReceiver) { 507 ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color); 508 } 509 } 510 511 /** 512 * Calculates the margin that isn't already accounted for in the view's padding. 513 */ calculateMargin(int margin, int padding)514 private int calculateMargin(int margin, int padding) { 515 if (padding >= margin) { 516 return 0; 517 } else { 518 return margin - padding; 519 } 520 } 521 dump(FileDescriptor fd, PrintWriter pw, String[] args)522 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 523 pw.println("KeyguardStatusBarView:"); 524 pw.println(" mBatteryCharging: " + mBatteryCharging); 525 pw.println(" mKeyguardUserSwitcherShowing: " + mKeyguardUserSwitcherShowing); 526 pw.println(" mBatteryListening: " + mBatteryListening); 527 pw.println(" mLayoutState: " + mLayoutState); 528 if (mBatteryView != null) { 529 mBatteryView.dump(fd, pw, args); 530 } 531 } 532 } 533