• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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