blob: d2a43b755f1839ff0bcf129c5bb4d21e18d38aec [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
19import com.android.internal.R;
20
21import android.annotation.Nullable;
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.util.AttributeSet;
26import android.view.RemotableViewMethod;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.RemoteViews;
30
31/**
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
39 /**
40 * Spacing to be applied between views.
41 */
42 private int mSpacing;
43
44 /**
45 * The maximum height allowed.
46 */
47 private int mMaxHeight;
48
49 private int mIndentLines;
50
Adrian Roosfeafa052016-06-01 17:09:45 -070051 /**
52 * Id of the child that's also visible in the contracted layout.
53 */
54 private int mContractedChildId;
55
Adrian Roosc1a80b02016-04-05 14:54:55 -070056 public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
57 super(context, attrs);
58
59 final TypedArray a = context.obtainStyledAttributes(attrs,
60 R.styleable.MessagingLinearLayout, 0,
61 0);
62
63 final int N = a.getIndexCount();
64 for (int i = 0; i < N; i++) {
65 int attr = a.getIndex(i);
66 switch (attr) {
67 case R.styleable.MessagingLinearLayout_maxHeight:
68 mMaxHeight = a.getDimensionPixelSize(i, 0);
69 break;
70 case R.styleable.MessagingLinearLayout_spacing:
71 mSpacing = a.getDimensionPixelSize(i, 0);
72 break;
73 }
74 }
75
76 if (mMaxHeight <= 0) {
77 throw new IllegalStateException(
78 "MessagingLinearLayout: Must specify positive maxHeight");
79 }
80
81 a.recycle();
82 }
83
84
85 @Override
86 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
87 // This is essentially a bottom-up linear layout that only adds children that fit entirely
88 // up to a maximum height.
89
90 switch (MeasureSpec.getMode(heightMeasureSpec)) {
91 case MeasureSpec.AT_MOST:
92 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
93 Math.min(mMaxHeight, MeasureSpec.getSize(heightMeasureSpec)),
94 MeasureSpec.AT_MOST);
95 break;
96 case MeasureSpec.UNSPECIFIED:
97 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
98 mMaxHeight,
99 MeasureSpec.AT_MOST);
100 break;
101 case MeasureSpec.EXACTLY:
102 break;
103 }
104 final int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
105 final int count = getChildCount();
106
107 for (int i = 0; i < count; ++i) {
108 final View child = getChildAt(i);
109 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
110 lp.hide = true;
111 }
112
113 int totalHeight = mPaddingTop + mPaddingBottom;
114 boolean first = true;
115
116 // Starting from the bottom: we measure every view as if it were the only one. If it still
117 // fits, we take it, otherwise we stop there.
118 for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
119 if (getChildAt(i).getVisibility() == GONE) {
120 continue;
121 }
122 final View child = getChildAt(i);
123 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
124
125 if (child instanceof ImageFloatingTextView) {
126 // Pretend we need the image padding for all views, we don't know which
127 // one will end up needing to do this (might end up not using all the space,
128 // but calculating this exactly would be more expensive).
129 ((ImageFloatingTextView) child).setNumIndentLines(mIndentLines);
130 }
131
132 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
133
134 final int childHeight = child.getMeasuredHeight();
135 int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
136 lp.bottomMargin + (first ? 0 : mSpacing));
137 first = false;
138
139 if (newHeight <= targetHeight) {
140 totalHeight = newHeight;
141 lp.hide = false;
142 } else {
143 break;
144 }
145 }
146
147 // Now that we know which views to take, fix up the indents and see what width we get.
148 int measuredWidth = mPaddingLeft + mPaddingRight;
149 int imageLines = mIndentLines;
150 for (int i = 0; i < count; i++) {
151 final View child = getChildAt(i);
152 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
153
154 if (child.getVisibility() == GONE || lp.hide) {
155 continue;
156 }
157
158 if (child instanceof ImageFloatingTextView) {
159 ImageFloatingTextView textChild = (ImageFloatingTextView) child;
160 if (imageLines == 2 && textChild.getLineCount() > 2) {
161 // HACK: If we need indent for two lines, and they're coming from the same
162 // view, we need extra spacing to compensate for the lack of margins,
163 // so add an extra line of indent.
164 imageLines = 3;
165 }
166 boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
167 if (changed) {
168 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
169 }
170 imageLines -= textChild.getLineCount();
171 }
172
173 measuredWidth = Math.max(measuredWidth,
174 child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
175 + mPaddingLeft + mPaddingRight);
176 }
177
178
179 setMeasuredDimension(
180 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
181 widthMeasureSpec),
182 resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
183 heightMeasureSpec));
184 }
185
186 @Override
187 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
188 final int paddingLeft = mPaddingLeft;
189
190 int childTop;
191
192 // Where right end of child should go
193 final int width = right - left;
194 final int childRight = width - mPaddingRight;
195
196 final int layoutDirection = getLayoutDirection();
197 final int count = getChildCount();
198
199 childTop = mPaddingTop;
200
201 boolean first = true;
202
203 for (int i = 0; i < count; i++) {
204 final View child = getChildAt(i);
205 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
206
207 if (child.getVisibility() == GONE || lp.hide) {
208 continue;
209 }
210
211 final int childWidth = child.getMeasuredWidth();
212 final int childHeight = child.getMeasuredHeight();
213
214 int childLeft;
215 if (layoutDirection == LAYOUT_DIRECTION_RTL) {
216 childLeft = childRight - childWidth - lp.rightMargin;
217 } else {
218 childLeft = paddingLeft + lp.leftMargin;
219 }
220
221 if (!first) {
222 childTop += mSpacing;
223 }
224
225 childTop += lp.topMargin;
226 child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
227
228 childTop += childHeight + lp.bottomMargin;
229
230 first = false;
231 }
232 }
233
234 @Override
235 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
236 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
237 if (lp.hide) {
238 return true;
239 }
240 return super.drawChild(canvas, child, drawingTime);
241 }
242
243 @Override
244 public LayoutParams generateLayoutParams(AttributeSet attrs) {
245 return new LayoutParams(mContext, attrs);
246 }
247
248 @Override
249 protected LayoutParams generateDefaultLayoutParams() {
250 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
251
252 }
253
254 @Override
255 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
256 LayoutParams copy = new LayoutParams(lp.width, lp.height);
257 if (lp instanceof MarginLayoutParams) {
258 copy.copyMarginsFrom((MarginLayoutParams) lp);
259 }
260 return copy;
261 }
262
Adrian Roosc1a80b02016-04-05 14:54:55 -0700263 /**
264 * Sets how many lines should be indented to avoid a floating image.
265 */
Adrian Roosfeafa052016-06-01 17:09:45 -0700266 @RemotableViewMethod
Adrian Roosc1a80b02016-04-05 14:54:55 -0700267 public void setNumIndentLines(int numberLines) {
268 mIndentLines = numberLines;
269 }
270
Adrian Roosfeafa052016-06-01 17:09:45 -0700271 /**
272 * Set id of the child that's also visible in the contracted layout.
273 */
274 @RemotableViewMethod
275 public void setContractedChildId(int contractedChildId) {
276 mContractedChildId = contractedChildId;
277 }
278
279 /**
280 * Get id of the child that's also visible in the contracted layout.
281 */
282 public int getContractedChildId() {
283 return mContractedChildId;
284 }
285
Adrian Roosc1a80b02016-04-05 14:54:55 -0700286 public static class LayoutParams extends MarginLayoutParams {
287
288 boolean hide = false;
289
290 public LayoutParams(Context c, AttributeSet attrs) {
291 super(c, attrs);
292 }
293
294 public LayoutParams(int width, int height) {
295 super(width, height);
296 }
297 }
298}