blob: 1104318763c0796eebfd51bceb80010d2b47e2ac [file] [log] [blame]
Adrian Roosc1a80b02016-04-05 14:54:55 -07001/*
2 * Copyright (C) 2016 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.internal.widget;
18
Adrian Roosc1a80b02016-04-05 14:54:55 -070019import android.annotation.Nullable;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.util.AttributeSet;
24import android.view.RemotableViewMethod;
25import android.view.View;
26import android.view.ViewGroup;
27import android.widget.RemoteViews;
28
Selim Cinek9281e5e2017-03-08 17:29:55 -080029import com.android.internal.R;
30
Adrian Roosc1a80b02016-04-05 14:54:55 -070031/**
32 * A custom-built layout for the Notification.MessagingStyle.
33 *
34 * Evicts children until they all fit.
35 */
36@RemoteViews.RemoteView
37public class MessagingLinearLayout extends ViewGroup {
38
Selim Cinek64aed1a2017-02-08 14:10:03 -080039 private static final int NOT_MEASURED_BEFORE = -1;
Adrian Roosc1a80b02016-04-05 14:54:55 -070040 /**
41 * Spacing to be applied between views.
42 */
43 private int mSpacing;
44
45 /**
46 * The maximum height allowed.
47 */
48 private int mMaxHeight;
49
50 private int mIndentLines;
51
Adrian Roosfeafa052016-06-01 17:09:45 -070052 /**
53 * Id of the child that's also visible in the contracted layout.
54 */
55 private int mContractedChildId;
Selim Cinek64aed1a2017-02-08 14:10:03 -080056 /**
57 * The last measured with in a layout pass if it was measured before or
58 * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
59 */
60 private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
Adrian Roosfeafa052016-06-01 17:09:45 -070061
Adrian Roosc1a80b02016-04-05 14:54:55 -070062 public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
63 super(context, attrs);
64
65 final TypedArray a = context.obtainStyledAttributes(attrs,
66 R.styleable.MessagingLinearLayout, 0,
67 0);
68
69 final int N = a.getIndexCount();
70 for (int i = 0; i < N; i++) {
71 int attr = a.getIndex(i);
72 switch (attr) {
Adrian Roosc1a80b02016-04-05 14:54:55 -070073 case R.styleable.MessagingLinearLayout_spacing:
74 mSpacing = a.getDimensionPixelSize(i, 0);
75 break;
76 }
77 }
78
Adrian Roosc1a80b02016-04-05 14:54:55 -070079 a.recycle();
80 }
81
82
83 @Override
84 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
85 // This is essentially a bottom-up linear layout that only adds children that fit entirely
86 // up to a maximum height.
Selim Cinek64aed1a2017-02-08 14:10:03 -080087 int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
Adrian Roosc1a80b02016-04-05 14:54:55 -070088 switch (MeasureSpec.getMode(heightMeasureSpec)) {
Adrian Roosc1a80b02016-04-05 14:54:55 -070089 case MeasureSpec.UNSPECIFIED:
Selim Cinek64aed1a2017-02-08 14:10:03 -080090 targetHeight = Integer.MAX_VALUE;
Adrian Roosc1a80b02016-04-05 14:54:55 -070091 break;
92 }
Selim Cinek64aed1a2017-02-08 14:10:03 -080093 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
94 boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
95 || getMeasuredHeight() != targetHeight
96 || mLastMeasuredWidth != widthSize;
97
Adrian Roosc1a80b02016-04-05 14:54:55 -070098 final int count = getChildCount();
Selim Cinek64aed1a2017-02-08 14:10:03 -080099 if (recalculateVisibility) {
100 // We only need to recalculate the view visibilities if the view wasn't measured already
101 // in this pass, otherwise we may drop messages here already since we are measured
102 // exactly with what we returned before, which was optimized already with the
103 // line-indents.
104 for (int i = 0; i < count; ++i) {
105 final View child = getChildAt(i);
106 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
107 lp.hide = true;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700108 }
109
Selim Cinek64aed1a2017-02-08 14:10:03 -0800110 int totalHeight = mPaddingTop + mPaddingBottom;
111 boolean first = true;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700112
Selim Cinek64aed1a2017-02-08 14:10:03 -0800113 // Starting from the bottom: we measure every view as if it were the only one. If it still
Adrian Roosc1a80b02016-04-05 14:54:55 -0700114
Selim Cinek64aed1a2017-02-08 14:10:03 -0800115 // fits, we take it, otherwise we stop there.
116 for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
117 if (getChildAt(i).getVisibility() == GONE) {
118 continue;
119 }
120 final View child = getChildAt(i);
121 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
Selim Cinek9281e5e2017-03-08 17:29:55 -0800122 ImageFloatingTextView textChild = null;
Selim Cinek64aed1a2017-02-08 14:10:03 -0800123 if (child instanceof ImageFloatingTextView) {
124 // Pretend we need the image padding for all views, we don't know which
125 // one will end up needing to do this (might end up not using all the space,
126 // but calculating this exactly would be more expensive).
Selim Cinek9281e5e2017-03-08 17:29:55 -0800127 textChild = (ImageFloatingTextView) child;
128 textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
Selim Cinek64aed1a2017-02-08 14:10:03 -0800129 }
130
Selim Cinek9281e5e2017-03-08 17:29:55 -0800131 int spacing = first ? 0 : mSpacing;
132 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
133 - mPaddingTop - mPaddingBottom + spacing);
Selim Cinek64aed1a2017-02-08 14:10:03 -0800134
135 final int childHeight = child.getMeasuredHeight();
136 int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
Selim Cinek9281e5e2017-03-08 17:29:55 -0800137 lp.bottomMargin + spacing);
Selim Cinek64aed1a2017-02-08 14:10:03 -0800138 first = false;
Selim Cinek9281e5e2017-03-08 17:29:55 -0800139 boolean measuredTooSmall = false;
140 if (textChild != null) {
141 measuredTooSmall = childHeight < textChild.getLayout().getHeight()
142 + textChild.getPaddingTop() + textChild.getPaddingBottom();
143 }
Selim Cinek64aed1a2017-02-08 14:10:03 -0800144
Selim Cinek9281e5e2017-03-08 17:29:55 -0800145 if (newHeight <= targetHeight && !measuredTooSmall) {
Selim Cinek64aed1a2017-02-08 14:10:03 -0800146 totalHeight = newHeight;
147 lp.hide = false;
148 } else {
149 break;
150 }
Adrian Roosc1a80b02016-04-05 14:54:55 -0700151 }
152 }
153
154 // Now that we know which views to take, fix up the indents and see what width we get.
155 int measuredWidth = mPaddingLeft + mPaddingRight;
156 int imageLines = mIndentLines;
Adrian Roos4ac8f402016-12-05 15:42:16 -0800157 // Need to redo the height because it may change due to changing indents.
Selim Cinek64aed1a2017-02-08 14:10:03 -0800158 int totalHeight = mPaddingTop + mPaddingBottom;
159 boolean first = true;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700160 for (int i = 0; i < count; i++) {
161 final View child = getChildAt(i);
162 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
163
164 if (child.getVisibility() == GONE || lp.hide) {
165 continue;
166 }
167
168 if (child instanceof ImageFloatingTextView) {
169 ImageFloatingTextView textChild = (ImageFloatingTextView) child;
170 if (imageLines == 2 && textChild.getLineCount() > 2) {
171 // HACK: If we need indent for two lines, and they're coming from the same
172 // view, we need extra spacing to compensate for the lack of margins,
173 // so add an extra line of indent.
174 imageLines = 3;
175 }
176 boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
Selim Cinek64aed1a2017-02-08 14:10:03 -0800177 if (changed || !recalculateVisibility) {
Selim Cinek9281e5e2017-03-08 17:29:55 -0800178 final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
179 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
180 lp.width);
181 // we want to measure it at most as high as it is currently, otherwise we'll
182 // drop later lines
183 final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
184 targetHeight - child.getMeasuredHeight(), lp.height);
185
186 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700187 }
188 imageLines -= textChild.getLineCount();
189 }
190
191 measuredWidth = Math.max(measuredWidth,
192 child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
193 + mPaddingLeft + mPaddingRight);
Adrian Roos4ac8f402016-12-05 15:42:16 -0800194 totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
195 lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
196 first = false;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700197 }
198
199
200 setMeasuredDimension(
201 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
202 widthMeasureSpec),
203 resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
204 heightMeasureSpec));
Selim Cinek64aed1a2017-02-08 14:10:03 -0800205 mLastMeasuredWidth = widthSize;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700206 }
207
208 @Override
209 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
210 final int paddingLeft = mPaddingLeft;
211
212 int childTop;
213
214 // Where right end of child should go
215 final int width = right - left;
216 final int childRight = width - mPaddingRight;
217
218 final int layoutDirection = getLayoutDirection();
219 final int count = getChildCount();
220
221 childTop = mPaddingTop;
222
223 boolean first = true;
224
225 for (int i = 0; i < count; i++) {
226 final View child = getChildAt(i);
227 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
228
229 if (child.getVisibility() == GONE || lp.hide) {
230 continue;
231 }
232
233 final int childWidth = child.getMeasuredWidth();
234 final int childHeight = child.getMeasuredHeight();
235
236 int childLeft;
237 if (layoutDirection == LAYOUT_DIRECTION_RTL) {
238 childLeft = childRight - childWidth - lp.rightMargin;
239 } else {
240 childLeft = paddingLeft + lp.leftMargin;
241 }
242
243 if (!first) {
244 childTop += mSpacing;
245 }
246
247 childTop += lp.topMargin;
248 child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
249
250 childTop += childHeight + lp.bottomMargin;
251
252 first = false;
253 }
Selim Cinek64aed1a2017-02-08 14:10:03 -0800254 mLastMeasuredWidth = NOT_MEASURED_BEFORE;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700255 }
256
257 @Override
258 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
259 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
260 if (lp.hide) {
261 return true;
262 }
263 return super.drawChild(canvas, child, drawingTime);
264 }
265
266 @Override
267 public LayoutParams generateLayoutParams(AttributeSet attrs) {
268 return new LayoutParams(mContext, attrs);
269 }
270
271 @Override
272 protected LayoutParams generateDefaultLayoutParams() {
273 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
274
275 }
276
277 @Override
278 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
279 LayoutParams copy = new LayoutParams(lp.width, lp.height);
280 if (lp instanceof MarginLayoutParams) {
281 copy.copyMarginsFrom((MarginLayoutParams) lp);
282 }
283 return copy;
284 }
285
Adrian Roosc1a80b02016-04-05 14:54:55 -0700286 /**
287 * Sets how many lines should be indented to avoid a floating image.
288 */
Adrian Roosfeafa052016-06-01 17:09:45 -0700289 @RemotableViewMethod
Adrian Roosc1a80b02016-04-05 14:54:55 -0700290 public void setNumIndentLines(int numberLines) {
291 mIndentLines = numberLines;
292 }
293
Adrian Roosfeafa052016-06-01 17:09:45 -0700294 /**
295 * Set id of the child that's also visible in the contracted layout.
296 */
297 @RemotableViewMethod
298 public void setContractedChildId(int contractedChildId) {
299 mContractedChildId = contractedChildId;
300 }
301
302 /**
303 * Get id of the child that's also visible in the contracted layout.
304 */
305 public int getContractedChildId() {
306 return mContractedChildId;
307 }
308
Adrian Roosc1a80b02016-04-05 14:54:55 -0700309 public static class LayoutParams extends MarginLayoutParams {
310
311 boolean hide = false;
312
313 public LayoutParams(Context c, AttributeSet attrs) {
314 super(c, attrs);
315 }
316
317 public LayoutParams(int width, int height) {
318 super(width, height);
319 }
320 }
321}