From e2d92acee548ede31aae1aa2061299d285957763 Mon Sep 17 00:00:00 2001 From: Aaron Heuckroth Date: Wed, 1 May 2019 10:44:59 -0400 Subject: [PATCH] Add GlobalActionsColumnLayout to replace HardwareUILayout. Refactor code to improve code re-use and enable testing, and add lots of unit tests. HardwareUILayout is no longer used. Test: Automated tests pass. Manual testing with different display sizes and with panel plugin enabled/disabled. Fixes: 130808177 Fixes: 128372852 Change-Id: I1e48d226973a9b610cece2691af7b233cdb5235c --- .../res/layout-land/global_actions_column.xml | 68 ++++ .../global_actions_column_seascape.xml | 68 ++++ .../res/layout/global_actions_column.xml | 70 ++++ .../android/systemui/HardwareUiLayout.java | 10 - .../com/android/systemui/MultiListLayout.java | 24 +- .../GlobalActionsColumnLayout.java | 195 +++++++++++ .../globalactions/GlobalActionsDialog.java | 17 +- .../GlobalActionsGridLayout.java | 106 ++---- .../globalactions/GlobalActionsLayout.java | 146 +++++++++ .../GlobalActionsColumnLayoutTest.java | 201 ++++++++++++ .../GlobalActionsGridLayoutTest.java | 167 +--------- .../GlobalActionsLayoutTest.java | 303 ++++++++++++++++++ 12 files changed, 1115 insertions(+), 260 deletions(-) create mode 100644 packages/SystemUI/res/layout-land/global_actions_column.xml create mode 100644 packages/SystemUI/res/layout-land/global_actions_column_seascape.xml create mode 100644 packages/SystemUI/res/layout/global_actions_column.xml create mode 100644 packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java create mode 100644 packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsColumnLayoutTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java diff --git a/packages/SystemUI/res/layout-land/global_actions_column.xml b/packages/SystemUI/res/layout-land/global_actions_column.xml new file mode 100644 index 0000000000000..99a4e13d20648 --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_column.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + diff --git a/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml new file mode 100644 index 0000000000000..0f86131949592 --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + diff --git a/packages/SystemUI/res/layout/global_actions_column.xml b/packages/SystemUI/res/layout/global_actions_column.xml new file mode 100644 index 0000000000000..b58146b6ce418 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_column.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java index 802903db7ba8f..ad2e0024065f7 100644 --- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java @@ -79,16 +79,6 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable { return findViewById(android.R.id.list); } - @Override - public void removeAllItems() { - if (mList != null) { - mList.removeAllViews(); - } - if (mSeparatedView != null) { - mSeparatedView.removeAllViews(); - } - } - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java index f8287a4dc2dc8..d153fb0398020 100644 --- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -44,11 +44,6 @@ public abstract class MultiListLayout extends LinearLayout { protected abstract ViewGroup getListView(); - /** - * Removes all child items from the separated and list views, if they exist. - */ - protected abstract void removeAllItems(); - /** * Sets the divided view, which may have a differently-colored background. */ @@ -110,6 +105,25 @@ public abstract class MultiListLayout extends LinearLayout { onUpdateList(); } + protected void removeAllSeparatedViews() { + ViewGroup separated = getSeparatedView(); + if (separated != null) { + separated.removeAllViews(); + } + } + + protected void removeAllListViews() { + ViewGroup list = getListView(); + if (list != null) { + list.removeAllViews(); + } + } + + protected void removeAllItems() { + removeAllListViews(); + removeAllSeparatedViews(); + } + protected void onUpdateList() { removeAllItems(); setSeparatedViewVisibility(mAdapter.hasSeparatedItems()); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java new file mode 100644 index 0000000000000..59070287cc5cb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2019 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.systemui.globalactions; + +import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; + +/** + * Grid-based implementation of the button layout created by the global actions dialog. + */ +public class GlobalActionsColumnLayout extends GlobalActionsLayout { + private boolean mLastSnap; + + public GlobalActionsColumnLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + post(() -> updateSnap()); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @VisibleForTesting + protected boolean shouldReverseListItems() { + int rotation = getCurrentRotation(); + if (rotation == ROTATION_NONE) { + return false; + } + if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + return rotation == ROTATION_LANDSCAPE; + } + return rotation == ROTATION_SEASCAPE; + } + + @Override + public void onUpdateList() { + super.onUpdateList(); + updateChildOrdering(); + } + + private void updateChildOrdering() { + if (shouldReverseListItems()) { + getListView().bringToFront(); + } else { + getSeparatedView().bringToFront(); + } + } + + /** + * Snap this layout to align with the power button. + */ + @VisibleForTesting + protected void snapToPowerButton() { + int offset = getPowerButtonOffsetDistance(); + switch (getCurrentRotation()) { + case (ROTATION_LANDSCAPE): + setPadding(offset, 0, 0, 0); + setGravity(Gravity.LEFT | Gravity.TOP); + break; + case (ROTATION_SEASCAPE): + setPadding(0, 0, offset, 0); + setGravity(Gravity.RIGHT | Gravity.BOTTOM); + break; + default: + setPadding(0, offset, 0, 0); + setGravity(Gravity.TOP | Gravity.RIGHT); + break; + } + } + + /** + * Detach this layout from snapping to the power button and instead center along that edge. + */ + @VisibleForTesting + protected void centerAlongEdge() { + switch (getCurrentRotation()) { + case (ROTATION_LANDSCAPE): + setPadding(0, 0, 0, 0); + setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP); + break; + case (ROTATION_SEASCAPE): + setPadding(0, 0, 0, 0); + setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); + break; + default: + setPadding(0, 0, 0, 0); + setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); + break; + } + } + + /** + * Determines the distance from the top of the screen to the power button. + */ + @VisibleForTesting + protected int getPowerButtonOffsetDistance() { + return Math.round(getContext().getResources().getDimension( + R.dimen.global_actions_top_padding)); + } + + /** + * Check whether there is enough extra space below the dialog such that we can offset the top + * of the dialog from the top of the phone to line it up with the power button, then either + * snap the dialog to the power button or center it along the edge with snapToPowerButton. + */ + @VisibleForTesting + protected boolean shouldSnapToPowerButton() { + int offsetSize = getPowerButtonOffsetDistance(); + int dialogSize; + int screenSize; + View wrapper = getWrapper(); + int rotation = getCurrentRotation(); + if (rotation == ROTATION_NONE) { + dialogSize = wrapper.getMeasuredHeight(); + screenSize = getMeasuredHeight(); + } else { + dialogSize = wrapper.getMeasuredWidth(); + screenSize = getMeasuredWidth(); + } + return dialogSize + offsetSize < screenSize; + } + + @VisibleForTesting + protected void updateSnap() { + boolean snap = shouldSnapToPowerButton(); + if (snap != mLastSnap) { + if (snap) { + snapToPowerButton(); + } else { + centerAlongEdge(); + } + } + mLastSnap = snap; + } + + @VisibleForTesting + protected float getGridItemSize() { + return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height); + } + + @VisibleForTesting + protected float getAnimationDistance() { + return getGridItemSize() / 2; + } + + @Override + public float getAnimationOffsetX() { + if (getCurrentRotation() == ROTATION_NONE) { + return getAnimationDistance(); + } + return 0; + } + + @Override + public float getAnimationOffsetY() { + switch (getCurrentRotation()) { + case ROTATION_LANDSCAPE: + return -getAnimationDistance(); + case ROTATION_SEASCAPE: + return getAnimationDistance(); + default: // Portrait + return 0; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 9b69dc50a1a1b..2b006ced29cb8 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1582,13 +1582,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private int getGlobalActionsLayoutId(Context context) { - if (isForceGridEnabled(context) || shouldUsePanel()) { - if (RotationUtils.getRotation(context) == RotationUtils.ROTATION_SEASCAPE) { + boolean useGridLayout = isForceGridEnabled(context) || shouldUsePanel(); + if (RotationUtils.getRotation(context) == RotationUtils.ROTATION_SEASCAPE) { + if (useGridLayout) { return com.android.systemui.R.layout.global_actions_grid_seascape; + } else { + return com.android.systemui.R.layout.global_actions_column_seascape; + } + } else { + if (useGridLayout) { + return com.android.systemui.R.layout.global_actions_grid; + } else { + return com.android.systemui.R.layout.global_actions_column; } - return com.android.systemui.R.layout.global_actions_grid; } - return com.android.systemui.R.layout.global_actions_wrapped; } @Override @@ -1711,7 +1718,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } public void onRotate(int from, int to) { - if (mShowing && (shouldUsePanel() || isForceGridEnabled(mContext))) { + if (mShowing) { refreshDialog(); } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java index 554ed738c3354..e1462d15c8871 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -22,58 +22,23 @@ import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; import android.content.Context; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.HardwareBgDrawable; -import com.android.systemui.MultiListLayout; -import com.android.systemui.util.leak.RotationUtils; /** * Grid-based implementation of the button layout created by the global actions dialog. */ -public class GlobalActionsGridLayout extends MultiListLayout { - - boolean mBackgroundsSet; - +public class GlobalActionsGridLayout extends GlobalActionsLayout { public GlobalActionsGridLayout(Context context, AttributeSet attrs) { super(context, attrs); } - private void setBackgrounds() { - int gridBackgroundColor = getResources().getColor( - com.android.systemui.R.color.global_actions_grid_background, null); - int separatedBackgroundColor = getResources().getColor( - com.android.systemui.R.color.global_actions_separated_background, null); - HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext()); - HardwareBgDrawable separatedBackground = new HardwareBgDrawable(true, true, getContext()); - listBackground.setTint(gridBackgroundColor); - separatedBackground.setTint(separatedBackgroundColor); - getListView().setBackground(listBackground); - getSeparatedView().setBackground(separatedBackground); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // backgrounds set only once, the first time onMeasure is called after inflation - if (getListView() != null && !mBackgroundsSet) { - setBackgrounds(); - mBackgroundsSet = true; - } - } - @VisibleForTesting - protected int getCurrentRotation() { - return RotationUtils.getRotation(mContext); - } - - @VisibleForTesting - protected void setupListView(ListGridLayout listView, int itemCount) { - listView.setExpectedCount(itemCount); + protected void setupListView() { + ListGridLayout listView = getListView(); + listView.setExpectedCount(mAdapter.countListItems()); listView.setReverseSublists(shouldReverseSublists()); listView.setReverseItems(shouldReverseListItems()); listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns()); @@ -81,29 +46,8 @@ public class GlobalActionsGridLayout extends MultiListLayout { @Override public void onUpdateList() { + setupListView(); super.onUpdateList(); - - ViewGroup separatedView = getSeparatedView(); - ListGridLayout listView = getListView(); - setupListView(listView, mAdapter.countListItems()); - - for (int i = 0; i < mAdapter.getCount(); i++) { - // generate the view item - View v; - boolean separated = mAdapter.shouldBeSeparated(i); - if (separated) { - v = mAdapter.getView(i, null, separatedView); - } else { - v = mAdapter.getView(i, null, listView); - } - Log.d("GlobalActionsGridLayout", "View: " + v); - - if (separated) { - separatedView.addView(v); - } else { - listView.addItem(v); - } - } updateSeparatedItemSize(); } @@ -111,7 +55,8 @@ public class GlobalActionsGridLayout extends MultiListLayout { * If the separated view contains only one item, expand the bounds of that item to take up the * entire view, so that the whole thing is touch-able. */ - private void updateSeparatedItemSize() { + @VisibleForTesting + protected void updateSeparatedItemSize() { ViewGroup separated = getSeparatedView(); if (separated.getChildCount() == 0) { return; @@ -129,13 +74,24 @@ public class GlobalActionsGridLayout extends MultiListLayout { } @Override - protected ViewGroup getSeparatedView() { - return findViewById(com.android.systemui.R.id.separated_button); + protected ListGridLayout getListView() { + return (ListGridLayout) super.getListView(); } @Override - protected ListGridLayout getListView() { - return findViewById(android.R.id.list); + protected void removeAllListViews() { + ListGridLayout list = getListView(); + if (list != null) { + list.removeAllItems(); + } + } + + @Override + protected void addToListView(View v, boolean reverse) { + ListGridLayout list = getListView(); + if (list != null) { + list.addItem(v); + } } @Override @@ -174,12 +130,7 @@ public class GlobalActionsGridLayout extends MultiListLayout { return true; } - /** - * Determines whether the ListGridLayout should reverse the ordering of items within sublists. - * Used for RTL languages to ensure that items appear in the same positions, without having to - * override layoutDirection, which breaks Talkback ordering. - */ - @VisibleForTesting + @Override protected boolean shouldReverseListItems() { int rotation = getCurrentRotation(); boolean reverse = false; // should we add items to parents in the reverse order? @@ -187,20 +138,13 @@ public class GlobalActionsGridLayout extends MultiListLayout { || rotation == ROTATION_SEASCAPE) { reverse = !reverse; // if we're in portrait or seascape, reverse items } - if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { reverse = !reverse; // if we're in an RTL language, reverse items (again) } return reverse; } - /** - * Not ued in this implementation of the Global Actions Menu, but necessary for some others. - */ - @Override - public void setDivisionView(View v) { - // do nothing - } - + @VisibleForTesting protected float getAnimationDistance() { int rows = getListView().getRowCount(); float gridItemSize = getContext().getResources().getDimension( diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java new file mode 100644 index 0000000000000..f755a93ed2753 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayout.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2019 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.systemui.globalactions; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.HardwareBgDrawable; +import com.android.systemui.MultiListLayout; +import com.android.systemui.R; +import com.android.systemui.util.leak.RotationUtils; + +import java.util.Locale; + +/** + * Grid-based implementation of the button layout created by the global actions dialog. + */ +public abstract class GlobalActionsLayout extends MultiListLayout { + + boolean mBackgroundsSet; + + public GlobalActionsLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private void setBackgrounds() { + int gridBackgroundColor = getResources().getColor( + R.color.global_actions_grid_background, null); + int separatedBackgroundColor = getResources().getColor( + R.color.global_actions_separated_background, null); + HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext()); + HardwareBgDrawable separatedBackground = new HardwareBgDrawable(true, true, getContext()); + listBackground.setTint(gridBackgroundColor); + separatedBackground.setTint(separatedBackgroundColor); + getListView().setBackground(listBackground); + getSeparatedView().setBackground(separatedBackground); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // backgrounds set only once, the first time onMeasure is called after inflation + if (getListView() != null && !mBackgroundsSet) { + setBackgrounds(); + mBackgroundsSet = true; + } + } + + protected void addToListView(View v, boolean reverse) { + if (reverse) { + getListView().addView(v, 0); + } else { + getListView().addView(v); + } + } + + protected void addToSeparatedView(View v, boolean reverse) { + if (reverse) { + getSeparatedView().addView(v, 0); + } else { + getSeparatedView().addView(v); + } + } + + @VisibleForTesting + protected int getCurrentLayoutDirection() { + return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); + } + + @VisibleForTesting + protected int getCurrentRotation() { + return RotationUtils.getRotation(mContext); + } + + /** + * Determines whether the ListGridLayout should reverse the ordering of items within sublists. + * Used for RTL languages to ensure that items appear in the same positions, without having to + * override layoutDirection, which breaks Talkback ordering. + */ + protected abstract boolean shouldReverseListItems(); + + @Override + public void onUpdateList() { + super.onUpdateList(); + + ViewGroup separatedView = getSeparatedView(); + ViewGroup listView = getListView(); + + for (int i = 0; i < mAdapter.getCount(); i++) { + // generate the view item + View v; + boolean separated = mAdapter.shouldBeSeparated(i); + if (separated) { + v = mAdapter.getView(i, null, separatedView); + } else { + v = mAdapter.getView(i, null, listView); + } + if (separated) { + addToSeparatedView(v, false); + } else { + addToListView(v, shouldReverseListItems()); + } + } + } + + @Override + protected ViewGroup getSeparatedView() { + return findViewById(R.id.separated_button); + } + + @Override + protected ViewGroup getListView() { + return findViewById(android.R.id.list); + } + + protected View getWrapper() { + return getChildAt(0); + } + + /** + * Not used in this implementation of the Global Actions Menu, but necessary for some others. + */ + @Override + public void setDivisionView(View v) { + // do nothing + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsColumnLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsColumnLayoutTest.java new file mode 100644 index 0000000000000..16d665cbafcda --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsColumnLayoutTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2019 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.systemui.globalactions; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.leak.RotationUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link ListGridLayout}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class GlobalActionsColumnLayoutTest extends SysuiTestCase { + + private GlobalActionsColumnLayout mColumnLayout; + + @Before + public void setUp() throws Exception { + mColumnLayout = spy((GlobalActionsColumnLayout) + LayoutInflater.from(mContext).inflate(R.layout.global_actions_column, null)); + } + + @Test + public void testShouldReverseListItems() { + doReturn(View.LAYOUT_DIRECTION_LTR).when(mColumnLayout).getCurrentLayoutDirection(); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(false, mColumnLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + assertEquals(false, mColumnLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(true, mColumnLayout.shouldReverseListItems()); + + doReturn(View.LAYOUT_DIRECTION_RTL).when(mColumnLayout).getCurrentLayoutDirection(); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(true, mColumnLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + assertEquals(false, mColumnLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(false, mColumnLayout.shouldReverseListItems()); + } + + @Test + public void testGetAnimationOffsetX() { + doReturn(50f).when(mColumnLayout).getAnimationDistance(); + + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + assertEquals(50f, mColumnLayout.getAnimationOffsetX(), .01); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(0, mColumnLayout.getAnimationOffsetX(), .01); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(0, mColumnLayout.getAnimationOffsetX(), .01); + } + + @Test + public void testGetAnimationOffsetY() { + doReturn(50f).when(mColumnLayout).getAnimationDistance(); + + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + assertEquals(0, mColumnLayout.getAnimationOffsetY(), .01); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(-50f, mColumnLayout.getAnimationOffsetY(), .01); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mColumnLayout).getCurrentRotation(); + assertEquals(50f, mColumnLayout.getAnimationOffsetY(), .01); + } + + @Test + public void testSnapToPowerButton_portrait() { + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + doReturn(50).when(mColumnLayout).getPowerButtonOffsetDistance(); + + mColumnLayout.snapToPowerButton(); + assertEquals(Gravity.TOP | Gravity.RIGHT, mColumnLayout.getGravity()); + assertEquals(50, mColumnLayout.getPaddingTop(), .01); + } + + @Test + public void testCenterAlongEdge_portrait() { + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + + mColumnLayout.centerAlongEdge(); + assertEquals(Gravity.CENTER_VERTICAL | Gravity.RIGHT, mColumnLayout.getGravity()); + assertEquals(0, mColumnLayout.getPaddingTop(), .01); + } + + @Test + public void testUpdateSnap_initialState() { + doReturn(false).when(mColumnLayout).shouldSnapToPowerButton(); + + mColumnLayout.updateSnap(); // should do nothing, since snap has not changed from init state + + verify(mColumnLayout, times(0)).snapToPowerButton(); + verify(mColumnLayout, times(0)).centerAlongEdge(); + } + + @Test + public void testUpdateSnap_snapThenSnap() { + doReturn(true).when(mColumnLayout).shouldSnapToPowerButton(); + + mColumnLayout.updateSnap(); // should snap to power button + + verify(mColumnLayout, times(1)).snapToPowerButton(); + verify(mColumnLayout, times(0)).centerAlongEdge(); + + mColumnLayout.updateSnap(); // should do nothing, since this is the same state as last time + + verify(mColumnLayout, times(1)).snapToPowerButton(); + verify(mColumnLayout, times(0)).centerAlongEdge(); + } + + @Test + public void testUpdateSnap_snapThenCenter() { + doReturn(true).when(mColumnLayout).shouldSnapToPowerButton(); + + mColumnLayout.updateSnap(); // should snap to power button + + verify(mColumnLayout, times(1)).snapToPowerButton(); + verify(mColumnLayout, times(0)).centerAlongEdge(); + + doReturn(false).when(mColumnLayout).shouldSnapToPowerButton(); + + mColumnLayout.updateSnap(); // should center to edge + + verify(mColumnLayout, times(1)).snapToPowerButton(); + verify(mColumnLayout, times(1)).centerAlongEdge(); + } + + @Test + public void testShouldSnapToPowerButton_vertical() { + doReturn(RotationUtils.ROTATION_NONE).when(mColumnLayout).getCurrentRotation(); + doReturn(300).when(mColumnLayout).getPowerButtonOffsetDistance(); + doReturn(1000).when(mColumnLayout).getMeasuredHeight(); + View wrapper = spy(new View(mContext, null)); + doReturn(wrapper).when(mColumnLayout).getWrapper(); + doReturn(500).when(wrapper).getMeasuredHeight(); + + assertEquals(true, mColumnLayout.shouldSnapToPowerButton()); + + doReturn(600).when(mColumnLayout).getMeasuredHeight(); + + assertEquals(false, mColumnLayout.shouldSnapToPowerButton()); + } + + @Test + public void testShouldSnapToPowerButton_horizontal() { + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mColumnLayout).getCurrentRotation(); + doReturn(300).when(mColumnLayout).getPowerButtonOffsetDistance(); + doReturn(1000).when(mColumnLayout).getMeasuredWidth(); + View wrapper = spy(new View(mContext, null)); + doReturn(wrapper).when(mColumnLayout).getWrapper(); + doReturn(500).when(wrapper).getMeasuredWidth(); + + assertEquals(true, mColumnLayout.shouldSnapToPowerButton()); + + doReturn(600).when(mColumnLayout).getMeasuredWidth(); + + assertEquals(false, mColumnLayout.shouldSnapToPowerButton()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java index 3c52e9d86c150..a396f3e8bf467 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java @@ -18,12 +18,8 @@ package com.android.systemui.globalactions; import static junit.framework.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; @@ -32,7 +28,6 @@ import android.view.ViewGroup; import androidx.test.filters.SmallTest; -import com.android.systemui.MultiListLayout; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.leak.RotationUtils; @@ -49,61 +44,12 @@ import org.junit.runner.RunWith; public class GlobalActionsGridLayoutTest extends SysuiTestCase { private GlobalActionsGridLayout mGridLayout; - private TestAdapter mAdapter; private ListGridLayout mListGrid; - private class TestAdapter extends MultiListLayout.MultiListAdapter { - @Override - public void onClickItem(int index) { } - - @Override - public boolean onLongClickItem(int index) { - return true; - } - - @Override - public int countSeparatedItems() { - return -1; - } - - @Override - public int countListItems() { - return -1; - } - - @Override - public boolean shouldBeSeparated(int position) { - return false; - } - - @Override - public int getCount() { - return countSeparatedItems() + countListItems(); - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return -1; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - return null; - } - } - - @Before public void setUp() throws Exception { mGridLayout = spy((GlobalActionsGridLayout) LayoutInflater.from(mContext).inflate(R.layout.global_actions_grid, null)); - mAdapter = spy(new TestAdapter()); - mGridLayout.setAdapter(mAdapter); mListGrid = spy(mGridLayout.getListView()); doReturn(mListGrid).when(mGridLayout).getListView(); } @@ -122,7 +68,7 @@ public class GlobalActionsGridLayoutTest extends SysuiTestCase { @Test public void testShouldReverseListItems() { - doReturn(View.LAYOUT_DIRECTION_LTR).when(mGridLayout).getLayoutDirection(); + doReturn(View.LAYOUT_DIRECTION_LTR).when(mGridLayout).getCurrentLayoutDirection(); doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); assertEquals(false, mGridLayout.shouldReverseListItems()); @@ -133,7 +79,7 @@ public class GlobalActionsGridLayoutTest extends SysuiTestCase { doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation(); assertEquals(true, mGridLayout.shouldReverseListItems()); - doReturn(View.LAYOUT_DIRECTION_RTL).when(mGridLayout).getLayoutDirection(); + doReturn(View.LAYOUT_DIRECTION_RTL).when(mGridLayout).getCurrentLayoutDirection(); doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); assertEquals(true, mGridLayout.shouldReverseListItems()); @@ -185,123 +131,26 @@ public class GlobalActionsGridLayoutTest extends SysuiTestCase { assertEquals(0f, mGridLayout.getAnimationOffsetY(), .01); } - @Test(expected = IllegalStateException.class) - public void testOnUpdateList_noAdapter() { - mGridLayout.setAdapter(null); - mGridLayout.updateList(); - } - @Test - public void testOnUpdateList_noItems() { - doReturn(0).when(mAdapter).countSeparatedItems(); - doReturn(0).when(mAdapter).countListItems(); - mGridLayout.updateList(); - - ViewGroup separatedView = mGridLayout.getSeparatedView(); - ListGridLayout listView = mGridLayout.getListView(); - - assertEquals(0, separatedView.getChildCount()); - assertEquals(View.GONE, separatedView.getVisibility()); - - verify(mListGrid, times(0)).addItem(any()); - } - - @Test - public void testOnUpdateList_resizesFirstSeparatedItem() { - doReturn(1).when(mAdapter).countSeparatedItems(); - doReturn(0).when(mAdapter).countListItems(); + public void testUpdateSeparatedItemSize() { View firstView = new View(mContext, null); View secondView = new View(mContext, null); - doReturn(firstView).when(mAdapter).getView(eq(0), any(), any()); - doReturn(true).when(mAdapter).shouldBeSeparated(0); + ViewGroup separatedView = mGridLayout.getSeparatedView(); + separatedView.addView(firstView); - mGridLayout.updateList(); + mGridLayout.updateSeparatedItemSize(); ViewGroup.LayoutParams childParams = firstView.getLayoutParams(); assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, childParams.width); assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, childParams.height); - doReturn(2).when(mAdapter).countSeparatedItems(); - doReturn(secondView).when(mAdapter).getView(eq(1), any(), any()); - doReturn(true).when(mAdapter).shouldBeSeparated(1); + separatedView.addView(secondView); - mGridLayout.updateList(); + mGridLayout.updateSeparatedItemSize(); childParams = firstView.getLayoutParams(); assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, childParams.width); assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, childParams.height); - - - } - - @Test - public void testOnUpdateList_onlySeparatedItems() { - doReturn(1).when(mAdapter).countSeparatedItems(); - doReturn(0).when(mAdapter).countListItems(); - View testView = new View(mContext, null); - doReturn(testView).when(mAdapter).getView(eq(0), any(), any()); - doReturn(true).when(mAdapter).shouldBeSeparated(0); - - mGridLayout.updateList(); - - verify(mListGrid, times(0)).addItem(any()); - } - - @Test - public void testOnUpdateList_oneSeparatedOneList() { - doReturn(1).when(mAdapter).countSeparatedItems(); - doReturn(1).when(mAdapter).countListItems(); - View view1 = new View(mContext, null); - View view2 = new View(mContext, null); - - doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); - doReturn(true).when(mAdapter).shouldBeSeparated(0); - - doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(1); - - mGridLayout.updateList(); - - ViewGroup separatedView = mGridLayout.getSeparatedView(); - - assertEquals(1, separatedView.getChildCount()); - assertEquals(View.VISIBLE, separatedView.getVisibility()); - assertEquals(view1, separatedView.getChildAt(0)); - - verify(mListGrid, times(1)).addItem(view2); - } - - @Test - public void testOnUpdateList_fourInList() { - doReturn(0).when(mAdapter).countSeparatedItems(); - doReturn(4).when(mAdapter).countListItems(); - View view1 = new View(mContext, null); - View view2 = new View(mContext, null); - View view3 = new View(mContext, null); - View view4 = new View(mContext, null); - - doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(0); - - doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(1); - - doReturn(view3).when(mAdapter).getView(eq(2), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(2); - - doReturn(view4).when(mAdapter).getView(eq(3), any(), any()); - doReturn(false).when(mAdapter).shouldBeSeparated(3); - - mGridLayout.updateList(); - - ViewGroup separatedView = mGridLayout.getSeparatedView(); - assertEquals(0, separatedView.getChildCount()); - assertEquals(View.GONE, separatedView.getVisibility()); - - verify(mListGrid, times(1)).addItem(view1); - verify(mListGrid, times(1)).addItem(view2); - verify(mListGrid, times(1)).addItem(view3); - verify(mListGrid, times(1)).addItem(view4); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java new file mode 100644 index 0000000000000..16dcd659b3f91 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2019 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.systemui.globalactions; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.testing.AndroidTestingRunner; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.MultiListLayout; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +/** + * Tests for {@link ListGridLayout}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class GlobalActionsLayoutTest extends SysuiTestCase { + + private TestLayout mLayout; + private TestAdapter mAdapter; + + private class TestAdapter extends MultiListLayout.MultiListAdapter { + @Override + public void onClickItem(int index) { } + + @Override + public boolean onLongClickItem(int index) { + return true; + } + + @Override + public int countSeparatedItems() { + return -1; + } + + @Override + public int countListItems() { + return -1; + } + + @Override + public boolean shouldBeSeparated(int position) { + return false; + } + + @Override + public int getCount() { + return countSeparatedItems() + countListItems(); + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return -1; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return null; + } + } + + private class TestLayout extends GlobalActionsLayout { + ArrayList mSeparatedViews = new ArrayList<>(); + ArrayList mListViews = new ArrayList<>(); + boolean mSeparatedViewVisible = false; + + TestLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected boolean shouldReverseListItems() { + return false; + } + + @Override + public float getAnimationOffsetX() { + return 0; + } + + @Override + public float getAnimationOffsetY() { + return 0; + } + + @Override + protected void addToListView(View v, boolean reverse) { + if (reverse) { + mListViews.add(0, v); + } else { + mListViews.add(v); + } + } + + @Override + protected void addToSeparatedView(View v, boolean reverse) { + if (reverse) { + mSeparatedViews.add(0, v); + } else { + mSeparatedViews.add(v); + } + } + + @Override + protected void setSeparatedViewVisibility(boolean visible) { + mSeparatedViewVisible = visible; + } + } + + @Before + public void setUp() throws Exception { + mLayout = spy(new TestLayout(mContext, null)); + mAdapter = spy(new TestAdapter()); + mLayout.setAdapter(mAdapter); + } + + @Test(expected = IllegalStateException.class) + public void testOnUpdateList_noAdapter() { + mLayout.setAdapter(null); + mLayout.updateList(); + } + + @Test + public void testOnUpdateList_noItems() { + doReturn(0).when(mAdapter).countSeparatedItems(); + doReturn(0).when(mAdapter).countListItems(); + mLayout.updateList(); + + assertEquals(0, mLayout.mSeparatedViews.size()); + assertEquals(0, mLayout.mListViews.size()); + + assertEquals(false, mLayout.mSeparatedViewVisible); + } + + @Test + public void testOnUpdateList_oneSeparatedOneList() { + doReturn(1).when(mAdapter).countSeparatedItems(); + doReturn(1).when(mAdapter).countListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(0); + + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(1); + + mLayout.updateList(); + + assertEquals(1, mLayout.mSeparatedViews.size()); + assertEquals(1, mLayout.mListViews.size()); + assertEquals(view1, mLayout.mSeparatedViews.get(0)); + assertEquals(view2, mLayout.mListViews.get(0)); + } + + + @Test + public void testOnUpdateList_twoSeparatedItems() { + doReturn(2).when(mAdapter).countSeparatedItems(); + doReturn(0).when(mAdapter).countListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(0); + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(1); + + mLayout.updateList(); + + assertEquals(2, mLayout.mSeparatedViews.size()); + assertEquals(0, mLayout.mListViews.size()); + + assertEquals(view1, mLayout.mSeparatedViews.get(0)); + assertEquals(view2, mLayout.mSeparatedViews.get(1)); + + // if separated view has items in it, should be made visible + assertEquals(true, mLayout.mSeparatedViewVisible); + } + + @Test + public void testOnUpdateList_twoSeparatedItems_reverse() { + doReturn(2).when(mAdapter).countSeparatedItems(); + doReturn(0).when(mAdapter).countListItems(); + doReturn(true).when(mLayout).shouldReverseListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(0); + + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(true).when(mAdapter).shouldBeSeparated(1); + + mLayout.updateList(); + + assertEquals(2, mLayout.mSeparatedViews.size()); + assertEquals(0, mLayout.mListViews.size()); + + // separated view items are not reversed in current implementation, and this is intentional! + assertEquals(view1, mLayout.mSeparatedViews.get(0)); + assertEquals(view2, mLayout.mSeparatedViews.get(1)); + } + + @Test + public void testOnUpdateList_fourInList() { + doReturn(0).when(mAdapter).countSeparatedItems(); + doReturn(4).when(mAdapter).countListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + View view3 = new View(mContext, null); + View view4 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(0); + + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(1); + + doReturn(view3).when(mAdapter).getView(eq(2), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(2); + + doReturn(view4).when(mAdapter).getView(eq(3), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(3); + + mLayout.updateList(); + + assertEquals(0, mLayout.mSeparatedViews.size()); + assertEquals(4, mLayout.mListViews.size()); + assertEquals(view1, mLayout.mListViews.get(0)); + assertEquals(view2, mLayout.mListViews.get(1)); + assertEquals(view3, mLayout.mListViews.get(2)); + assertEquals(view4, mLayout.mListViews.get(3)); + } + + @Test + public void testOnUpdateList_fourInList_reverse() { + doReturn(0).when(mAdapter).countSeparatedItems(); + doReturn(4).when(mAdapter).countListItems(); + doReturn(true).when(mLayout).shouldReverseListItems(); + View view1 = new View(mContext, null); + View view2 = new View(mContext, null); + View view3 = new View(mContext, null); + View view4 = new View(mContext, null); + + doReturn(view1).when(mAdapter).getView(eq(0), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(0); + + doReturn(view2).when(mAdapter).getView(eq(1), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(1); + + doReturn(view3).when(mAdapter).getView(eq(2), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(2); + + doReturn(view4).when(mAdapter).getView(eq(3), any(), any()); + doReturn(false).when(mAdapter).shouldBeSeparated(3); + + mLayout.updateList(); + + assertEquals(0, mLayout.mSeparatedViews.size()); + assertEquals(4, mLayout.mListViews.size()); + assertEquals(view1, mLayout.mListViews.get(3)); + assertEquals(view2, mLayout.mListViews.get(2)); + assertEquals(view3, mLayout.mListViews.get(1)); + assertEquals(view4, mLayout.mListViews.get(0)); + } +}