diff options
| author | 2016-12-05 15:42:16 -0800 | |
|---|---|---|
| committer | 2016-12-07 15:16:19 -0800 | |
| commit | 4ac8f40d96196a2a36d7bda7d92eee9bbd4ee7e7 (patch) | |
| tree | 03833b1a6b867e839b6b51d091e24a08e82b314b | |
| parent | e8ac4110c8ba22dfa01e9941c296d8b32781c500 (diff) | |
MessagingStyle: Fix buggy measure in MessagingLinearLayout
Fixes a bug in MessagingLinearLayout where we would not
recompute the height of it after applying the computed
insets to the text views, leading to incorrect padding
and in rare cases a cut off message at the bottom.
Change-Id: If87a527555158e94e501832e9f49e380cc7da2bb
Test: runtest -x core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
Fixes: 31463075
3 files changed, 256 insertions, 1 deletions
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index d2a43b755f18..cb123a13ad1d 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -126,7 +126,8 @@ public class MessagingLinearLayout extends ViewGroup { // Pretend we need the image padding for all views, we don't know which // one will end up needing to do this (might end up not using all the space, // but calculating this exactly would be more expensive). - ((ImageFloatingTextView) child).setNumIndentLines(mIndentLines); + ((ImageFloatingTextView) child).setNumIndentLines( + mIndentLines == 2 ? 3 : mIndentLines); } measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); @@ -147,6 +148,9 @@ public class MessagingLinearLayout extends ViewGroup { // Now that we know which views to take, fix up the indents and see what width we get. int measuredWidth = mPaddingLeft + mPaddingRight; int imageLines = mIndentLines; + // Need to redo the height because it may change due to changing indents. + totalHeight = mPaddingTop + mPaddingBottom; + first = true; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); @@ -173,6 +177,9 @@ public class MessagingLinearLayout extends ViewGroup { measuredWidth = Math.max(measuredWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + mPaddingLeft + mPaddingRight); + totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() + + lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing)); + first = false; } diff --git a/core/tests/coretests/res/layout/messaging_linear_layout_test.xml b/core/tests/coretests/res/layout/messaging_linear_layout_test.xml new file mode 100644 index 000000000000..8ba3e07da22e --- /dev/null +++ b/core/tests/coretests/res/layout/messaging_linear_layout_test.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2016 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<com.android.internal.widget.MessagingLinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:maxHeight="300px" + android:spacing="5px"> + +</com.android.internal.widget.MessagingLinearLayout>
\ No newline at end of file diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java new file mode 100644 index 000000000000..75b2c1d5fd2c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.os.Debug; +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.core.deps.guava.base.Function; +import android.support.test.filters.SmallTest; +import android.view.LayoutInflater; +import android.view.View.MeasureSpec; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; + +@SmallTest +public class MessagingLinearLayoutTest { + + public static final int WIDTH_SPEC = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY); + public static final int HEIGHT_SPEC = MeasureSpec.makeMeasureSpec(400, MeasureSpec.AT_MOST); + private Context mContext; + private MessagingLinearLayout mView; + + @Before + public void setup() { + mContext = InstrumentationRegistry.getTargetContext(); + // maxHeight: 300px + // spacing: 50px + mView = (MessagingLinearLayout) LayoutInflater.from(mContext).inflate( + R.layout.messaging_linear_layout_test, null); + } + + @Test + public void testSingleChild() { + FakeImageFloatingTextView child = fakeChild((i) -> 3); + + mView.setNumIndentLines(2); + mView.addView(child); + + mView.measure(WIDTH_SPEC, HEIGHT_SPEC); + mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); + + assertEquals(3, child.getNumIndentLines()); + assertFalse(child.isHidden()); + assertEquals(150, mView.getMeasuredHeight()); + } + + @Test + public void testLargeSmall() { + FakeImageFloatingTextView child1 = fakeChild((i) -> 3); + FakeImageFloatingTextView child2 = fakeChild((i) -> 1); + + mView.setNumIndentLines(2); + mView.addView(child1); + mView.addView(child2); + + mView.measure(WIDTH_SPEC, HEIGHT_SPEC); + mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); + + assertEquals(3, child1.getNumIndentLines()); + assertEquals(0, child2.getNumIndentLines()); + assertFalse(child1.isHidden()); + assertFalse(child2.isHidden()); + assertEquals(205, mView.getMeasuredHeight()); + } + + @Test + public void testSmallSmall() { + FakeImageFloatingTextView child1 = fakeChild((i) -> 1); + FakeImageFloatingTextView child2 = fakeChild((i) -> 1); + + mView.setNumIndentLines(2); + mView.addView(child1); + mView.addView(child2); + + mView.measure(WIDTH_SPEC, HEIGHT_SPEC); + mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); + + assertEquals(2, child1.getNumIndentLines()); + assertEquals(1, child2.getNumIndentLines()); + assertFalse(child1.isHidden()); + assertFalse(child2.isHidden()); + assertEquals(105, mView.getMeasuredHeight()); + } + + @Test + public void testLargeLarge() { + FakeImageFloatingTextView child1 = fakeChild((i) -> 7); + FakeImageFloatingTextView child2 = fakeChild((i) -> 7); + + mView.setNumIndentLines(2); + mView.addView(child1); + mView.addView(child2); + + mView.measure(WIDTH_SPEC, HEIGHT_SPEC); + mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); + + assertEquals(3, child2.getNumIndentLines()); + assertTrue(child1.isHidden()); + assertFalse(child2.isHidden()); + assertEquals(300, mView.getMeasuredHeight()); + } + + @Test + public void testLargeSmall_largeWrapsWith3indentbutnot3_andHitsMax() { + FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 5 : 4); + FakeImageFloatingTextView child2 = fakeChild((i) -> 1); + + mView.setNumIndentLines(2); + mView.addView(child1); + mView.addView(child2); + + mView.measure(WIDTH_SPEC, HEIGHT_SPEC); + mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); + + assertTrue(child1.isHidden()); + assertFalse(child2.isHidden()); + assertEquals(50, mView.getMeasuredHeight()); + assertEquals(2, child2.getNumIndentLines()); + } + + @Test + public void testLargeSmall_largeWrapsWith3indentbutnot3() { + FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 4 : 3); + FakeImageFloatingTextView child2 = fakeChild((i) -> 1); + + mView.setNumIndentLines(2); + mView.addView(child1); + mView.addView(child2); + + mView.measure(WIDTH_SPEC, HEIGHT_SPEC); + mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); + + assertFalse(child1.isHidden()); + assertFalse(child2.isHidden()); + assertEquals(255, mView.getMeasuredHeight()); + assertEquals(3, child1.getNumIndentLines()); + assertEquals(0, child2.getNumIndentLines()); + } + + private class FakeImageFloatingTextView extends ImageFloatingTextView { + + public static final int LINE_HEIGHT = 50; + private final Function<Integer, Integer> mLinesForIndent; + private int mNumIndentLines; + + public FakeImageFloatingTextView(Context context, + Function<Integer, Integer> linesForIndent) { + super(context, null, 0, 0); + mLinesForIndent = linesForIndent; + } + + @Override + public boolean setNumIndentLines(int lines) { + boolean changed = (mNumIndentLines != lines); + mNumIndentLines = lines; + return changed; + } + + public int getNumIndentLines() { + return mNumIndentLines; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension( + getDefaultSize(500, widthMeasureSpec), + resolveSize(getDesiredHeight(), heightMeasureSpec)); + } + + @Override + public int getLineCount() { + return mLinesForIndent.apply(mNumIndentLines); + } + + public int getDesiredHeight() { + return LINE_HEIGHT * getLineCount(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + // swallow + } + + public boolean isHidden() { + MessagingLinearLayout.LayoutParams lp = + (MessagingLinearLayout.LayoutParams) getLayoutParams(); + try { + Field hide = MessagingLinearLayout.LayoutParams.class.getDeclaredField("hide"); + hide.setAccessible(true); + return hide.getBoolean(lp); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } + + private FakeImageFloatingTextView fakeChild(Function<Integer,Integer> linesForIndent) { + return new FakeImageFloatingTextView(mContext, linesForIndent); + } +} |