blob: b33424c563a38398bcf5f495bc5becd738e9ff78 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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
17package com.android.systemui.statusbar;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.content.res.Resources;
22import android.graphics.Point;
23import android.graphics.Rect;
24import android.os.Bundle;
25import android.os.Parcelable;
26import android.util.AttributeSet;
27import android.view.DisplayCutout;
28import android.view.View;
29import android.widget.TextView;
30
31import com.android.internal.annotations.VisibleForTesting;
32import com.android.keyguard.AlphaOptimizedLinearLayout;
33import com.android.systemui.R;
34import com.android.systemui.plugins.DarkIconDispatcher;
35import com.android.systemui.statusbar.notification.collection.NotificationEntry;
36import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;
37
38import java.util.List;
39
40/**
41 * The view in the statusBar that contains part of the heads-up information
42 */
43public 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
66 public HeadsUpStatusBarView(Context context) {
67 this(context, null);
68 }
69
70 public HeadsUpStatusBarView(Context context, AttributeSet attrs) {
71 this(context, attrs, 0);
72 }
73
74 public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
75 this(context, attrs, defStyleAttr, 0);
76 }
77
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
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
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
112 protected void onConfigurationChanged(Configuration newConfig) {
113 super.onConfigurationChanged(newConfig);
114 updateMaxWidth();
115 }
116
117 @Override
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
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
149 public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) {
150 this(context);
151 mIconPlaceholder = iconPlaceholder;
152 mTextView = textView;
153 }
154
155 @Override
156 protected void onFinishInflate() {
157 super.onFinishInflate();
158 mIconPlaceholder = findViewById(R.id.icon_placeholder);
159 mTextView = findViewById(R.id.text);
160 }
161
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
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 */
230 public void setPanelTranslation(float translationX) {
231 setTranslationX(translationX);
232 updateDrawingRect();
233 }
234
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
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
273 public NotificationEntry getShowingEntry() {
274 return mShowingEntry;
275 }
276
277 public Rect getIconDrawingRect() {
278 return mIconDrawingRect;
279 }
280
281 public void onDarkChanged(Rect area, float darkIntensity, int tint) {
282 mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint));
283 }
284
285 public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) {
286 mOnDrawingRectChangedListener = onDrawingRectChangedListener;
287 }
288
289 private void getDisplaySize() {
290 if (mDisplaySize == null) {
291 mDisplaySize = new Point();
292 }
293 getDisplay().getRealSize(mDisplaySize);
294 }
295
296 @Override
297 protected void onAttachedToWindow() {
298 super.onAttachedToWindow();
299 getDisplaySize();
300 }
301}