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 com.android.internal.R; 20 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.content.res.TypedArray; 24 import android.graphics.Canvas; 25 import android.util.AttributeSet; 26 import android.view.RemotableViewMethod; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import 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 37 public 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 51 /** 52 * Id of the child that's also visible in the contracted layout. 53 */ 54 private int mContractedChildId; 55 56 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 263 /** 264 * Sets how many lines should be indented to avoid a floating image. 265 */ 266 @RemotableViewMethod 267 public void setNumIndentLines(int numberLines) { 268 mIndentLines = numberLines; 269 } 270 271 /** 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 286 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 } 299