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