blob: 7a7447a6543f228b8cf4044dcc96b7d18a77a9b2 [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
Selim Cineke62255c2017-09-28 18:23:23 -070045 private int mMaxDisplayedLines = Integer.MAX_VALUE;
Adrian Roosc1a80b02016-04-05 14:54:55 -070046
Adrian Roosfeafa052016-06-01 17:09:45 -070047 /**
48 * Id of the child that's also visible in the contracted layout.
49 */
50 private int mContractedChildId;
Selim Cinek64aed1a2017-02-08 14:10:03 -080051 /**
52 * The last measured with in a layout pass if it was measured before or
53 * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
54 */
55 private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
Adrian Roosfeafa052016-06-01 17:09:45 -070056
Adrian Roosc1a80b02016-04-05 14:54:55 -070057 public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
58 super(context, attrs);
59
60 final TypedArray a = context.obtainStyledAttributes(attrs,
61 R.styleable.MessagingLinearLayout, 0,
62 0);
63
64 final int N = a.getIndexCount();
65 for (int i = 0; i < N; i++) {
66 int attr = a.getIndex(i);
67 switch (attr) {
Adrian Roosc1a80b02016-04-05 14:54:55 -070068 case R.styleable.MessagingLinearLayout_spacing:
69 mSpacing = a.getDimensionPixelSize(i, 0);
70 break;
71 }
72 }
73
Adrian Roosc1a80b02016-04-05 14:54:55 -070074 a.recycle();
75 }
76
Adrian Roosc1a80b02016-04-05 14:54:55 -070077 @Override
78 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
79 // This is essentially a bottom-up linear layout that only adds children that fit entirely
80 // up to a maximum height.
Selim Cinek64aed1a2017-02-08 14:10:03 -080081 int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
Adrian Roosc1a80b02016-04-05 14:54:55 -070082 switch (MeasureSpec.getMode(heightMeasureSpec)) {
Adrian Roosc1a80b02016-04-05 14:54:55 -070083 case MeasureSpec.UNSPECIFIED:
Selim Cinek64aed1a2017-02-08 14:10:03 -080084 targetHeight = Integer.MAX_VALUE;
Adrian Roosc1a80b02016-04-05 14:54:55 -070085 break;
86 }
Selim Cinek64aed1a2017-02-08 14:10:03 -080087 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
88 boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
89 || getMeasuredHeight() != targetHeight
90 || mLastMeasuredWidth != widthSize;
91
Selim Cinek88188f22017-09-19 16:46:56 -070092 // Now that we know which views to take, fix up the indents and see what width we get.
93 int measuredWidth = mPaddingLeft + mPaddingRight;
Adrian Roosc1a80b02016-04-05 14:54:55 -070094 final int count = getChildCount();
Selim Cinek88188f22017-09-19 16:46:56 -070095 int totalHeight = getMeasuredHeight();
Selim Cinek64aed1a2017-02-08 14:10:03 -080096 if (recalculateVisibility) {
97 // We only need to recalculate the view visibilities if the view wasn't measured already
98 // in this pass, otherwise we may drop messages here already since we are measured
99 // exactly with what we returned before, which was optimized already with the
100 // line-indents.
101 for (int i = 0; i < count; ++i) {
102 final View child = getChildAt(i);
103 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
104 lp.hide = true;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700105 }
106
Selim Cinek88188f22017-09-19 16:46:56 -0700107 totalHeight = mPaddingTop + mPaddingBottom;
Selim Cinek64aed1a2017-02-08 14:10:03 -0800108 boolean first = true;
Selim Cineke62255c2017-09-28 18:23:23 -0700109 int linesRemaining = mMaxDisplayedLines;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700110
Selim Cinek64aed1a2017-02-08 14:10:03 -0800111 // 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 -0700112
Selim Cinek64aed1a2017-02-08 14:10:03 -0800113 // fits, we take it, otherwise we stop there.
114 for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
115 if (getChildAt(i).getVisibility() == GONE) {
116 continue;
117 }
118 final View child = getChildAt(i);
119 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
Selim Cineke62255c2017-09-28 18:23:23 -0700120 MessagingChild messagingChild = null;
121 if (child instanceof MessagingChild) {
122 messagingChild = (MessagingChild) child;
123 messagingChild.setMaxDisplayedLines(linesRemaining);
124 }
Selim Cinek9281e5e2017-03-08 17:29:55 -0800125 int spacing = first ? 0 : mSpacing;
126 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
127 - mPaddingTop - mPaddingBottom + spacing);
Selim Cinek64aed1a2017-02-08 14:10:03 -0800128
129 final int childHeight = child.getMeasuredHeight();
130 int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
Selim Cinek9281e5e2017-03-08 17:29:55 -0800131 lp.bottomMargin + spacing);
Selim Cinek64aed1a2017-02-08 14:10:03 -0800132 first = false;
Selim Cinek88188f22017-09-19 16:46:56 -0700133 int measureType = MessagingChild.MEASURED_NORMAL;
Selim Cineke62255c2017-09-28 18:23:23 -0700134 if (messagingChild != null) {
135 measureType = messagingChild.getMeasuredType();
Selim Cinek88188f22017-09-19 16:46:56 -0700136 linesRemaining -= messagingChild.getConsumedLines();
Selim Cinek9281e5e2017-03-08 17:29:55 -0800137 }
Selim Cinek88188f22017-09-19 16:46:56 -0700138 boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
139 boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
140 if (newHeight <= targetHeight && !isTooSmall) {
Selim Cinek64aed1a2017-02-08 14:10:03 -0800141 totalHeight = newHeight;
Selim Cinek88188f22017-09-19 16:46:56 -0700142 measuredWidth = Math.max(measuredWidth,
143 child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
144 + mPaddingLeft + mPaddingRight);
Selim Cinek64aed1a2017-02-08 14:10:03 -0800145 lp.hide = false;
Selim Cineke62255c2017-09-28 18:23:23 -0700146 if (isShortened || linesRemaining <= 0) {
Selim Cinek88188f22017-09-19 16:46:56 -0700147 break;
148 }
Selim Cinek64aed1a2017-02-08 14:10:03 -0800149 } else {
150 break;
151 }
Adrian Roosc1a80b02016-04-05 14:54:55 -0700152 }
153 }
154
Adrian Roosc1a80b02016-04-05 14:54:55 -0700155 setMeasuredDimension(
156 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
157 widthMeasureSpec),
158 resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
159 heightMeasureSpec));
Selim Cinek64aed1a2017-02-08 14:10:03 -0800160 mLastMeasuredWidth = widthSize;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700161 }
162
163 @Override
164 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
165 final int paddingLeft = mPaddingLeft;
166
167 int childTop;
168
169 // Where right end of child should go
170 final int width = right - left;
171 final int childRight = width - mPaddingRight;
172
173 final int layoutDirection = getLayoutDirection();
174 final int count = getChildCount();
175
176 childTop = mPaddingTop;
177
178 boolean first = true;
Selim Cinekd3809992017-10-27 16:09:20 -0700179 final boolean shown = isShown();
Adrian Roosc1a80b02016-04-05 14:54:55 -0700180 for (int i = 0; i < count; i++) {
181 final View child = getChildAt(i);
Selim Cinekd3809992017-10-27 16:09:20 -0700182 if (child.getVisibility() == GONE) {
Adrian Roosc1a80b02016-04-05 14:54:55 -0700183 continue;
184 }
Selim Cinekd3809992017-10-27 16:09:20 -0700185 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
186 MessagingChild messagingChild = (MessagingChild) child;
187 if (lp.hide) {
188 if (shown && lp.visibleBefore) {
189 messagingChild.hideAnimated();
190 }
191 lp.visibleBefore = false;
192 continue;
193 } else {
194 lp.visibleBefore = true;
195 }
Adrian Roosc1a80b02016-04-05 14:54:55 -0700196
197 final int childWidth = child.getMeasuredWidth();
198 final int childHeight = child.getMeasuredHeight();
199
200 int childLeft;
201 if (layoutDirection == LAYOUT_DIRECTION_RTL) {
202 childLeft = childRight - childWidth - lp.rightMargin;
203 } else {
204 childLeft = paddingLeft + lp.leftMargin;
205 }
206
207 if (!first) {
208 childTop += mSpacing;
209 }
210
211 childTop += lp.topMargin;
212 child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
213
214 childTop += childHeight + lp.bottomMargin;
215
216 first = false;
217 }
Selim Cinek64aed1a2017-02-08 14:10:03 -0800218 mLastMeasuredWidth = NOT_MEASURED_BEFORE;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700219 }
220
221 @Override
222 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
223 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
224 if (lp.hide) {
Selim Cinekd3809992017-10-27 16:09:20 -0700225 MessagingChild messagingChild = (MessagingChild) child;
226 if (!messagingChild.isHidingAnimated()) {
227 return true;
228 }
Adrian Roosc1a80b02016-04-05 14:54:55 -0700229 }
230 return super.drawChild(canvas, child, drawingTime);
231 }
232
233 @Override
234 public LayoutParams generateLayoutParams(AttributeSet attrs) {
235 return new LayoutParams(mContext, attrs);
236 }
237
238 @Override
239 protected LayoutParams generateDefaultLayoutParams() {
240 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
241
242 }
243
244 @Override
245 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
246 LayoutParams copy = new LayoutParams(lp.width, lp.height);
247 if (lp instanceof MarginLayoutParams) {
248 copy.copyMarginsFrom((MarginLayoutParams) lp);
249 }
250 return copy;
251 }
252
Adrian Roosc1a80b02016-04-05 14:54:55 -0700253 /**
Selim Cineke62255c2017-09-28 18:23:23 -0700254 * Sets how many lines should be displayed at most
Adrian Roosc1a80b02016-04-05 14:54:55 -0700255 */
Adrian Roosfeafa052016-06-01 17:09:45 -0700256 @RemotableViewMethod
Selim Cineke62255c2017-09-28 18:23:23 -0700257 public void setMaxDisplayedLines(int numberLines) {
258 mMaxDisplayedLines = numberLines;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700259 }
260
Selim Cinek88188f22017-09-19 16:46:56 -0700261 public interface MessagingChild {
262 int MEASURED_NORMAL = 0;
263 int MEASURED_SHORTENED = 1;
264 int MEASURED_TOO_SMALL = 2;
Selim Cineke62255c2017-09-28 18:23:23 -0700265
Selim Cinek88188f22017-09-19 16:46:56 -0700266 int getMeasuredType();
267 int getConsumedLines();
Selim Cineke62255c2017-09-28 18:23:23 -0700268 void setMaxDisplayedLines(int lines);
Selim Cinekd3809992017-10-27 16:09:20 -0700269 void hideAnimated();
270 boolean isHidingAnimated();
Adrian Roosfeafa052016-06-01 17:09:45 -0700271 }
272
Adrian Roosc1a80b02016-04-05 14:54:55 -0700273 public static class LayoutParams extends MarginLayoutParams {
274
Selim Cinekd3809992017-10-27 16:09:20 -0700275 public boolean hide = false;
276 public boolean visibleBefore = false;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700277
278 public LayoutParams(Context c, AttributeSet attrs) {
279 super(c, attrs);
280 }
281
282 public LayoutParams(int width, int height) {
283 super(width, height);
284 }
285 }
286}