• 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 android.content.Context;
21 import android.content.res.Configuration;
22 import android.content.res.Resources;
23 import android.graphics.Color;
24 import android.graphics.Rect;
25 import android.util.AttributeSet;
26 import android.util.Pair;
27 import android.view.DisplayCutout;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.WindowInsets;
31 import android.widget.FrameLayout;
32 import android.widget.LinearLayout;
33 import android.widget.Space;
34 
35 import androidx.annotation.NonNull;
36 
37 import com.android.settingslib.Utils;
38 import com.android.systemui.BatteryMeterView;
39 import com.android.systemui.R;
40 import com.android.systemui.qs.QSDetail.Callback;
41 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
42 import com.android.systemui.statusbar.phone.StatusBarWindowView;
43 import com.android.systemui.statusbar.phone.StatusIconContainer;
44 import com.android.systemui.statusbar.policy.Clock;
45 
46 import java.util.List;
47 
48 /**
49  * View that contains the top-most bits of the QS panel (primarily the status bar with date, time,
50  * battery, carrier info and privacy icons) and also contains the {@link QuickQSPanel}.
51  */
52 public class QuickStatusBarHeader extends FrameLayout {
53 
54     private boolean mExpanded;
55     private boolean mQsDisabled;
56 
57     private TouchAnimator mAlphaAnimator;
58     private TouchAnimator mTranslationAnimator;
59     private TouchAnimator mIconsAlphaAnimator;
60     private TouchAnimator mIconsAlphaAnimatorFixed;
61 
62     protected QuickQSPanel mHeaderQsPanel;
63     private View mDatePrivacyView;
64     private View mDateView;
65     private View mSecurityHeaderView;
66     private View mClockIconsView;
67     private View mContainer;
68 
69     private View mQSCarriers;
70     private Clock mClockView;
71     private Space mDatePrivacySeparator;
72     private View mClockIconsSeparator;
73     private boolean mShowClockIconsSeparator;
74     private View mRightLayout;
75     private View mDateContainer;
76     private View mPrivacyContainer;
77 
78     private BatteryMeterView mBatteryRemainingIcon;
79     private StatusIconContainer mIconContainer;
80     private View mPrivacyChip;
81 
82     private TintedIconManager mTintedIconManager;
83     private QSExpansionPathInterpolator mQSExpansionPathInterpolator;
84 
85     private int mRoundedCornerPadding = 0;
86     private int mWaterfallTopInset;
87     private int mCutOutPaddingLeft;
88     private int mCutOutPaddingRight;
89     private float mViewAlpha = 1.0f;
90     private float mKeyguardExpansionFraction;
91     private int mTextColorPrimary = Color.TRANSPARENT;
92     private int mTopViewMeasureHeight;
93 
94     @NonNull
95     private List<String> mRssiIgnoredSlots;
96     private boolean mIsSingleCarrier;
97 
98     private boolean mHasCenterCutout;
99     private boolean mConfigShowBatteryEstimate;
100 
QuickStatusBarHeader(Context context, AttributeSet attrs)101     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
102         super(context, attrs);
103     }
104 
105     /**
106      * How much the view containing the clock and QQS will translate down when QS is fully expanded.
107      *
108      * This matches the measured height of the view containing the date and privacy icons.
109      */
getOffsetTranslation()110     public int getOffsetTranslation() {
111         return mTopViewMeasureHeight;
112     }
113 
114     @Override
onFinishInflate()115     protected void onFinishInflate() {
116         super.onFinishInflate();
117 
118         mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
119         mDatePrivacyView = findViewById(R.id.quick_status_bar_date_privacy);
120         mClockIconsView = findViewById(R.id.quick_qs_status_icons);
121         mQSCarriers = findViewById(R.id.carrier_group);
122         mContainer = findViewById(R.id.qs_container);
123         mIconContainer = findViewById(R.id.statusIcons);
124         mPrivacyChip = findViewById(R.id.privacy_chip);
125         mDateView = findViewById(R.id.date);
126         mSecurityHeaderView = findViewById(R.id.header_text_container);
127         mClockIconsSeparator = findViewById(R.id.separator);
128         mRightLayout = findViewById(R.id.rightLayout);
129         mDateContainer = findViewById(R.id.date_container);
130         mPrivacyContainer = findViewById(R.id.privacy_container);
131 
132         mClockView = findViewById(R.id.clock);
133         mDatePrivacySeparator = findViewById(R.id.space);
134         // Tint for the battery icons are handled in setupHost()
135         mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
136 
137         updateResources();
138         Configuration config = mContext.getResources().getConfiguration();
139         setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
140         setSecurityHeaderContainerVisibility(
141                 config.orientation == Configuration.ORIENTATION_LANDSCAPE);
142 
143         // Don't need to worry about tuner settings for this icon
144         mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
145         // QS will always show the estimate, and BatteryMeterView handles the case where
146         // it's unavailable or charging
147         mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
148 
149         mIconsAlphaAnimatorFixed = new TouchAnimator.Builder()
150                 .addFloat(mIconContainer, "alpha", 0, 1)
151                 .addFloat(mBatteryRemainingIcon, "alpha", 0, 1)
152                 .build();
153     }
154 
onAttach(TintedIconManager iconManager, QSExpansionPathInterpolator qsExpansionPathInterpolator, List<String> rssiIgnoredSlots)155     void onAttach(TintedIconManager iconManager,
156             QSExpansionPathInterpolator qsExpansionPathInterpolator,
157             List<String> rssiIgnoredSlots) {
158         mTintedIconManager = iconManager;
159         mRssiIgnoredSlots = rssiIgnoredSlots;
160         int fillColor = Utils.getColorAttrDefaultColor(getContext(),
161                 android.R.attr.textColorPrimary);
162 
163         // Set the correct tint for the status icons so they contrast
164         iconManager.setTint(fillColor);
165 
166         mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
167         updateAnimators();
168     }
169 
setIsSingleCarrier(boolean isSingleCarrier)170     void setIsSingleCarrier(boolean isSingleCarrier) {
171         mIsSingleCarrier = isSingleCarrier;
172         updateAlphaAnimator();
173     }
174 
getHeaderQsPanel()175     public QuickQSPanel getHeaderQsPanel() {
176         return mHeaderQsPanel;
177     }
178 
179     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)180     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
181         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
182         if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) {
183             mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight();
184             updateAnimators();
185         }
186     }
187 
188     @Override
onConfigurationChanged(Configuration newConfig)189     protected void onConfigurationChanged(Configuration newConfig) {
190         super.onConfigurationChanged(newConfig);
191         updateResources();
192         setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
193         setSecurityHeaderContainerVisibility(
194                 newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
195     }
196 
197     @Override
onRtlPropertiesChanged(int layoutDirection)198     public void onRtlPropertiesChanged(int layoutDirection) {
199         super.onRtlPropertiesChanged(layoutDirection);
200         updateResources();
201     }
202 
setDatePrivacyContainersWidth(boolean landscape)203     private void setDatePrivacyContainersWidth(boolean landscape) {
204         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mDateContainer.getLayoutParams();
205         lp.width = landscape ? WRAP_CONTENT : 0;
206         lp.weight = landscape ? 0f : 1f;
207         mDateContainer.setLayoutParams(lp);
208 
209         lp = (LinearLayout.LayoutParams) mPrivacyContainer.getLayoutParams();
210         lp.width = landscape ? WRAP_CONTENT : 0;
211         lp.weight = landscape ? 0f : 1f;
212         mPrivacyContainer.setLayoutParams(lp);
213     }
214 
setSecurityHeaderContainerVisibility(boolean landscape)215     private void setSecurityHeaderContainerVisibility(boolean landscape) {
216         mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE);
217     }
218 
updateBatteryMode()219     private void updateBatteryMode() {
220         if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
221             mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
222         } else {
223             mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ON);
224         }
225     }
226 
updateResources()227     void updateResources() {
228         Resources resources = mContext.getResources();
229 
230         mConfigShowBatteryEstimate = resources.getBoolean(R.bool.config_showBatteryEstimateQSBH);
231 
232         mRoundedCornerPadding = resources.getDimensionPixelSize(
233                 R.dimen.rounded_corner_content_padding);
234 
235         int qsOffsetHeight = resources.getDimensionPixelSize(
236                 com.android.internal.R.dimen.quick_qs_offset_height);
237 
238         mDatePrivacyView.getLayoutParams().height =
239                 Math.max(qsOffsetHeight, mDatePrivacyView.getMinimumHeight());
240         mDatePrivacyView.setLayoutParams(mDatePrivacyView.getLayoutParams());
241 
242         mClockIconsView.getLayoutParams().height =
243                 Math.max(qsOffsetHeight, mClockIconsView.getMinimumHeight());
244         mClockIconsView.setLayoutParams(mClockIconsView.getLayoutParams());
245 
246         ViewGroup.LayoutParams lp = getLayoutParams();
247         if (mQsDisabled) {
248             lp.height = mClockIconsView.getLayoutParams().height;
249         } else {
250             lp.height = WRAP_CONTENT;
251         }
252         setLayoutParams(lp);
253 
254         int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
255         if (textColor != mTextColorPrimary) {
256             int textColorSecondary = Utils.getColorAttrDefaultColor(mContext,
257                     android.R.attr.textColorSecondary);
258             mTextColorPrimary = textColor;
259             mClockView.setTextColor(textColor);
260             if (mTintedIconManager != null) {
261                 mTintedIconManager.setTint(textColor);
262             }
263             mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
264                     mTextColorPrimary);
265         }
266 
267         MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams();
268         qqsLP.topMargin = mContext.getResources()
269                 .getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
270         mHeaderQsPanel.setLayoutParams(qqsLP);
271 
272         updateBatteryMode();
273         updateHeadersPadding();
274         updateAnimators();
275     }
276 
updateAnimators()277     private void updateAnimators() {
278         updateAlphaAnimator();
279         int offset = mTopViewMeasureHeight;
280 
281         mTranslationAnimator = new TouchAnimator.Builder()
282                 .addFloat(mContainer, "translationY", 0, offset)
283                 .setInterpolator(mQSExpansionPathInterpolator != null
284                         ? mQSExpansionPathInterpolator.getYInterpolator()
285                         : null)
286                 .build();
287     }
288 
updateAlphaAnimator()289     private void updateAlphaAnimator() {
290         TouchAnimator.Builder builder = new TouchAnimator.Builder()
291                 .addFloat(mSecurityHeaderView, "alpha", 0, 1)
292                 // These views appear on expanding down
293                 .addFloat(mClockView, "alpha", 0, 1)
294                 .addFloat(mQSCarriers, "alpha", 0, 1)
295                 .setListener(new TouchAnimator.ListenerAdapter() {
296                     @Override
297                     public void onAnimationAtEnd() {
298                         super.onAnimationAtEnd();
299                         if (!mIsSingleCarrier) {
300                             mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
301                         }
302                     }
303 
304                     @Override
305                     public void onAnimationStarted() {
306                         setSeparatorVisibility(false);
307                         if (!mIsSingleCarrier) {
308                             mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
309                         }
310                     }
311 
312                     @Override
313                     public void onAnimationAtStart() {
314                         super.onAnimationAtStart();
315                         setSeparatorVisibility(mShowClockIconsSeparator);
316                         // In QQS we never ignore RSSI.
317                         mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots);
318                     }
319                 });
320         mAlphaAnimator = builder.build();
321     }
322 
setChipVisibility(boolean visibility)323     void setChipVisibility(boolean visibility) {
324         mPrivacyChip.setVisibility(visibility ? View.VISIBLE : View.GONE);
325         if (visibility) {
326             // Animates the icons and battery indicator from alpha 0 to 1, when the chip is visible
327             mIconsAlphaAnimator = mIconsAlphaAnimatorFixed;
328             mIconsAlphaAnimator.setPosition(mKeyguardExpansionFraction);
329         } else {
330             mIconsAlphaAnimator = null;
331             mIconContainer.setAlpha(1);
332             mBatteryRemainingIcon.setAlpha(1);
333         }
334 
335     }
336 
337     /** */
setExpanded(boolean expanded, QuickQSPanelController quickQSPanelController)338     public void setExpanded(boolean expanded, QuickQSPanelController quickQSPanelController) {
339         if (mExpanded == expanded) return;
340         mExpanded = expanded;
341         quickQSPanelController.setExpanded(expanded);
342         updateEverything();
343     }
344 
345     /**
346      * Animates the inner contents based on the given expansion details.
347      *
348      * @param forceExpanded whether we should show the state expanded forcibly
349      * @param expansionFraction how much the QS panel is expanded/pulled out (up to 1f)
350      * @param panelTranslationY how much the panel has physically moved down vertically (required
351      *                          for keyguard animations only)
352      */
setExpansion(boolean forceExpanded, float expansionFraction, float panelTranslationY)353     public void setExpansion(boolean forceExpanded, float expansionFraction,
354                              float panelTranslationY) {
355         final float keyguardExpansionFraction = forceExpanded ? 1f : expansionFraction;
356 
357         if (mAlphaAnimator != null) {
358             mAlphaAnimator.setPosition(keyguardExpansionFraction);
359         }
360         if (mTranslationAnimator != null) {
361             mTranslationAnimator.setPosition(keyguardExpansionFraction);
362         }
363         if (mIconsAlphaAnimator != null) {
364             mIconsAlphaAnimator.setPosition(keyguardExpansionFraction);
365         }
366         // If forceExpanded (we are opening QS from lockscreen), the animators have been set to
367         // position = 1f.
368         if (forceExpanded) {
369             setTranslationY(panelTranslationY);
370         } else {
371             setTranslationY(0);
372         }
373 
374         mKeyguardExpansionFraction = keyguardExpansionFraction;
375     }
376 
disable(int state1, int state2, boolean animate)377     public void disable(int state1, int state2, boolean animate) {
378         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
379         if (disabled == mQsDisabled) return;
380         mQsDisabled = disabled;
381         mHeaderQsPanel.setDisabledByPolicy(disabled);
382         mClockIconsView.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
383         updateResources();
384     }
385 
386     @Override
onApplyWindowInsets(WindowInsets insets)387     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
388         // Handle padding of the views
389         DisplayCutout cutout = insets.getDisplayCutout();
390         Pair<Integer, Integer> cornerCutoutPadding = StatusBarWindowView.cornerCutoutMargins(
391                 cutout, getDisplay());
392         Pair<Integer, Integer> padding =
393                 StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
394                         cutout, cornerCutoutPadding, -1);
395         mDatePrivacyView.setPadding(padding.first, 0, padding.second, 0);
396         mClockIconsView.setPadding(padding.first, 0, padding.second, 0);
397         LinearLayout.LayoutParams datePrivacySeparatorLayoutParams =
398                 (LinearLayout.LayoutParams) mDatePrivacySeparator.getLayoutParams();
399         LinearLayout.LayoutParams mClockIconsSeparatorLayoutParams =
400                 (LinearLayout.LayoutParams) mClockIconsSeparator.getLayoutParams();
401         boolean cornerCutout = cornerCutoutPadding != null
402                 && (cornerCutoutPadding.first == 0 || cornerCutoutPadding.second == 0);
403         if (cutout != null) {
404             Rect topCutout = cutout.getBoundingRectTop();
405             if (topCutout.isEmpty() || cornerCutout) {
406                 datePrivacySeparatorLayoutParams.width = 0;
407                 mDatePrivacySeparator.setVisibility(View.GONE);
408                 mClockIconsSeparatorLayoutParams.width = 0;
409                 setSeparatorVisibility(false);
410                 mShowClockIconsSeparator = false;
411                 mHasCenterCutout = false;
412             } else {
413                 datePrivacySeparatorLayoutParams.width = topCutout.width();
414                 mDatePrivacySeparator.setVisibility(View.VISIBLE);
415                 mClockIconsSeparatorLayoutParams.width = topCutout.width();
416                 mShowClockIconsSeparator = true;
417                 setSeparatorVisibility(mKeyguardExpansionFraction == 0f);
418                 mHasCenterCutout = true;
419             }
420         }
421         mDatePrivacySeparator.setLayoutParams(datePrivacySeparatorLayoutParams);
422         mClockIconsSeparator.setLayoutParams(mClockIconsSeparatorLayoutParams);
423         mCutOutPaddingLeft = padding.first;
424         mCutOutPaddingRight = padding.second;
425         mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
426 
427         updateBatteryMode();
428         updateHeadersPadding();
429         return super.onApplyWindowInsets(insets);
430     }
431 
432     /**
433      * Sets the visibility of the separator between clock and icons.
434      *
435      * This separator is "visible" when there is a center cutout, to block that space. In that
436      * case, the clock and the layout on the right (containing the icons and the battery meter) are
437      * set to weight 1 to take the available space.
438      * @param visible whether the separator between clock and icons should be visible.
439      */
setSeparatorVisibility(boolean visible)440     private void setSeparatorVisibility(boolean visible) {
441         int newVisibility = visible ? View.VISIBLE : View.GONE;
442         if (mClockIconsSeparator.getVisibility() == newVisibility) return;
443 
444         mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE);
445         mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE);
446 
447         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams();
448         lp.width = visible ? 0 : WRAP_CONTENT;
449         lp.weight = visible ? 1f : 0f;
450         mClockView.setLayoutParams(lp);
451 
452         lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams();
453         lp.width = visible ? 0 : WRAP_CONTENT;
454         lp.weight = visible ? 1f : 0f;
455         mRightLayout.setLayoutParams(lp);
456     }
457 
updateHeadersPadding()458     private void updateHeadersPadding() {
459         setContentMargins(mDatePrivacyView, 0, 0);
460         setContentMargins(mClockIconsView, 0, 0);
461         int paddingLeft = 0;
462         int paddingRight = 0;
463 
464         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
465         int leftMargin = lp.leftMargin;
466         int rightMargin = lp.rightMargin;
467 
468         // The clock might collide with cutouts, let's shift it out of the way.
469         // We only do that if the inset is bigger than our own padding, since it's nicer to
470         // align with
471         if (mCutOutPaddingLeft > 0) {
472             // if there's a cutout, let's use at least the rounded corner inset
473             int cutoutPadding = Math.max(mCutOutPaddingLeft, mRoundedCornerPadding);
474             paddingLeft = Math.max(cutoutPadding - leftMargin, 0);
475         }
476         if (mCutOutPaddingRight > 0) {
477             // if there's a cutout, let's use at least the rounded corner inset
478             int cutoutPadding = Math.max(mCutOutPaddingRight, mRoundedCornerPadding);
479             paddingRight = Math.max(cutoutPadding - rightMargin, 0);
480         }
481 
482         mDatePrivacyView.setPadding(paddingLeft,
483                 mWaterfallTopInset,
484                 paddingRight,
485                 0);
486         mClockIconsView.setPadding(paddingLeft,
487                 mWaterfallTopInset,
488                 paddingRight,
489                 0);
490     }
491 
updateEverything()492     public void updateEverything() {
493         post(() -> setClickable(!mExpanded));
494     }
495 
setCallback(Callback qsPanelCallback)496     public void setCallback(Callback qsPanelCallback) {
497         mHeaderQsPanel.setCallback(qsPanelCallback);
498     }
499 
setContentMargins(View view, int marginStart, int marginEnd)500     private void setContentMargins(View view, int marginStart, int marginEnd) {
501         MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
502         lp.setMarginStart(marginStart);
503         lp.setMarginEnd(marginEnd);
504         view.setLayoutParams(lp);
505     }
506 
507     /**
508      * Scroll the headers away.
509      *
510      * @param scrollY the scroll of the QSPanel container
511      */
setExpandedScrollAmount(int scrollY)512     public void setExpandedScrollAmount(int scrollY) {
513         mClockIconsView.setScrollY(scrollY);
514         mDatePrivacyView.setScrollY(scrollY);
515     }
516 }
517