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