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