blob: c97c8ebd0545184892fac347ef26bebf1325e37c [file] [log] [blame]
Evan Laird058c8ae2018-01-12 14:26:10 -05001/*
2 * Copyright (C) 2017 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
Evan Laird058c8ae2018-01-12 14:26:10 -050017package com.android.systemui.statusbar.phone;
18
Evan Laird20b87bf2018-04-12 09:54:11 -040019import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
20import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
21import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
22
Evan Laird058c8ae2018-01-12 14:26:10 -050023import android.annotation.Nullable;
24import android.content.Context;
Evan Laird20b87bf2018-04-12 09:54:11 -040025import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.Paint;
28import android.graphics.Paint.Style;
Evan Laird058c8ae2018-01-12 14:26:10 -050029import android.util.AttributeSet;
Evan Lairde1d13c92018-03-20 16:58:01 -040030import android.util.Log;
Evan Laird058c8ae2018-01-12 14:26:10 -050031
32import android.view.View;
Evan Laird20b87bf2018-04-12 09:54:11 -040033import android.view.ViewGroup;
Evan Laird058c8ae2018-01-12 14:26:10 -050034import com.android.keyguard.AlphaOptimizedLinearLayout;
35import com.android.systemui.R;
Evan Lairde1d13c92018-03-20 16:58:01 -040036import com.android.systemui.statusbar.StatusIconDisplayable;
Evan Laird058c8ae2018-01-12 14:26:10 -050037import com.android.systemui.statusbar.stack.ViewState;
Evan Laird20b87bf2018-04-12 09:54:11 -040038import java.lang.ref.WeakReference;
39import java.util.ArrayList;
Evan Laird058c8ae2018-01-12 14:26:10 -050040
Evan Laird937d9fa2018-02-08 10:12:16 -080041/**
42 * A container for Status bar system icons. Limits the number of system icons and handles overflow
Evan Laird20b87bf2018-04-12 09:54:11 -040043 * similar to {@link NotificationIconContainer}.
Evan Laird937d9fa2018-02-08 10:12:16 -080044 *
Evan Laird20b87bf2018-04-12 09:54:11 -040045 * Children are expected to implement {@link StatusIconDisplayable}
Evan Laird937d9fa2018-02-08 10:12:16 -080046 */
Evan Laird058c8ae2018-01-12 14:26:10 -050047public class StatusIconContainer extends AlphaOptimizedLinearLayout {
48
49 private static final String TAG = "StatusIconContainer";
Evan Lairde0fbc3e2018-02-13 11:41:00 -050050 private static final boolean DEBUG = false;
Evan Laird20b87bf2018-04-12 09:54:11 -040051 private static final boolean DEBUG_OVERFLOW = false;
52 // Max 5 status icons including battery
53 private static final int MAX_ICONS = 4;
Evan Laird058c8ae2018-01-12 14:26:10 -050054 private static final int MAX_DOTS = 3;
55
Evan Laird20b87bf2018-04-12 09:54:11 -040056 private int mDotPadding;
57 private int mStaticDotDiameter;
58 private int mUnderflowWidth;
59 private int mUnderflowStart = 0;
60 // Whether or not we can draw into the underflow space
61 private boolean mNeedsUnderflow;
62 // Individual StatusBarIconViews draw their etc dots centered in this width
63 private int mIconDotFrameWidth;
64 private boolean mShouldRestrictIcons = true;
65 // Used to count which states want to be visible during layout
66 private ArrayList<StatusIconState> mLayoutStates = new ArrayList<>();
67 // So we can count and measure properly
68 private ArrayList<View> mMeasureViews = new ArrayList<>();
69
Evan Laird937d9fa2018-02-08 10:12:16 -080070 public StatusIconContainer(Context context) {
71 this(context, null);
72 }
73
Evan Laird058c8ae2018-01-12 14:26:10 -050074 public StatusIconContainer(Context context, AttributeSet attrs) {
75 super(context, attrs);
76 }
77
78 @Override
Evan Laird20b87bf2018-04-12 09:54:11 -040079 protected void onFinishInflate() {
80 super.onFinishInflate();
81 setWillNotDraw(!DEBUG_OVERFLOW);
82 initDimens();
83 }
84
85 public void setShouldRestrictIcons(boolean should) {
86 mShouldRestrictIcons = should;
87 }
88
89 private void initDimens() {
90 // This is the same value that StatusBarIconView uses
91 mIconDotFrameWidth = getResources().getDimensionPixelSize(
92 com.android.internal.R.dimen.status_bar_icon_size);
93 mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
94 int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
95 mStaticDotDiameter = 2 * radius;
96 mUnderflowWidth = mIconDotFrameWidth + 2 * (mStaticDotDiameter + mDotPadding);
97 }
98
99 @Override
Evan Laird058c8ae2018-01-12 14:26:10 -0500100 protected void onLayout(boolean changed, int l, int t, int r, int b) {
101 float midY = getHeight() / 2.0f;
102
103 // Layout all child views so that we can move them around later
104 for (int i = 0; i < getChildCount(); i++) {
105 View child = getChildAt(i);
106 int width = child.getMeasuredWidth();
107 int height = child.getMeasuredHeight();
108 int top = (int) (midY - height / 2.0f);
109 child.layout(0, top, width, top + height);
110 }
111
112 resetViewStates();
113 calculateIconTranslations();
114 applyIconStates();
115 }
116
117 @Override
Evan Laird20b87bf2018-04-12 09:54:11 -0400118 protected void onDraw(Canvas canvas) {
119 super.onDraw(canvas);
120 if (DEBUG_OVERFLOW) {
121 Paint paint = new Paint();
122 paint.setStyle(Style.STROKE);
123 paint.setColor(Color.RED);
124
125 // Show bounding box
126 canvas.drawRect(getPaddingStart(), 0, getWidth() - getPaddingEnd(), getHeight(), paint);
127
128 // Show etc box
129 paint.setColor(Color.GREEN);
130 canvas.drawRect(
131 mUnderflowStart, 0, mUnderflowStart + mUnderflowWidth, getHeight(), paint);
132 }
133 }
134
135 @Override
Evan Laird058c8ae2018-01-12 14:26:10 -0500136 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Evan Laird20b87bf2018-04-12 09:54:11 -0400137 mMeasureViews.clear();
138 int mode = MeasureSpec.getMode(widthMeasureSpec);
139 final int width = MeasureSpec.getSize(widthMeasureSpec);
Evan Laird058c8ae2018-01-12 14:26:10 -0500140 final int count = getChildCount();
Evan Laird20b87bf2018-04-12 09:54:11 -0400141 // Collect all of the views which want to be laid out
Evan Laird058c8ae2018-01-12 14:26:10 -0500142 for (int i = 0; i < count; i++) {
Evan Laird20b87bf2018-04-12 09:54:11 -0400143 StatusIconDisplayable icon = (StatusIconDisplayable) getChildAt(i);
144 if (icon.isIconVisible() && !icon.isIconBlocked()) {
145 mMeasureViews.add((View) icon);
146 }
147 }
148
149 int visibleCount = mMeasureViews.size();
150 int maxVisible = visibleCount <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
151 int totalWidth = getPaddingStart() + getPaddingEnd();
152 boolean trackWidth = true;
153
154 // Measure all children so that they report the correct width
155 int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
156 mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS;
157 for (int i = 0; i < mMeasureViews.size(); i++) {
158 // Walking backwards
159 View child = mMeasureViews.get(visibleCount - i - 1);
160 measureChild(child, childWidthSpec, heightMeasureSpec);
161 if (mShouldRestrictIcons) {
162 if (i < maxVisible && trackWidth) {
163 totalWidth += getViewTotalMeasuredWidth(child);
164 } else if (trackWidth) {
165 // We've hit the icon limit; add space for dots
166 totalWidth += mUnderflowWidth;
167 trackWidth = false;
168 }
169 } else {
170 totalWidth += getViewTotalMeasuredWidth(child);
171 }
172 }
173
174 if (mode == MeasureSpec.EXACTLY) {
175 if (!mNeedsUnderflow && totalWidth > width) {
176 mNeedsUnderflow = true;
177 }
178 setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
179 } else {
180 if (mode == MeasureSpec.AT_MOST && totalWidth > width) {
181 mNeedsUnderflow = true;
182 totalWidth = width;
183 }
184 setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
Evan Laird058c8ae2018-01-12 14:26:10 -0500185 }
186 }
187
188 @Override
189 public void onViewAdded(View child) {
190 super.onViewAdded(child);
Evan Laird20b87bf2018-04-12 09:54:11 -0400191 StatusIconState vs = new StatusIconState();
Evan Laird058c8ae2018-01-12 14:26:10 -0500192 child.setTag(R.id.status_bar_view_state_tag, vs);
193 }
194
195 @Override
196 public void onViewRemoved(View child) {
197 super.onViewRemoved(child);
198 child.setTag(R.id.status_bar_view_state_tag, null);
199 }
200
201 /**
202 * Layout is happening from end -> start
203 */
204 private void calculateIconTranslations() {
Evan Laird20b87bf2018-04-12 09:54:11 -0400205 mLayoutStates.clear();
Evan Lairde1d13c92018-03-20 16:58:01 -0400206 float width = getWidth() - getPaddingEnd();
Evan Laird63036132018-02-02 16:30:41 -0500207 float translationX = width;
Evan Laird058c8ae2018-01-12 14:26:10 -0500208 float contentStart = getPaddingStart();
209 int childCount = getChildCount();
210 // Underflow === don't show content until that index
211 int firstUnderflowIndex = -1;
Evan Laird264275e2018-03-01 19:39:39 -0500212 if (DEBUG) android.util.Log.d(TAG, "calculateIconTransitions: start=" + translationX
213 + " width=" + width);
Evan Laird058c8ae2018-01-12 14:26:10 -0500214
Evan Laird20b87bf2018-04-12 09:54:11 -0400215 // Collect all of the states which want to be visible
Evan Laird058c8ae2018-01-12 14:26:10 -0500216 for (int i = childCount - 1; i >= 0; i--) {
217 View child = getChildAt(i);
Evan Lairde1d13c92018-03-20 16:58:01 -0400218 StatusIconDisplayable iconView = (StatusIconDisplayable) child;
Evan Laird20b87bf2018-04-12 09:54:11 -0400219 StatusIconState childState = getViewStateFromChild(child);
Evan Laird058c8ae2018-01-12 14:26:10 -0500220
Evan Lairde1d13c92018-03-20 16:58:01 -0400221 if (!iconView.isIconVisible() || iconView.isIconBlocked()) {
Evan Laird20b87bf2018-04-12 09:54:11 -0400222 childState.visibleState = STATE_HIDDEN;
Evan Lairde1d13c92018-03-20 16:58:01 -0400223 if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") not visible");
Evan Laird058c8ae2018-01-12 14:26:10 -0500224 continue;
225 }
226
Evan Laird20b87bf2018-04-12 09:54:11 -0400227 childState.visibleState = STATE_ICON;
228 childState.xTranslation = translationX - getViewTotalWidth(child);
229 mLayoutStates.add(0, childState);
Evan Laird058c8ae2018-01-12 14:26:10 -0500230
Evan Laird20b87bf2018-04-12 09:54:11 -0400231 translationX -= getViewTotalWidth(child);
232 }
233
234 // Show either 1-4 dots, or 3 dots + overflow
235 int totalVisible = mLayoutStates.size();
236 int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
237
238 mUnderflowStart = 0;
239 int visible = 0;
240 firstUnderflowIndex = -1;
241 for (int i = totalVisible - 1; i >= 0; i--) {
242 StatusIconState state = mLayoutStates.get(i);
243 // Allow room for underflow if we found we need it in onMeasure
244 if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth))||
245 (mShouldRestrictIcons && visible >= maxVisible)) {
246 firstUnderflowIndex = i;
247 break;
Evan Laird058c8ae2018-01-12 14:26:10 -0500248 }
Evan Laird20b87bf2018-04-12 09:54:11 -0400249 mUnderflowStart = (int) Math.max(contentStart, state.xTranslation - mUnderflowWidth);
250 visible++;
Evan Laird058c8ae2018-01-12 14:26:10 -0500251 }
252
253 if (firstUnderflowIndex != -1) {
Evan Laird20b87bf2018-04-12 09:54:11 -0400254 int totalDots = 0;
255 int dotWidth = mStaticDotDiameter + mDotPadding;
256 int dotOffset = mUnderflowStart + mUnderflowWidth - mIconDotFrameWidth;
257 for (int i = firstUnderflowIndex; i >= 0; i--) {
258 StatusIconState state = mLayoutStates.get(i);
259 if (totalDots < MAX_DOTS) {
260 state.xTranslation = dotOffset;
261 state.visibleState = STATE_DOT;
262 dotOffset -= dotWidth;
263 totalDots++;
264 } else {
265 state.visibleState = STATE_HIDDEN;
Evan Laird058c8ae2018-01-12 14:26:10 -0500266 }
267 }
268 }
Evan Laird63036132018-02-02 16:30:41 -0500269
270 // Stole this from NotificationIconContainer. Not optimal but keeps the layout logic clean
271 if (isLayoutRtl()) {
272 for (int i = 0; i < childCount; i++) {
273 View child = getChildAt(i);
Evan Laird20b87bf2018-04-12 09:54:11 -0400274 StatusIconState state = getViewStateFromChild(child);
Evan Laird63036132018-02-02 16:30:41 -0500275 state.xTranslation = width - state.xTranslation - child.getWidth();
276 }
277 }
Evan Laird058c8ae2018-01-12 14:26:10 -0500278 }
279
280 private void applyIconStates() {
281 for (int i = 0; i < getChildCount(); i++) {
282 View child = getChildAt(i);
Evan Laird20b87bf2018-04-12 09:54:11 -0400283 StatusIconState vs = getViewStateFromChild(child);
Evan Laird058c8ae2018-01-12 14:26:10 -0500284 if (vs != null) {
285 vs.applyToView(child);
286 }
287 }
288 }
289
290 private void resetViewStates() {
291 for (int i = 0; i < getChildCount(); i++) {
292 View child = getChildAt(i);
Evan Laird20b87bf2018-04-12 09:54:11 -0400293 StatusIconState vs = getViewStateFromChild(child);
Evan Laird058c8ae2018-01-12 14:26:10 -0500294 if (vs == null) {
295 continue;
296 }
297
298 vs.initFrom(child);
299 vs.alpha = 1.0f;
Evan Lairde1d13c92018-03-20 16:58:01 -0400300 if (child instanceof StatusIconDisplayable) {
301 vs.hidden = !((StatusIconDisplayable)child).isIconVisible();
Evan Laird058c8ae2018-01-12 14:26:10 -0500302 } else {
303 vs.hidden = false;
304 }
305 }
306 }
307
Evan Laird20b87bf2018-04-12 09:54:11 -0400308 private static @Nullable StatusIconState getViewStateFromChild(View child) {
309 return (StatusIconState) child.getTag(R.id.status_bar_view_state_tag);
310 }
311
312 private static int getViewTotalMeasuredWidth(View child) {
313 return child.getMeasuredWidth() + child.getPaddingStart() + child.getPaddingEnd();
314 }
315
316 private static int getViewTotalWidth(View child) {
317 return child.getWidth() + child.getPaddingStart() + child.getPaddingEnd();
318 }
319
320 public static class StatusIconState extends ViewState {
321 /// StatusBarIconView.STATE_*
322 public int visibleState = STATE_ICON;
323
324 @Override
325 public void applyToView(View view) {
326 if (view instanceof StatusIconDisplayable) {
327 StatusIconDisplayable icon = (StatusIconDisplayable) view;
328 icon.setVisibleState(visibleState);
329 }
330 super.applyToView(view);
331 }
Evan Laird058c8ae2018-01-12 14:26:10 -0500332 }
333}