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