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
This commit is contained in:
Aaron Heuckroth
2019-05-01 10:44:59 -04:00
parent 5e28f8c838
commit e2d92acee5
12 changed files with 1115 additions and 260 deletions

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<com.android.systemui.globalactions.GlobalActionsColumnLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/global_actions_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:clipToPadding="false"
android:theme="@style/qs_theme"
android:gravity="center_horizontal | top"
android:clipChildren="false"
>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="0dp"
android:orientation="horizontal"
android:clipChildren="false"
android:clipToPadding="false"
>
<!-- Grid of action items -->
<LinearLayout
android:id="@android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="@dimen/global_actions_grid_side_margin"
android:translationZ="@dimen/global_actions_translate"
android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
android:paddingTop="@dimen/global_actions_grid_vertical_padding"
android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
android:background="?android:attr/colorBackgroundFloating"
/>
<!-- For separated items-->
<LinearLayout
android:id="@+id/separated_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
android:layout_marginTop="@dimen/global_actions_grid_side_margin"
android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
android:paddingTop="@dimen/global_actions_grid_vertical_padding"
android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
android:orientation="horizontal"
android:background="?android:attr/colorBackgroundFloating"
android:translationZ="@dimen/global_actions_translate"
/>
</LinearLayout>
</com.android.systemui.globalactions.GlobalActionsColumnLayout>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<com.android.systemui.globalactions.GlobalActionsColumnLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/global_actions_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:clipToPadding="false"
android:theme="@style/qs_theme"
android:gravity="center_horizontal | bottom"
android:clipChildren="false"
>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="0dp"
android:orientation="horizontal"
>
<!-- Grid of action items -->
<com.android.systemui.globalactions.ListGridLayout
android:id="@android:id/list"
android:layout_gravity="bottom|left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="@dimen/global_actions_grid_side_margin"
android:translationZ="@dimen/global_actions_translate"
android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
android:paddingTop="@dimen/global_actions_grid_vertical_padding"
android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
android:background="?android:attr/colorBackgroundFloating"
/>
<!-- For separated items-->
<LinearLayout
android:id="@+id/separated_button"
android:layout_gravity="top|left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/global_actions_grid_side_margin"
android:layout_marginBottom="@dimen/global_actions_grid_side_margin"
android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
android:paddingTop="@dimen/global_actions_grid_vertical_padding"
android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
android:orientation="horizontal"
android:background="?android:attr/colorBackgroundFloating"
android:translationZ="@dimen/global_actions_translate"
/>
</LinearLayout>
</com.android.systemui.globalactions.GlobalActionsColumnLayout>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<com.android.systemui.globalactions.GlobalActionsColumnLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/global_actions_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipToPadding="false"
android:theme="@style/qs_theme"
android:gravity="center_vertical | right"
android:clipChildren="false"
>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:gravity="top | right"
android:orientation="vertical"
android:padding="0dp"
android:layout_marginTop="@dimen/global_actions_grid_container_bottom_margin"
android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin"
>
<!-- Global actions is right-aligned to be physically near power button -->
<LinearLayout
android:id="@android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="right"
android:translationZ="@dimen/global_actions_translate"
android:layout_marginRight="@dimen/global_actions_grid_side_margin"
android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
android:paddingTop="@dimen/global_actions_grid_vertical_padding"
android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
/>
<!-- For separated items-->
<LinearLayout
android:id="@+id/separated_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/global_actions_grid_side_margin"
android:layout_marginRight="@dimen/global_actions_grid_side_margin"
android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
android:paddingTop="@dimen/global_actions_grid_vertical_padding"
android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
android:orientation="vertical"
android:gravity="center"
android:translationZ="@dimen/global_actions_translate"
/>
</LinearLayout>
</com.android.systemui.globalactions.GlobalActionsColumnLayout>

View File

@@ -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();

View File

@@ -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());

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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(

View File

@@ -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
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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<View> mSeparatedViews = new ArrayList<>();
ArrayList<View> 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));
}
}