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