• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.statusbar;
18 
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.content.res.Resources;
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.os.Bundle;
25 import android.os.Parcelable;
26 import android.util.AttributeSet;
27 import android.view.DisplayCutout;
28 import android.view.View;
29 import android.widget.TextView;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.keyguard.AlphaOptimizedLinearLayout;
33 import com.android.systemui.R;
34 import com.android.systemui.plugins.DarkIconDispatcher;
35 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
36 import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;
37 
38 import java.util.List;
39 
40 /**
41  * The view in the statusBar that contains part of the heads-up information
42  */
43 public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
44     private static final String HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE =
45             "heads_up_status_bar_view_super_parcelable";
46     private static final String FIRST_LAYOUT = "first_layout";
47     private static final String VISIBILITY = "visibility";
48     private static final String ALPHA = "alpha";
49     private int mAbsoluteStartPadding;
50     private int mEndMargin;
51     private View mIconPlaceholder;
52     private TextView mTextView;
53     private NotificationEntry mShowingEntry;
54     private Rect mLayoutedIconRect = new Rect();
55     private int[] mTmpPosition = new int[2];
56     private boolean mFirstLayout = true;
57     private int mMaxWidth;
58     private View mRootView;
59     private int mSysWinInset;
60     private int mCutOutInset;
61     private List<Rect> mCutOutBounds;
62     private Rect mIconDrawingRect = new Rect();
63     private Point mDisplaySize;
64     private Runnable mOnDrawingRectChangedListener;
65 
HeadsUpStatusBarView(Context context)66     public HeadsUpStatusBarView(Context context) {
67         this(context, null);
68     }
69 
HeadsUpStatusBarView(Context context, AttributeSet attrs)70     public HeadsUpStatusBarView(Context context, AttributeSet attrs) {
71         this(context, attrs, 0);
72     }
73 
HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr)74     public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
75         this(context, attrs, defStyleAttr, 0);
76     }
77 
HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)78     public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr,
79             int defStyleRes) {
80         super(context, attrs, defStyleAttr, defStyleRes);
81         Resources res = getResources();
82         mAbsoluteStartPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings)
83             + res.getDimensionPixelSize(
84                     com.android.internal.R.dimen.notification_content_margin_start);
85         mEndMargin = res.getDimensionPixelSize(
86                 com.android.internal.R.dimen.notification_content_margin_end);
87         setPaddingRelative(mAbsoluteStartPadding, 0, mEndMargin, 0);
88         updateMaxWidth();
89     }
90 
updateMaxWidth()91     private void updateMaxWidth() {
92         int maxWidth = getResources().getDimensionPixelSize(R.dimen.qs_panel_width);
93         if (maxWidth != mMaxWidth) {
94             // maxWidth doesn't work with fill_parent, let's manually make it at most as big as the
95             // notification panel
96             mMaxWidth = maxWidth;
97             requestLayout();
98         }
99     }
100 
101     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)102     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
103         if (mMaxWidth > 0) {
104             int newSize = Math.min(MeasureSpec.getSize(widthMeasureSpec), mMaxWidth);
105             widthMeasureSpec = MeasureSpec.makeMeasureSpec(newSize,
106                     MeasureSpec.getMode(widthMeasureSpec));
107         }
108         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
109     }
110 
111     @Override
onConfigurationChanged(Configuration newConfig)112     protected void onConfigurationChanged(Configuration newConfig) {
113         super.onConfigurationChanged(newConfig);
114         updateMaxWidth();
115     }
116 
117     @Override
onSaveInstanceState()118     public Bundle onSaveInstanceState() {
119         Bundle bundle = new Bundle();
120         bundle.putParcelable(HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE,
121                 super.onSaveInstanceState());
122         bundle.putBoolean(FIRST_LAYOUT, mFirstLayout);
123         bundle.putInt(VISIBILITY, getVisibility());
124         bundle.putFloat(ALPHA, getAlpha());
125 
126         return bundle;
127     }
128 
129     @Override
onRestoreInstanceState(Parcelable state)130     public void onRestoreInstanceState(Parcelable state) {
131         if (state == null || !(state instanceof Bundle)) {
132             super.onRestoreInstanceState(state);
133             return;
134         }
135 
136         Bundle bundle = (Bundle) state;
137         Parcelable superState = bundle.getParcelable(HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE);
138         super.onRestoreInstanceState(superState);
139         mFirstLayout = bundle.getBoolean(FIRST_LAYOUT, true);
140         if (bundle.containsKey(VISIBILITY)) {
141             setVisibility(bundle.getInt(VISIBILITY));
142         }
143         if (bundle.containsKey(ALPHA)) {
144             setAlpha(bundle.getFloat(ALPHA));
145         }
146     }
147 
148     @VisibleForTesting
HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView)149     public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) {
150         this(context);
151         mIconPlaceholder = iconPlaceholder;
152         mTextView = textView;
153     }
154 
155     @Override
onFinishInflate()156     protected void onFinishInflate() {
157         super.onFinishInflate();
158         mIconPlaceholder = findViewById(R.id.icon_placeholder);
159         mTextView = findViewById(R.id.text);
160     }
161 
setEntry(NotificationEntry entry)162     public void setEntry(NotificationEntry entry) {
163         if (mShowingEntry != null) {
164             mShowingEntry.removeOnSensitivityChangedListener(mOnSensitivityChangedListener);
165         }
166         mShowingEntry = entry;
167 
168         if (mShowingEntry != null) {
169             CharSequence text = entry.headsUpStatusBarText;
170             if (entry.isSensitive()) {
171                 text = entry.headsUpStatusBarTextPublic;
172             }
173             mTextView.setText(text);
174             mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener);
175         }
176     }
177 
178     private final OnSensitivityChangedListener mOnSensitivityChangedListener = entry -> {
179         if (entry != mShowingEntry) {
180             throw new IllegalStateException("Got a sensitivity change for " + entry
181                     + " but mShowingEntry is " + mShowingEntry);
182         }
183         // Update the text
184         setEntry(entry);
185     };
186 
187     @Override
onLayout(boolean changed, int l, int t, int r, int b)188     protected void onLayout(boolean changed, int l, int t, int r, int b) {
189         super.onLayout(changed, l, t, r, b);
190         mIconPlaceholder.getLocationOnScreen(mTmpPosition);
191         int left = (int) (mTmpPosition[0] - getTranslationX());
192         int top = mTmpPosition[1];
193         int right = left + mIconPlaceholder.getWidth();
194         int bottom = top + mIconPlaceholder.getHeight();
195         mLayoutedIconRect.set(left, top, right, bottom);
196         updateDrawingRect();
197         int targetPadding = mAbsoluteStartPadding + mSysWinInset + mCutOutInset;
198         boolean isRtl = isLayoutRtl();
199         int start = isRtl ? (mDisplaySize.x - right) : left;
200 
201         if (start != targetPadding) {
202             if (mCutOutBounds != null) {
203                 for (Rect cutOutRect : mCutOutBounds) {
204                     int cutOutStart = (isRtl)
205                             ? (mDisplaySize.x - cutOutRect.right) : cutOutRect.left;
206                     if (start > cutOutStart) {
207                         start -= cutOutRect.width();
208                         break;
209                     }
210                 }
211             }
212 
213             int newPadding = targetPadding - start + getPaddingStart();
214             setPaddingRelative(newPadding, 0, mEndMargin, 0);
215         }
216         if (mFirstLayout) {
217             // we need to do the padding calculation in the first frame, so the layout specified
218             // our visibility to be INVISIBLE in the beginning. let's correct that and set it
219             // to GONE.
220             setVisibility(GONE);
221             mFirstLayout = false;
222         }
223     }
224 
225     /** In order to do UI alignment, this view will be notified by
226      * {@link com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout}.
227      * After scroller laid out, the scroller will tell this view about scroller's getX()
228      * @param translationX how to translate the horizontal position
229      */
setPanelTranslation(float translationX)230     public void setPanelTranslation(float translationX) {
231         setTranslationX(translationX);
232         updateDrawingRect();
233     }
234 
updateDrawingRect()235     private void updateDrawingRect() {
236         float oldLeft = mIconDrawingRect.left;
237         mIconDrawingRect.set(mLayoutedIconRect);
238         mIconDrawingRect.offset((int) getTranslationX(), 0);
239         if (oldLeft != mIconDrawingRect.left && mOnDrawingRectChangedListener != null) {
240             mOnDrawingRectChangedListener.run();
241         }
242     }
243 
244     @Override
fitSystemWindows(Rect insets)245     protected boolean fitSystemWindows(Rect insets) {
246         boolean isRtl = isLayoutRtl();
247         mSysWinInset = isRtl ? insets.right : insets.left;
248         DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
249         mCutOutInset = (displayCutout != null)
250                 ? (isRtl ? displayCutout.getSafeInsetRight() : displayCutout.getSafeInsetLeft())
251                 : 0;
252 
253         getDisplaySize();
254 
255         mCutOutBounds = null;
256         if (displayCutout != null && displayCutout.getSafeInsetRight() == 0
257                 && displayCutout.getSafeInsetLeft() == 0) {
258             mCutOutBounds = displayCutout.getBoundingRects();
259         }
260 
261         // For Double Cut Out mode, the System window navigation bar is at the right
262         // side of the left cut out. In this condition, mSysWinInset include the left cut
263         // out width so we set mCutOutInset to be 0. For RTL, the condition is the same.
264         // The navigation bar is at the left side of the right cut out and include the
265         // right cut out width.
266         if (mSysWinInset != 0) {
267             mCutOutInset = 0;
268         }
269 
270         return super.fitSystemWindows(insets);
271     }
272 
getShowingEntry()273     public NotificationEntry getShowingEntry() {
274         return mShowingEntry;
275     }
276 
getIconDrawingRect()277     public Rect getIconDrawingRect() {
278         return mIconDrawingRect;
279     }
280 
onDarkChanged(Rect area, float darkIntensity, int tint)281     public void onDarkChanged(Rect area, float darkIntensity, int tint) {
282         mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint));
283     }
284 
setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener)285     public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) {
286         mOnDrawingRectChangedListener = onDrawingRectChangedListener;
287     }
288 
getDisplaySize()289     private void getDisplaySize() {
290         if (mDisplaySize == null) {
291             mDisplaySize = new Point();
292         }
293         getDisplay().getRealSize(mDisplaySize);
294     }
295 
296     @Override
onAttachedToWindow()297     protected void onAttachedToWindow() {
298         super.onAttachedToWindow();
299         getDisplaySize();
300     }
301 }
302