1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.qs; 16 17 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; 18 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 19 20 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; 21 22 import android.annotation.ColorInt; 23 import android.app.ActivityManager; 24 import android.app.AlarmManager; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.res.ColorStateList; 28 import android.content.res.Configuration; 29 import android.content.res.Resources; 30 import android.graphics.Color; 31 import android.graphics.Rect; 32 import android.media.AudioManager; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.provider.AlarmClock; 36 import android.provider.Settings; 37 import android.service.notification.ZenModeConfig; 38 import android.text.format.DateUtils; 39 import android.util.AttributeSet; 40 import android.util.Log; 41 import android.util.MathUtils; 42 import android.util.Pair; 43 import android.view.ContextThemeWrapper; 44 import android.view.DisplayCutout; 45 import android.view.View; 46 import android.view.ViewGroup; 47 import android.view.WindowInsets; 48 import android.widget.FrameLayout; 49 import android.widget.ImageView; 50 import android.widget.LinearLayout; 51 import android.widget.RelativeLayout; 52 import android.widget.Space; 53 import android.widget.TextView; 54 55 import androidx.annotation.NonNull; 56 import androidx.annotation.VisibleForTesting; 57 import androidx.lifecycle.Lifecycle; 58 import androidx.lifecycle.LifecycleOwner; 59 import androidx.lifecycle.LifecycleRegistry; 60 61 import com.android.internal.logging.UiEventLogger; 62 import com.android.settingslib.Utils; 63 import com.android.systemui.BatteryMeterView; 64 import com.android.systemui.DualToneHandler; 65 import com.android.systemui.Interpolators; 66 import com.android.systemui.R; 67 import com.android.systemui.plugins.ActivityStarter; 68 import com.android.systemui.plugins.DarkIconDispatcher; 69 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; 70 import com.android.systemui.privacy.OngoingPrivacyChip; 71 import com.android.systemui.privacy.PrivacyChipBuilder; 72 import com.android.systemui.privacy.PrivacyChipEvent; 73 import com.android.systemui.privacy.PrivacyItem; 74 import com.android.systemui.privacy.PrivacyItemController; 75 import com.android.systemui.qs.QSDetail.Callback; 76 import com.android.systemui.qs.carrier.QSCarrierGroup; 77 import com.android.systemui.statusbar.CommandQueue; 78 import com.android.systemui.statusbar.phone.StatusBarIconController; 79 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; 80 import com.android.systemui.statusbar.phone.StatusBarWindowView; 81 import com.android.systemui.statusbar.phone.StatusIconContainer; 82 import com.android.systemui.statusbar.policy.Clock; 83 import com.android.systemui.statusbar.policy.DateView; 84 import com.android.systemui.statusbar.policy.NextAlarmController; 85 import com.android.systemui.statusbar.policy.ZenModeController; 86 import com.android.systemui.util.RingerModeTracker; 87 88 import java.util.ArrayList; 89 import java.util.List; 90 import java.util.Locale; 91 import java.util.Objects; 92 93 import javax.inject.Inject; 94 import javax.inject.Named; 95 96 /** 97 * View that contains the top-most bits of the screen (primarily the status bar with date, time, and 98 * battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner 99 * contents. 100 */ 101 public class QuickStatusBarHeader extends RelativeLayout implements 102 View.OnClickListener, NextAlarmController.NextAlarmChangeCallback, 103 ZenModeController.Callback, LifecycleOwner { 104 private static final String TAG = "QuickStatusBarHeader"; 105 private static final boolean DEBUG = false; 106 107 /** Delay for auto fading out the long press tooltip after it's fully visible (in ms). */ 108 private static final long AUTO_FADE_OUT_DELAY_MS = DateUtils.SECOND_IN_MILLIS * 6; 109 private static final int FADE_ANIMATION_DURATION_MS = 300; 110 private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0; 111 public static final int MAX_TOOLTIP_SHOWN_COUNT = 2; 112 113 private final NextAlarmController mAlarmController; 114 private final ZenModeController mZenController; 115 private final StatusBarIconController mStatusBarIconController; 116 private final ActivityStarter mActivityStarter; 117 118 private QSPanel mQsPanel; 119 120 private boolean mExpanded; 121 private boolean mListening; 122 private boolean mQsDisabled; 123 124 private QSCarrierGroup mCarrierGroup; 125 protected QuickQSPanel mHeaderQsPanel; 126 protected QSTileHost mHost; 127 private TintedIconManager mIconManager; 128 private TouchAnimator mStatusIconsAlphaAnimator; 129 private TouchAnimator mHeaderTextContainerAlphaAnimator; 130 private TouchAnimator mPrivacyChipAlphaAnimator; 131 private DualToneHandler mDualToneHandler; 132 private final CommandQueue mCommandQueue; 133 134 private View mSystemIconsView; 135 private View mQuickQsStatusIcons; 136 private View mHeaderTextContainerView; 137 138 private int mRingerMode = AudioManager.RINGER_MODE_NORMAL; 139 private AlarmManager.AlarmClockInfo mNextAlarm; 140 141 private ImageView mNextAlarmIcon; 142 /** {@link TextView} containing the actual text indicating when the next alarm will go off. */ 143 private TextView mNextAlarmTextView; 144 private View mNextAlarmContainer; 145 private View mStatusSeparator; 146 private ImageView mRingerModeIcon; 147 private TextView mRingerModeTextView; 148 private View mRingerContainer; 149 private Clock mClockView; 150 private DateView mDateView; 151 private OngoingPrivacyChip mPrivacyChip; 152 private Space mSpace; 153 private BatteryMeterView mBatteryRemainingIcon; 154 private RingerModeTracker mRingerModeTracker; 155 private boolean mAllIndicatorsEnabled; 156 private boolean mMicCameraIndicatorsEnabled; 157 158 private PrivacyItemController mPrivacyItemController; 159 private final UiEventLogger mUiEventLogger; 160 // Used for RingerModeTracker 161 private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); 162 163 private boolean mHasTopCutout = false; 164 private int mStatusBarPaddingTop = 0; 165 private int mRoundedCornerPadding = 0; 166 private int mContentMarginStart; 167 private int mContentMarginEnd; 168 private int mWaterfallTopInset; 169 private int mCutOutPaddingLeft; 170 private int mCutOutPaddingRight; 171 private float mExpandedHeaderAlpha = 1.0f; 172 private float mKeyguardExpansionFraction; 173 private boolean mPrivacyChipLogged = false; 174 175 private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() { 176 @Override 177 public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) { 178 mPrivacyChip.setPrivacyList(privacyItems); 179 setChipVisibility(!privacyItems.isEmpty()); 180 } 181 182 @Override 183 public void onFlagAllChanged(boolean flag) { 184 if (mAllIndicatorsEnabled != flag) { 185 mAllIndicatorsEnabled = flag; 186 update(); 187 } 188 } 189 190 @Override 191 public void onFlagMicCameraChanged(boolean flag) { 192 if (mMicCameraIndicatorsEnabled != flag) { 193 mMicCameraIndicatorsEnabled = flag; 194 update(); 195 } 196 } 197 198 private void update() { 199 StatusIconContainer iconContainer = requireViewById(R.id.statusIcons); 200 iconContainer.setIgnoredSlots(getIgnoredIconSlots()); 201 setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty()); 202 } 203 }; 204 205 @Inject QuickStatusBarHeader(@amedVIEW_CONTEXT) Context context, AttributeSet attrs, NextAlarmController nextAlarmController, ZenModeController zenModeController, StatusBarIconController statusBarIconController, ActivityStarter activityStarter, PrivacyItemController privacyItemController, CommandQueue commandQueue, RingerModeTracker ringerModeTracker, UiEventLogger uiEventLogger)206 public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, 207 NextAlarmController nextAlarmController, ZenModeController zenModeController, 208 StatusBarIconController statusBarIconController, 209 ActivityStarter activityStarter, PrivacyItemController privacyItemController, 210 CommandQueue commandQueue, RingerModeTracker ringerModeTracker, 211 UiEventLogger uiEventLogger) { 212 super(context, attrs); 213 mAlarmController = nextAlarmController; 214 mZenController = zenModeController; 215 mStatusBarIconController = statusBarIconController; 216 mActivityStarter = activityStarter; 217 mPrivacyItemController = privacyItemController; 218 mDualToneHandler = new DualToneHandler( 219 new ContextThemeWrapper(context, R.style.QSHeaderTheme)); 220 mCommandQueue = commandQueue; 221 mRingerModeTracker = ringerModeTracker; 222 mUiEventLogger = uiEventLogger; 223 } 224 225 @Override onFinishInflate()226 protected void onFinishInflate() { 227 super.onFinishInflate(); 228 229 mHeaderQsPanel = findViewById(R.id.quick_qs_panel); 230 mSystemIconsView = findViewById(R.id.quick_status_bar_system_icons); 231 mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons); 232 StatusIconContainer iconContainer = findViewById(R.id.statusIcons); 233 // Ignore privacy icons because they show in the space above QQS 234 iconContainer.addIgnoredSlots(getIgnoredIconSlots()); 235 iconContainer.setShouldRestrictIcons(false); 236 mIconManager = new TintedIconManager(iconContainer, mCommandQueue); 237 238 // Views corresponding to the header info section (e.g. ringer and next alarm). 239 mHeaderTextContainerView = findViewById(R.id.header_text_container); 240 mStatusSeparator = findViewById(R.id.status_separator); 241 mNextAlarmIcon = findViewById(R.id.next_alarm_icon); 242 mNextAlarmTextView = findViewById(R.id.next_alarm_text); 243 mNextAlarmContainer = findViewById(R.id.alarm_container); 244 mNextAlarmContainer.setOnClickListener(this::onClick); 245 mRingerModeIcon = findViewById(R.id.ringer_mode_icon); 246 mRingerModeTextView = findViewById(R.id.ringer_mode_text); 247 mRingerContainer = findViewById(R.id.ringer_container); 248 mRingerContainer.setOnClickListener(this::onClick); 249 mPrivacyChip = findViewById(R.id.privacy_chip); 250 mPrivacyChip.setOnClickListener(this::onClick); 251 mCarrierGroup = findViewById(R.id.carrier_group); 252 253 254 updateResources(); 255 256 Rect tintArea = new Rect(0, 0, 0, 0); 257 int colorForeground = Utils.getColorAttrDefaultColor(getContext(), 258 android.R.attr.colorForeground); 259 float intensity = getColorIntensity(colorForeground); 260 int fillColor = mDualToneHandler.getSingleColor(intensity); 261 262 // Set light text on the header icons because they will always be on a black background 263 applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT); 264 265 // Set the correct tint for the status icons so they contrast 266 mIconManager.setTint(fillColor); 267 mNextAlarmIcon.setImageTintList(ColorStateList.valueOf(fillColor)); 268 mRingerModeIcon.setImageTintList(ColorStateList.valueOf(fillColor)); 269 270 mClockView = findViewById(R.id.clock); 271 mClockView.setOnClickListener(this); 272 mDateView = findViewById(R.id.date); 273 mSpace = findViewById(R.id.space); 274 275 // Tint for the battery icons are handled in setupHost() 276 mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon); 277 // Don't need to worry about tuner settings for this icon 278 mBatteryRemainingIcon.setIgnoreTunerUpdates(true); 279 // QS will always show the estimate, and BatteryMeterView handles the case where 280 // it's unavailable or charging 281 mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE); 282 mRingerModeTextView.setSelected(true); 283 mNextAlarmTextView.setSelected(true); 284 285 mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable(); 286 mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable(); 287 } 288 getHeaderQsPanel()289 public QuickQSPanel getHeaderQsPanel() { 290 return mHeaderQsPanel; 291 } 292 getIgnoredIconSlots()293 private List<String> getIgnoredIconSlots() { 294 ArrayList<String> ignored = new ArrayList<>(); 295 if (getChipEnabled()) { 296 ignored.add(mContext.getResources().getString( 297 com.android.internal.R.string.status_bar_camera)); 298 ignored.add(mContext.getResources().getString( 299 com.android.internal.R.string.status_bar_microphone)); 300 if (mAllIndicatorsEnabled) { 301 ignored.add(mContext.getResources().getString( 302 com.android.internal.R.string.status_bar_location)); 303 } 304 } 305 306 return ignored; 307 } 308 updateStatusText()309 private void updateStatusText() { 310 boolean changed = updateRingerStatus() || updateAlarmStatus(); 311 312 if (changed) { 313 boolean alarmVisible = mNextAlarmTextView.getVisibility() == View.VISIBLE; 314 boolean ringerVisible = mRingerModeTextView.getVisibility() == View.VISIBLE; 315 mStatusSeparator.setVisibility(alarmVisible && ringerVisible ? View.VISIBLE 316 : View.GONE); 317 } 318 } 319 setChipVisibility(boolean chipVisible)320 private void setChipVisibility(boolean chipVisible) { 321 if (chipVisible && getChipEnabled()) { 322 mPrivacyChip.setVisibility(View.VISIBLE); 323 // Makes sure that the chip is logged as viewed at most once each time QS is opened 324 // mListening makes sure that the callback didn't return after the user closed QS 325 if (!mPrivacyChipLogged && mListening) { 326 mPrivacyChipLogged = true; 327 mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW); 328 } 329 } else { 330 mPrivacyChip.setVisibility(View.GONE); 331 } 332 } 333 updateRingerStatus()334 private boolean updateRingerStatus() { 335 boolean isOriginalVisible = mRingerModeTextView.getVisibility() == View.VISIBLE; 336 CharSequence originalRingerText = mRingerModeTextView.getText(); 337 338 boolean ringerVisible = false; 339 if (!ZenModeConfig.isZenOverridingRinger(mZenController.getZen(), 340 mZenController.getConsolidatedPolicy())) { 341 if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { 342 mRingerModeIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); 343 mRingerModeTextView.setText(R.string.qs_status_phone_vibrate); 344 ringerVisible = true; 345 } else if (mRingerMode == AudioManager.RINGER_MODE_SILENT) { 346 mRingerModeIcon.setImageResource(R.drawable.ic_volume_ringer_mute); 347 mRingerModeTextView.setText(R.string.qs_status_phone_muted); 348 ringerVisible = true; 349 } 350 } 351 mRingerModeIcon.setVisibility(ringerVisible ? View.VISIBLE : View.GONE); 352 mRingerModeTextView.setVisibility(ringerVisible ? View.VISIBLE : View.GONE); 353 mRingerContainer.setVisibility(ringerVisible ? View.VISIBLE : View.GONE); 354 355 return isOriginalVisible != ringerVisible || 356 !Objects.equals(originalRingerText, mRingerModeTextView.getText()); 357 } 358 updateAlarmStatus()359 private boolean updateAlarmStatus() { 360 boolean isOriginalVisible = mNextAlarmTextView.getVisibility() == View.VISIBLE; 361 CharSequence originalAlarmText = mNextAlarmTextView.getText(); 362 363 boolean alarmVisible = false; 364 if (mNextAlarm != null) { 365 alarmVisible = true; 366 mNextAlarmTextView.setText(formatNextAlarm(mNextAlarm)); 367 } 368 mNextAlarmIcon.setVisibility(alarmVisible ? View.VISIBLE : View.GONE); 369 mNextAlarmTextView.setVisibility(alarmVisible ? View.VISIBLE : View.GONE); 370 mNextAlarmContainer.setVisibility(alarmVisible ? View.VISIBLE : View.GONE); 371 372 return isOriginalVisible != alarmVisible || 373 !Objects.equals(originalAlarmText, mNextAlarmTextView.getText()); 374 } 375 applyDarkness(int id, Rect tintArea, float intensity, int color)376 private void applyDarkness(int id, Rect tintArea, float intensity, int color) { 377 View v = findViewById(id); 378 if (v instanceof DarkReceiver) { 379 ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color); 380 } 381 } 382 383 @Override onConfigurationChanged(Configuration newConfig)384 protected void onConfigurationChanged(Configuration newConfig) { 385 super.onConfigurationChanged(newConfig); 386 updateResources(); 387 388 // Update color schemes in landscape to use wallpaperTextColor 389 boolean shouldUseWallpaperTextColor = 390 newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; 391 mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor); 392 } 393 394 @Override onRtlPropertiesChanged(int layoutDirection)395 public void onRtlPropertiesChanged(int layoutDirection) { 396 super.onRtlPropertiesChanged(layoutDirection); 397 updateResources(); 398 } 399 400 /** 401 * The height of QQS should always be the status bar height + 128dp. This is normally easy, but 402 * when there is a notch involved the status bar can remain a fixed pixel size. 403 */ updateMinimumHeight()404 private void updateMinimumHeight() { 405 int sbHeight = mContext.getResources().getDimensionPixelSize( 406 com.android.internal.R.dimen.status_bar_height); 407 int qqsHeight = mContext.getResources().getDimensionPixelSize( 408 R.dimen.qs_quick_header_panel_height); 409 410 setMinimumHeight(sbHeight + qqsHeight); 411 } 412 updateResources()413 private void updateResources() { 414 Resources resources = mContext.getResources(); 415 updateMinimumHeight(); 416 417 mRoundedCornerPadding = resources.getDimensionPixelSize( 418 R.dimen.rounded_corner_content_padding); 419 mStatusBarPaddingTop = resources.getDimensionPixelSize(R.dimen.status_bar_padding_top); 420 421 // Update height for a few views, especially due to landscape mode restricting space. 422 mHeaderTextContainerView.getLayoutParams().height = 423 resources.getDimensionPixelSize(R.dimen.qs_header_tooltip_height); 424 mHeaderTextContainerView.setLayoutParams(mHeaderTextContainerView.getLayoutParams()); 425 426 mSystemIconsView.getLayoutParams().height = resources.getDimensionPixelSize( 427 com.android.internal.R.dimen.quick_qs_offset_height); 428 mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams()); 429 430 ViewGroup.LayoutParams lp = getLayoutParams(); 431 if (mQsDisabled) { 432 lp.height = resources.getDimensionPixelSize( 433 com.android.internal.R.dimen.quick_qs_offset_height); 434 } else { 435 lp.height = WRAP_CONTENT; 436 } 437 setLayoutParams(lp); 438 439 updateStatusIconAlphaAnimator(); 440 updateHeaderTextContainerAlphaAnimator(); 441 updatePrivacyChipAlphaAnimator(); 442 } 443 updateStatusIconAlphaAnimator()444 private void updateStatusIconAlphaAnimator() { 445 mStatusIconsAlphaAnimator = new TouchAnimator.Builder() 446 .addFloat(mQuickQsStatusIcons, "alpha", 1, 0, 0) 447 .build(); 448 } 449 updateHeaderTextContainerAlphaAnimator()450 private void updateHeaderTextContainerAlphaAnimator() { 451 mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder() 452 .addFloat(mHeaderTextContainerView, "alpha", 0, 0, mExpandedHeaderAlpha) 453 .build(); 454 } 455 updatePrivacyChipAlphaAnimator()456 private void updatePrivacyChipAlphaAnimator() { 457 mPrivacyChipAlphaAnimator = new TouchAnimator.Builder() 458 .addFloat(mPrivacyChip, "alpha", 1, 0, 1) 459 .build(); 460 } 461 setExpanded(boolean expanded)462 public void setExpanded(boolean expanded) { 463 if (mExpanded == expanded) return; 464 mExpanded = expanded; 465 mHeaderQsPanel.setExpanded(expanded); 466 updateEverything(); 467 } 468 469 /** 470 * Animates the inner contents based on the given expansion details. 471 * 472 * @param forceExpanded whether we should show the state expanded forcibly 473 * @param expansionFraction how much the QS panel is expanded/pulled out (up to 1f) 474 * @param panelTranslationY how much the panel has physically moved down vertically (required 475 * for keyguard animations only) 476 */ setExpansion(boolean forceExpanded, float expansionFraction, float panelTranslationY)477 public void setExpansion(boolean forceExpanded, float expansionFraction, 478 float panelTranslationY) { 479 final float keyguardExpansionFraction = forceExpanded ? 1f : expansionFraction; 480 if (mStatusIconsAlphaAnimator != null) { 481 mStatusIconsAlphaAnimator.setPosition(keyguardExpansionFraction); 482 } 483 484 if (forceExpanded) { 485 // If the keyguard is showing, we want to offset the text so that it comes in at the 486 // same time as the panel as it slides down. 487 mHeaderTextContainerView.setTranslationY(panelTranslationY); 488 } else { 489 mHeaderTextContainerView.setTranslationY(0f); 490 } 491 492 if (mHeaderTextContainerAlphaAnimator != null) { 493 mHeaderTextContainerAlphaAnimator.setPosition(keyguardExpansionFraction); 494 if (keyguardExpansionFraction > 0) { 495 mHeaderTextContainerView.setVisibility(VISIBLE); 496 } else { 497 mHeaderTextContainerView.setVisibility(INVISIBLE); 498 } 499 } 500 if (mPrivacyChipAlphaAnimator != null) { 501 mPrivacyChip.setExpanded(expansionFraction > 0.5); 502 mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction); 503 } 504 if (expansionFraction < 1 && expansionFraction > 0.99) { 505 if (mHeaderQsPanel.switchTileLayout()) { 506 updateResources(); 507 } 508 } 509 mKeyguardExpansionFraction = keyguardExpansionFraction; 510 } 511 disable(int state1, int state2, boolean animate)512 public void disable(int state1, int state2, boolean animate) { 513 final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; 514 if (disabled == mQsDisabled) return; 515 mQsDisabled = disabled; 516 mHeaderQsPanel.setDisabledByPolicy(disabled); 517 mHeaderTextContainerView.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); 518 mQuickQsStatusIcons.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); 519 updateResources(); 520 } 521 522 @Override onAttachedToWindow()523 public void onAttachedToWindow() { 524 super.onAttachedToWindow(); 525 mRingerModeTracker.getRingerModeInternal().observe(this, ringer -> { 526 mRingerMode = ringer; 527 updateStatusText(); 528 }); 529 mStatusBarIconController.addIconGroup(mIconManager); 530 requestApplyInsets(); 531 } 532 533 @Override onApplyWindowInsets(WindowInsets insets)534 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 535 // Handle padding of the clock 536 DisplayCutout cutout = insets.getDisplayCutout(); 537 Pair<Integer, Integer> cornerCutoutPadding = StatusBarWindowView.cornerCutoutMargins( 538 cutout, getDisplay()); 539 Pair<Integer, Integer> padding = 540 StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner( 541 cutout, cornerCutoutPadding, -1); 542 if (padding == null) { 543 mSystemIconsView.setPaddingRelative( 544 getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start), 0, 545 getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end), 0); 546 } else { 547 mSystemIconsView.setPadding(padding.first, 0, padding.second, 0); 548 549 } 550 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSpace.getLayoutParams(); 551 boolean cornerCutout = cornerCutoutPadding != null 552 && (cornerCutoutPadding.first == 0 || cornerCutoutPadding.second == 0); 553 if (cutout != null) { 554 Rect topCutout = cutout.getBoundingRectTop(); 555 if (topCutout.isEmpty() || cornerCutout) { 556 mHasTopCutout = false; 557 lp.width = 0; 558 mSpace.setVisibility(View.GONE); 559 } else { 560 mHasTopCutout = true; 561 lp.width = topCutout.width(); 562 mSpace.setVisibility(View.VISIBLE); 563 } 564 } 565 mSpace.setLayoutParams(lp); 566 setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE); 567 mCutOutPaddingLeft = padding.first; 568 mCutOutPaddingRight = padding.second; 569 mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top; 570 updateClockPadding(); 571 return super.onApplyWindowInsets(insets); 572 } 573 updateClockPadding()574 private void updateClockPadding() { 575 int clockPaddingLeft = 0; 576 int clockPaddingRight = 0; 577 578 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); 579 int leftMargin = lp.leftMargin; 580 int rightMargin = lp.rightMargin; 581 582 // The clock might collide with cutouts, let's shift it out of the way. 583 // We only do that if the inset is bigger than our own padding, since it's nicer to 584 // align with 585 if (mCutOutPaddingLeft > 0) { 586 // if there's a cutout, let's use at least the rounded corner inset 587 int cutoutPadding = Math.max(mCutOutPaddingLeft, mRoundedCornerPadding); 588 int contentMarginLeft = isLayoutRtl() ? mContentMarginEnd : mContentMarginStart; 589 clockPaddingLeft = Math.max(cutoutPadding - contentMarginLeft - leftMargin, 0); 590 } 591 if (mCutOutPaddingRight > 0) { 592 // if there's a cutout, let's use at least the rounded corner inset 593 int cutoutPadding = Math.max(mCutOutPaddingRight, mRoundedCornerPadding); 594 int contentMarginRight = isLayoutRtl() ? mContentMarginStart : mContentMarginEnd; 595 clockPaddingRight = Math.max(cutoutPadding - contentMarginRight - rightMargin, 0); 596 } 597 598 mSystemIconsView.setPadding(clockPaddingLeft, 599 mWaterfallTopInset + mStatusBarPaddingTop, 600 clockPaddingRight, 601 0); 602 } 603 604 @Override 605 @VisibleForTesting onDetachedFromWindow()606 public void onDetachedFromWindow() { 607 setListening(false); 608 mRingerModeTracker.getRingerModeInternal().removeObservers(this); 609 mStatusBarIconController.removeIconGroup(mIconManager); 610 super.onDetachedFromWindow(); 611 } 612 setListening(boolean listening)613 public void setListening(boolean listening) { 614 if (listening == mListening) { 615 return; 616 } 617 mHeaderQsPanel.setListening(listening); 618 if (mHeaderQsPanel.switchTileLayout()) { 619 updateResources(); 620 } 621 mListening = listening; 622 623 if (listening) { 624 mZenController.addCallback(this); 625 mAlarmController.addCallback(this); 626 mLifecycle.setCurrentState(Lifecycle.State.RESUMED); 627 // Get the most up to date info 628 mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable(); 629 mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable(); 630 mPrivacyItemController.addCallback(mPICCallback); 631 } else { 632 mZenController.removeCallback(this); 633 mAlarmController.removeCallback(this); 634 mLifecycle.setCurrentState(Lifecycle.State.CREATED); 635 mPrivacyItemController.removeCallback(mPICCallback); 636 mPrivacyChipLogged = false; 637 } 638 } 639 640 @Override onClick(View v)641 public void onClick(View v) { 642 if (v == mClockView) { 643 mActivityStarter.postStartActivityDismissingKeyguard(new Intent( 644 AlarmClock.ACTION_SHOW_ALARMS), 0); 645 } else if (v == mNextAlarmContainer && mNextAlarmContainer.isVisibleToUser()) { 646 if (mNextAlarm.getShowIntent() != null) { 647 mActivityStarter.postStartActivityDismissingKeyguard( 648 mNextAlarm.getShowIntent()); 649 } else { 650 Log.d(TAG, "No PendingIntent for next alarm. Using default intent"); 651 mActivityStarter.postStartActivityDismissingKeyguard(new Intent( 652 AlarmClock.ACTION_SHOW_ALARMS), 0); 653 } 654 } else if (v == mPrivacyChip) { 655 // Makes sure that the builder is grabbed as soon as the chip is pressed 656 PrivacyChipBuilder builder = mPrivacyChip.getBuilder(); 657 if (builder.getAppsAndTypes().size() == 0) return; 658 Handler mUiHandler = new Handler(Looper.getMainLooper()); 659 mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK); 660 mUiHandler.post(() -> { 661 mActivityStarter.postStartActivityDismissingKeyguard( 662 new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0); 663 mHost.collapsePanels(); 664 }); 665 } else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) { 666 mActivityStarter.postStartActivityDismissingKeyguard(new Intent( 667 Settings.ACTION_SOUND_SETTINGS), 0); 668 } 669 } 670 671 @Override onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm)672 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { 673 mNextAlarm = nextAlarm; 674 updateStatusText(); 675 } 676 677 @Override onZenChanged(int zen)678 public void onZenChanged(int zen) { 679 updateStatusText(); 680 } 681 682 @Override onConfigChanged(ZenModeConfig config)683 public void onConfigChanged(ZenModeConfig config) { 684 updateStatusText(); 685 } 686 updateEverything()687 public void updateEverything() { 688 post(() -> setClickable(!mExpanded)); 689 } 690 setQSPanel(final QSPanel qsPanel)691 public void setQSPanel(final QSPanel qsPanel) { 692 mQsPanel = qsPanel; 693 setupHost(qsPanel.getHost()); 694 } 695 setupHost(final QSTileHost host)696 public void setupHost(final QSTileHost host) { 697 mHost = host; 698 //host.setHeaderView(mExpandIndicator); 699 mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this); 700 mHeaderQsPanel.setHost(host, null /* No customization in header */); 701 702 703 Rect tintArea = new Rect(0, 0, 0, 0); 704 int colorForeground = Utils.getColorAttrDefaultColor(getContext(), 705 android.R.attr.colorForeground); 706 float intensity = getColorIntensity(colorForeground); 707 int fillColor = mDualToneHandler.getSingleColor(intensity); 708 mBatteryRemainingIcon.onDarkChanged(tintArea, intensity, fillColor); 709 } 710 setCallback(Callback qsPanelCallback)711 public void setCallback(Callback qsPanelCallback) { 712 mHeaderQsPanel.setCallback(qsPanelCallback); 713 } 714 formatNextAlarm(AlarmManager.AlarmClockInfo info)715 private String formatNextAlarm(AlarmManager.AlarmClockInfo info) { 716 if (info == null) { 717 return ""; 718 } 719 String skeleton = android.text.format.DateFormat 720 .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma"; 721 String pattern = android.text.format.DateFormat 722 .getBestDateTimePattern(Locale.getDefault(), skeleton); 723 return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString(); 724 } 725 getColorIntensity(@olorInt int color)726 public static float getColorIntensity(@ColorInt int color) { 727 return color == Color.WHITE ? 0 : 1; 728 } 729 730 @NonNull 731 @Override getLifecycle()732 public Lifecycle getLifecycle() { 733 return mLifecycle; 734 } 735 setContentMargins(int marginStart, int marginEnd)736 public void setContentMargins(int marginStart, int marginEnd) { 737 mContentMarginStart = marginStart; 738 mContentMarginEnd = marginEnd; 739 for (int i = 0; i < getChildCount(); i++) { 740 View view = getChildAt(i); 741 if (view == mHeaderQsPanel) { 742 // QS panel doesn't lays out some of its content full width 743 mHeaderQsPanel.setContentMargins(marginStart, marginEnd); 744 } else { 745 MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams(); 746 lp.setMarginStart(marginStart); 747 lp.setMarginEnd(marginEnd); 748 view.setLayoutParams(lp); 749 } 750 } 751 updateClockPadding(); 752 } 753 setExpandedScrollAmount(int scrollY)754 public void setExpandedScrollAmount(int scrollY) { 755 // The scrolling of the expanded qs has changed. Since the header text isn't part of it, 756 // but would overlap content, we're fading it out. 757 float newAlpha = 1.0f; 758 if (mHeaderTextContainerView.getHeight() > 0) { 759 newAlpha = MathUtils.map(0, mHeaderTextContainerView.getHeight() / 2.0f, 1.0f, 0.0f, 760 scrollY); 761 newAlpha = Interpolators.ALPHA_OUT.getInterpolation(newAlpha); 762 } 763 mHeaderTextContainerView.setScrollY(scrollY); 764 if (newAlpha != mExpandedHeaderAlpha) { 765 mExpandedHeaderAlpha = newAlpha; 766 mHeaderTextContainerView.setAlpha(MathUtils.lerp(0.0f, mExpandedHeaderAlpha, 767 mKeyguardExpansionFraction)); 768 updateHeaderTextContainerAlphaAnimator(); 769 } 770 } 771 getChipEnabled()772 private boolean getChipEnabled() { 773 return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled; 774 } 775 } 776