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