1 /* 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 17 package com.android.internal.widget; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.graphics.Canvas; 23 import android.util.AttributeSet; 24 import android.view.RemotableViewMethod; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.RemoteViews; 28 29 import com.android.internal.R; 30 31 /** 32 * A custom-built layout for the Notification.MessagingStyle. 33 * 34 * Evicts children until they all fit. 35 */ 36 @RemoteViews.RemoteView 37 public class MessagingLinearLayout extends ViewGroup { 38 39 private static final int NOT_MEASURED_BEFORE = -1; 40 /** 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 52 /** 53 * Id of the child that's also visible in the contracted layout. 54 */ 55 private int mContractedChildId; 56 /** 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; 61 62 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) { 73 case R.styleable.MessagingLinearLayout_spacing: 74 mSpacing = a.getDimensionPixelSize(i, 0); 75 break; 76 } 77 } 78 79 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. 87 int targetHeight = MeasureSpec.getSize(heightMeasureSpec); 88 switch (MeasureSpec.getMode(heightMeasureSpec)) { 89 case MeasureSpec.UNSPECIFIED: 90 targetHeight = Integer.MAX_VALUE; 91 break; 92 } 93 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 94 boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE 95 || getMeasuredHeight() != targetHeight 96 || mLastMeasuredWidth != widthSize; 97 98 final int count = getChildCount(); 99 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; 108 } 109 110 int totalHeight = mPaddingTop + mPaddingBottom; 111 boolean first = true; 112 113 // Starting from the bottom: we measure every view as if it were the only one. If it still 114 115 // 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(); 122 ImageFloatingTextView textChild = null; 123 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). 127 textChild = (ImageFloatingTextView) child; 128 textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines); 129 } 130 131 int spacing = first ? 0 : mSpacing; 132 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight 133 - mPaddingTop - mPaddingBottom + spacing); 134 135 final int childHeight = child.getMeasuredHeight(); 136 int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin + 137 lp.bottomMargin + spacing); 138 first = false; 139 boolean measuredTooSmall = false; 140 if (textChild != null) { 141 measuredTooSmall = childHeight < textChild.getLayoutHeight() 142 + textChild.getPaddingTop() + textChild.getPaddingBottom(); 143 } 144 145 if (newHeight <= targetHeight && !measuredTooSmall) { 146 totalHeight = newHeight; 147 lp.hide = false; 148 } else { 149 break; 150 } 151 } 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; 157 // Need to redo the height because it may change due to changing indents. 158 int totalHeight = mPaddingTop + mPaddingBottom; 159 boolean first = true; 160 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)); 177 if (changed || !recalculateVisibility) { 178 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);; 187 } 188 imageLines -= textChild.getLineCount(); 189 } 190 191 measuredWidth = Math.max(measuredWidth, 192 child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin 193 + mPaddingLeft + mPaddingRight); 194 totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() + 195 lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing)); 196 first = false; 197 } 198 199 200 setMeasuredDimension( 201 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), 202 widthMeasureSpec), 203 resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight), 204 heightMeasureSpec)); 205 mLastMeasuredWidth = widthSize; 206 } 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 } 254 mLastMeasuredWidth = NOT_MEASURED_BEFORE; 255 } 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 286 /** 287 * Sets how many lines should be indented to avoid a floating image. 288 */ 289 @RemotableViewMethod 290 public void setNumIndentLines(int numberLines) { 291 mIndentLines = numberLines; 292 } 293 294 /** 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 309 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 } 322