blob: f0ef3707661819c3c20a55c7bbda2e7ced396c74 [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
39 /**
40 * Spacing to be applied between views.
41 */
42 private int mSpacing;
43
Selim Cineke62255c2017-09-28 18:23:23 -070044 private int mMaxDisplayedLines = Integer.MAX_VALUE;
Adrian Roosc1a80b02016-04-05 14:54:55 -070045
Selim Cinek1d6b50e2017-10-27 16:10:57 -070046 private MessagingLayout mMessagingLayout;
Adrian Roosfeafa052016-06-01 17:09:45 -070047
Adrian Roosc1a80b02016-04-05 14:54:55 -070048 public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
49 super(context, attrs);
50
51 final TypedArray a = context.obtainStyledAttributes(attrs,
52 R.styleable.MessagingLinearLayout, 0,
53 0);
54
55 final int N = a.getIndexCount();
56 for (int i = 0; i < N; i++) {
57 int attr = a.getIndex(i);
58 switch (attr) {
Adrian Roosc1a80b02016-04-05 14:54:55 -070059 case R.styleable.MessagingLinearLayout_spacing:
60 mSpacing = a.getDimensionPixelSize(i, 0);
61 break;
62 }
63 }
64
Adrian Roosc1a80b02016-04-05 14:54:55 -070065 a.recycle();
66 }
67
Adrian Roosc1a80b02016-04-05 14:54:55 -070068 @Override
69 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
70 // This is essentially a bottom-up linear layout that only adds children that fit entirely
71 // up to a maximum height.
Selim Cinek64aed1a2017-02-08 14:10:03 -080072 int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
Adrian Roosc1a80b02016-04-05 14:54:55 -070073 switch (MeasureSpec.getMode(heightMeasureSpec)) {
Adrian Roosc1a80b02016-04-05 14:54:55 -070074 case MeasureSpec.UNSPECIFIED:
Selim Cinek64aed1a2017-02-08 14:10:03 -080075 targetHeight = Integer.MAX_VALUE;
Adrian Roosc1a80b02016-04-05 14:54:55 -070076 break;
77 }
Selim Cinek64aed1a2017-02-08 14:10:03 -080078 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
Selim Cinek64aed1a2017-02-08 14:10:03 -080079
Selim Cinek88188f22017-09-19 16:46:56 -070080 // Now that we know which views to take, fix up the indents and see what width we get.
81 int measuredWidth = mPaddingLeft + mPaddingRight;
Adrian Roosc1a80b02016-04-05 14:54:55 -070082 final int count = getChildCount();
Selim Cinekdae97c92017-10-23 16:02:18 -070083 int totalHeight;
84 for (int i = 0; i < count; ++i) {
85 final View child = getChildAt(i);
86 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
87 lp.hide = true;
88 }
89
90 totalHeight = mPaddingTop + mPaddingBottom;
91 boolean first = true;
92 int linesRemaining = mMaxDisplayedLines;
93
94 // Starting from the bottom: we measure every view as if it were the only one. If it still
95 // fits, we take it, otherwise we stop there.
96 for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
97 if (getChildAt(i).getVisibility() == GONE) {
98 continue;
Adrian Roosc1a80b02016-04-05 14:54:55 -070099 }
Selim Cinekdae97c92017-10-23 16:02:18 -0700100 final View child = getChildAt(i);
101 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
102 MessagingChild messagingChild = null;
103 if (child instanceof MessagingChild) {
104 messagingChild = (MessagingChild) child;
105 messagingChild.setMaxDisplayedLines(linesRemaining);
106 }
107 int spacing = first ? 0 : mSpacing;
108 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
109 - mPaddingTop - mPaddingBottom + spacing);
Adrian Roosc1a80b02016-04-05 14:54:55 -0700110
Selim Cinekdae97c92017-10-23 16:02:18 -0700111 final int childHeight = child.getMeasuredHeight();
112 int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
113 lp.bottomMargin + spacing);
114 first = false;
115 int measureType = MessagingChild.MEASURED_NORMAL;
116 if (messagingChild != null) {
117 measureType = messagingChild.getMeasuredType();
118 linesRemaining -= messagingChild.getConsumedLines();
119 }
120 boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
121 boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
122 if (newHeight <= targetHeight && !isTooSmall) {
123 totalHeight = newHeight;
124 measuredWidth = Math.max(measuredWidth,
125 child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
126 + mPaddingLeft + mPaddingRight);
127 lp.hide = false;
128 if (isShortened || linesRemaining <= 0) {
Selim Cinek64aed1a2017-02-08 14:10:03 -0800129 break;
130 }
Selim Cinekdae97c92017-10-23 16:02:18 -0700131 } else {
132 break;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700133 }
134 }
135
Adrian Roosc1a80b02016-04-05 14:54:55 -0700136 setMeasuredDimension(
137 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
138 widthMeasureSpec),
Selim Cinekdae97c92017-10-23 16:02:18 -0700139 Math.max(getSuggestedMinimumHeight(), totalHeight));
Adrian Roosc1a80b02016-04-05 14:54:55 -0700140 }
141
142 @Override
143 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
144 final int paddingLeft = mPaddingLeft;
145
146 int childTop;
147
148 // Where right end of child should go
149 final int width = right - left;
150 final int childRight = width - mPaddingRight;
151
152 final int layoutDirection = getLayoutDirection();
153 final int count = getChildCount();
154
155 childTop = mPaddingTop;
156
157 boolean first = true;
Selim Cinekd3809992017-10-27 16:09:20 -0700158 final boolean shown = isShown();
Adrian Roosc1a80b02016-04-05 14:54:55 -0700159 for (int i = 0; i < count; i++) {
160 final View child = getChildAt(i);
Selim Cinekd3809992017-10-27 16:09:20 -0700161 if (child.getVisibility() == GONE) {
Adrian Roosc1a80b02016-04-05 14:54:55 -0700162 continue;
163 }
Selim Cinekd3809992017-10-27 16:09:20 -0700164 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
165 MessagingChild messagingChild = (MessagingChild) child;
166 if (lp.hide) {
167 if (shown && lp.visibleBefore) {
168 messagingChild.hideAnimated();
169 }
170 lp.visibleBefore = false;
171 continue;
172 } else {
173 lp.visibleBefore = true;
174 }
Adrian Roosc1a80b02016-04-05 14:54:55 -0700175
176 final int childWidth = child.getMeasuredWidth();
177 final int childHeight = child.getMeasuredHeight();
178
179 int childLeft;
180 if (layoutDirection == LAYOUT_DIRECTION_RTL) {
181 childLeft = childRight - childWidth - lp.rightMargin;
182 } else {
183 childLeft = paddingLeft + lp.leftMargin;
184 }
185
186 if (!first) {
187 childTop += mSpacing;
188 }
189
190 childTop += lp.topMargin;
191 child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
192
193 childTop += childHeight + lp.bottomMargin;
194
195 first = false;
196 }
197 }
198
199 @Override
200 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
201 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
202 if (lp.hide) {
Selim Cinekd3809992017-10-27 16:09:20 -0700203 MessagingChild messagingChild = (MessagingChild) child;
204 if (!messagingChild.isHidingAnimated()) {
205 return true;
206 }
Adrian Roosc1a80b02016-04-05 14:54:55 -0700207 }
208 return super.drawChild(canvas, child, drawingTime);
209 }
210
211 @Override
212 public LayoutParams generateLayoutParams(AttributeSet attrs) {
213 return new LayoutParams(mContext, attrs);
214 }
215
216 @Override
217 protected LayoutParams generateDefaultLayoutParams() {
218 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
219
220 }
221
222 @Override
223 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
224 LayoutParams copy = new LayoutParams(lp.width, lp.height);
225 if (lp instanceof MarginLayoutParams) {
226 copy.copyMarginsFrom((MarginLayoutParams) lp);
227 }
228 return copy;
229 }
230
Adrian Roosc1a80b02016-04-05 14:54:55 -0700231 /**
Selim Cineke62255c2017-09-28 18:23:23 -0700232 * Sets how many lines should be displayed at most
Adrian Roosc1a80b02016-04-05 14:54:55 -0700233 */
Adrian Roosfeafa052016-06-01 17:09:45 -0700234 @RemotableViewMethod
Selim Cineke62255c2017-09-28 18:23:23 -0700235 public void setMaxDisplayedLines(int numberLines) {
236 mMaxDisplayedLines = numberLines;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700237 }
238
Selim Cinek1d6b50e2017-10-27 16:10:57 -0700239 public void setMessagingLayout(MessagingLayout layout) {
240 mMessagingLayout = layout;
241 }
242
243 public MessagingLayout getMessagingLayout() {
244 return mMessagingLayout;
245 }
246
Selim Cinek88188f22017-09-19 16:46:56 -0700247 public interface MessagingChild {
248 int MEASURED_NORMAL = 0;
249 int MEASURED_SHORTENED = 1;
250 int MEASURED_TOO_SMALL = 2;
Selim Cineke62255c2017-09-28 18:23:23 -0700251
Selim Cinek88188f22017-09-19 16:46:56 -0700252 int getMeasuredType();
253 int getConsumedLines();
Selim Cineke62255c2017-09-28 18:23:23 -0700254 void setMaxDisplayedLines(int lines);
Selim Cinekd3809992017-10-27 16:09:20 -0700255 void hideAnimated();
256 boolean isHidingAnimated();
Adrian Roosfeafa052016-06-01 17:09:45 -0700257 }
258
Adrian Roosc1a80b02016-04-05 14:54:55 -0700259 public static class LayoutParams extends MarginLayoutParams {
260
Selim Cinekd3809992017-10-27 16:09:20 -0700261 public boolean hide = false;
262 public boolean visibleBefore = false;
Adrian Roosc1a80b02016-04-05 14:54:55 -0700263
264 public LayoutParams(Context c, AttributeSet attrs) {
265 super(c, attrs);
266 }
267
268 public LayoutParams(int width, int height) {
269 super(width, height);
270 }
271 }
272}