diff --git a/packages/SystemUI/res/layout-land/global_actions_grid.xml b/packages/SystemUI/res/layout-land/global_actions_grid.xml index c51e71b5c61f1..511910ea2f618 100644 --- a/packages/SystemUI/res/layout-land/global_actions_grid.xml +++ b/packages/SystemUI/res/layout-land/global_actions_grid.xml @@ -8,7 +8,7 @@ android:clipToPadding="false" android:theme="@style/qs_theme" android:paddingLeft="@dimen/global_actions_top_padding" - android:gravity="top|left" + android:gravity="right" android:clipChildren="false" > diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml index 8651e5a463c98..3f10b388fdd5c 100644 --- a/packages/SystemUI/res/layout/global_actions_grid.xml +++ b/packages/SystemUI/res/layout/global_actions_grid.xml @@ -6,9 +6,8 @@ android:layout_height="match_parent" android:orientation="horizontal" android:clipToPadding="false" - android:paddingTop="@dimen/global_actions_top_padding" android:theme="@style/qs_theme" - android:gravity="top|center" + android:gravity="bottom" android:clipChildren="false" > separatedActions = - mAdapter.getSeparatedItems(); - ArrayList listActions = mAdapter.getListItems(); for (int i = 0; i < mAdapter.getCount(); i++) { - Object action = mAdapter.getItem(i); - int separatedIndex = separatedActions.indexOf(action); ViewGroup parent; - if (separatedIndex != -1) { + boolean separated = mAdapter.shouldBeSeparated(i); + if (separated) { parent = getSeparatedView(); } else { - int listIndex = listActions.indexOf(action); parent = getListView(); } View v = mAdapter.getView(i, null, parent); - final int pos = i; - v.setOnClickListener(view -> mAdapter.onClickItem(pos)); - v.setOnLongClickListener(view -> mAdapter.onLongClickItem(pos)); parent.addView(v); } } @@ -421,7 +410,9 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); + post(() -> updatePosition()); + } private void animateChild(int oldHeight, int newHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java index a30b681ed7cc9..f8287a4dc2dc8 100644 --- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -26,16 +26,12 @@ import android.widget.LinearLayout; import com.android.systemui.util.leak.RotationUtils; -import java.util.ArrayList; - /** * Layout class representing the Global Actions menu which appears when the power button is held. */ public abstract class MultiListLayout extends LinearLayout { protected boolean mHasOutsideTouch; protected MultiListAdapter mAdapter; - protected boolean mSnapToEdge; - protected int mRotation; protected RotationListener mRotationListener; @@ -51,7 +47,7 @@ public abstract class MultiListLayout extends LinearLayout { /** * Removes all child items from the separated and list views, if they exist. */ - public abstract void removeAllItems(); + protected abstract void removeAllItems(); /** * Sets the divided view, which may have a differently-colored background. @@ -69,13 +65,6 @@ public abstract class MultiListLayout extends LinearLayout { getSeparatedView().setVisibility(visible ? View.VISIBLE : View.GONE); } - /** - * Sets whether the GlobalActions view should snap to the edge of the screen. - */ - public void setSnapToEdge(boolean snap) { - mSnapToEdge = snap; - } - /** * Sets the adapter used to inflate items. */ @@ -122,6 +111,7 @@ public abstract class MultiListLayout extends LinearLayout { } protected void onUpdateList() { + removeAllItems(); setSeparatedViewVisibility(mAdapter.hasSeparatedItems()); } @@ -163,16 +153,14 @@ public abstract class MultiListLayout extends LinearLayout { */ public abstract static class MultiListAdapter extends BaseAdapter { /** - * Creates an ArrayList of items which should be rendered in the separated view. - * @param useSeparatedView is true if the separated view will be used, false otherwise. + * Counts the number of items to be rendered in the separated view. */ - public abstract ArrayList getSeparatedItems(); + public abstract int countSeparatedItems(); /** - * Creates an ArrayList of items which should be rendered in the list view. - * @param useSeparatedView True if the separated view will be used, false otherwise. + * Counts the number of items be rendered in the list view. */ - public abstract ArrayList getListItems(); + public abstract int countListItems(); /** * Callback to run when an individual item is clicked or pressed. @@ -192,7 +180,13 @@ public abstract class MultiListLayout extends LinearLayout { * or not to hide the separated list from view. */ public boolean hasSeparatedItems() { - return getSeparatedItems().size() > 0; + return countSeparatedItems() > 0; } + + /** + * Determines whether the item at the given index should be rendered in the separarted view. + * @param position The index of the item. + */ + public abstract boolean shouldBeSeparated(int position); } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 411536c2ddbba..9b69dc50a1a1b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -919,58 +919,48 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}. */ public class MyAdapter extends MultiListAdapter { - @Override - public int getCount() { + private int countItems(boolean separated) { int count = 0; for (int i = 0; i < mItems.size(); i++) { final Action action = mItems.get(i); - if (mKeyguardShowing && !action.showDuringKeyguard()) { - continue; + if (shouldBeShown(action) && action.shouldBeSeparated() == separated) { + count++; } - if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { - continue; - } - count++; } return count; } + private boolean shouldBeShown(Action action) { + if (mKeyguardShowing && !action.showDuringKeyguard()) { + return false; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + return false; + } + return true; + } + + @Override + public int countSeparatedItems() { + return countItems(true); + } + + @Override + public int countListItems() { + return countItems(false); + } + + @Override + public int getCount() { + return countSeparatedItems() + countListItems(); + } + @Override public boolean isEnabled(int position) { return getItem(position).isEnabled(); } - @Override - public ArrayList getSeparatedItems() { - ArrayList separatedActions = new ArrayList(); - if (!shouldUseSeparatedView()) { - return separatedActions; - } - for (int i = 0; i < mItems.size(); i++) { - final Action action = mItems.get(i); - if (action.shouldBeSeparated()) { - separatedActions.add(action); - } - } - return separatedActions; - } - - @Override - public ArrayList getListItems() { - if (!shouldUseSeparatedView()) { - return new ArrayList(mItems); - } - ArrayList listActions = new ArrayList(); - for (int i = 0; i < mItems.size(); i++) { - final Action action = mItems.get(i); - if (!action.shouldBeSeparated()) { - listActions.add(action); - } - } - return listActions; - } - @Override public boolean areAllItemsEnabled() { return false; @@ -978,14 +968,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public Action getItem(int position) { - int filteredPos = 0; for (int i = 0; i < mItems.size(); i++) { final Action action = mItems.get(i); - if (mKeyguardShowing && !action.showDuringKeyguard()) { - continue; - } - if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + if (!shouldBeShown(action)) { continue; } if (filteredPos == position) { @@ -1010,10 +996,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, public View getView(int position, View convertView, ViewGroup parent) { Action action = getItem(position); View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); - // Everything but screenshot, the last item, gets white background. - if (position == getCount() - 1) { - MultiListLayout.get(parent).setDivisionView(view); - } + view.setOnClickListener(v -> onClickItem(position)); + view.setOnLongClickListener(v -> onLongClickItem(position)); return view; } @@ -1035,6 +1019,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } item.onPress(); } + + @Override + public boolean shouldBeSeparated(int position) { + return getItem(position).shouldBeSeparated(); + } } // note: the scheme below made more sense when we were planning on having @@ -1589,7 +1578,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, com.android.systemui.R.drawable.global_action_panel_scrim); mScrimAlpha = 1f; } - mGlobalActionsLayout.setSnapToEdge(true); getWindow().setBackgroundDrawable(mBackgroundDrawable); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java index 669348e4b4812..554ed738c3354 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -21,19 +21,16 @@ 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.text.TextUtils; import android.util.AttributeSet; -import android.view.Gravity; +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; -import java.util.ArrayList; -import java.util.Locale; - /** * Grid-based implementation of the button layout created by the global actions dialog. */ @@ -69,67 +66,65 @@ public class GlobalActionsGridLayout extends MultiListLayout { } } - /** - * Sets the number of items expected to be rendered in the list container. This allows the - * layout to correctly determine which parent containers will be used for items before they have - * beenadded to the layout. - * @param count The number of items expected. - */ - public void setExpectedListItemCount(int count) { - getListView().setExpectedCount(count); + @VisibleForTesting + protected int getCurrentRotation() { + return RotationUtils.getRotation(mContext); + } + + @VisibleForTesting + protected void setupListView(ListGridLayout listView, int itemCount) { + listView.setExpectedCount(itemCount); + listView.setReverseSublists(shouldReverseSublists()); + listView.setReverseItems(shouldReverseListItems()); + listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns()); } @Override public void onUpdateList() { super.onUpdateList(); - ArrayList separatedActions = - mAdapter.getSeparatedItems(); - ArrayList listActions = mAdapter.getListItems(); - setExpectedListItemCount(listActions.size()); - int rotation = RotationUtils.getRotation(mContext); - boolean reverse = false; // should we add items to parents in the reverse order? - if (rotation == ROTATION_NONE - || rotation == ROTATION_SEASCAPE) { - reverse = !reverse; // if we're in portrait or seascape, reverse items - } - if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) - == View.LAYOUT_DIRECTION_RTL) { - reverse = !reverse; // if we're in an RTL language, reverse items (again) - } + ViewGroup separatedView = getSeparatedView(); + ListGridLayout listView = getListView(); + setupListView(listView, mAdapter.countListItems()); for (int i = 0; i < mAdapter.getCount(); i++) { - Object action = mAdapter.getItem(i); - int separatedIndex = separatedActions.indexOf(action); - ViewGroup parent; - if (separatedIndex != -1) { - parent = getParentView(true, separatedIndex, rotation); + // generate the view item + View v; + boolean separated = mAdapter.shouldBeSeparated(i); + if (separated) { + v = mAdapter.getView(i, null, separatedView); } else { - int listIndex = listActions.indexOf(action); - parent = getParentView(false, listIndex, rotation); + v = mAdapter.getView(i, null, listView); } - View v = mAdapter.getView(i, null, parent); - final int pos = i; - v.setOnClickListener(view -> mAdapter.onClickItem(pos)); - v.setOnLongClickListener(view -> mAdapter.onLongClickItem(pos)); - if (reverse) { - parent.addView(v, 0); // reverse order of items + Log.d("GlobalActionsGridLayout", "View: " + v); + + if (separated) { + separatedView.addView(v); } else { - parent.addView(v); + listView.addItem(v); } - parent.setVisibility(View.VISIBLE); } - updateSnapPosition(); - updateSeparatedButtonSize(); + updateSeparatedItemSize(); } - private void updateSeparatedButtonSize() { + /** + * 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() { ViewGroup separated = getSeparatedView(); + if (separated.getChildCount() == 0) { + return; + } + View firstChild = separated.getChildAt(0); + ViewGroup.LayoutParams childParams = firstChild.getLayoutParams(); + if (separated.getChildCount() == 1) { - View onlyChild = separated.getChildAt(0); - ViewGroup.LayoutParams childParams = onlyChild.getLayoutParams(); childParams.width = ViewGroup.LayoutParams.MATCH_PARENT; childParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + } else { + childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; + childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; } } @@ -138,19 +133,6 @@ public class GlobalActionsGridLayout extends MultiListLayout { return findViewById(com.android.systemui.R.id.separated_button); } - private void updateSnapPosition() { - if (mSnapToEdge) { - setPadding(0, 0, 0, 0); - if (mRotation == ROTATION_LANDSCAPE) { - setGravity(Gravity.RIGHT); - } else if (mRotation == ROTATION_SEASCAPE) { - setGravity(Gravity.LEFT); - } else { - setGravity(Gravity.BOTTOM); - } - } - } - @Override protected ListGridLayout getListView() { return findViewById(android.R.id.list); @@ -168,19 +150,47 @@ public class GlobalActionsGridLayout extends MultiListLayout { } } - public ViewGroup getParentView(boolean separated, int index, int rotation) { - if (separated) { - return getSeparatedView(); - } else { - switch (rotation) { - case ROTATION_LANDSCAPE: - return getListView().getParentView(index, false, true); - case ROTATION_SEASCAPE: - return getListView().getParentView(index, true, true); - default: - return getListView().getParentView(index, false, false); - } + /** + * Determines whether the ListGridLayout should fill sublists in the reverse order. + * Used to account for sublist ordering changing between landscape and seascape views. + */ + @VisibleForTesting + protected boolean shouldReverseSublists() { + if (getCurrentRotation() == ROTATION_SEASCAPE) { + return true; } + return false; + } + + /** + * Determines whether the ListGridLayout should fill rows first instead of columns. + * Used to account for vertical/horizontal changes due to landscape or seascape rotations. + */ + @VisibleForTesting + protected boolean shouldSwapRowsAndColumns() { + if (getCurrentRotation() == ROTATION_NONE) { + return false; + } + 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 + protected boolean shouldReverseListItems() { + int rotation = getCurrentRotation(); + boolean reverse = false; // should we add items to parents in the reverse order? + if (rotation == ROTATION_NONE + || rotation == ROTATION_SEASCAPE) { + reverse = !reverse; // if we're in portrait or seascape, reverse items + } + if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + reverse = !reverse; // if we're in an RTL language, reverse items (again) + } + return reverse; } /** @@ -191,7 +201,7 @@ public class GlobalActionsGridLayout extends MultiListLayout { // do nothing } - private float getAnimationDistance() { + protected float getAnimationDistance() { int rows = getListView().getRowCount(); float gridItemSize = getContext().getResources().getDimension( com.android.systemui.R.dimen.global_actions_grid_item_height); @@ -200,7 +210,7 @@ public class GlobalActionsGridLayout extends MultiListLayout { @Override public float getAnimationOffsetX() { - switch (RotationUtils.getRotation(getContext())) { + switch (getCurrentRotation()) { case ROTATION_LANDSCAPE: return getAnimationDistance(); case ROTATION_SEASCAPE: @@ -212,7 +222,7 @@ public class GlobalActionsGridLayout extends MultiListLayout { @Override public float getAnimationOffsetY() { - if (RotationUtils.getRotation(mContext) == ROTATION_NONE) { + if (getCurrentRotation() == ROTATION_NONE) { return getAnimationDistance(); } return 0; diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java index 6bc975a9dc621..8c93b13921e6a 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java @@ -22,6 +22,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; +import com.android.internal.annotations.VisibleForTesting; + /** * Layout which uses nested LinearLayouts to create a grid with the following behavior: * @@ -40,6 +42,10 @@ import android.widget.LinearLayout; public class ListGridLayout extends LinearLayout { private static final String TAG = "ListGridLayout"; private int mExpectedCount; + private int mCurrentCount = 0; + private boolean mSwapRowsAndColumns; + private boolean mReverseSublists; + private boolean mReverseItems; // number of rows and columns to use for different numbers of items private final int[][] mConfigs = { @@ -60,17 +66,61 @@ public class ListGridLayout extends LinearLayout { super(context, attrs); } + /** + * Sets whether this grid should prioritize filling rows or columns first. + */ + public void setSwapRowsAndColumns(boolean swap) { + mSwapRowsAndColumns = swap; + } + + /** + * Sets whether this grid should fill sublists in reverse order. + */ + public void setReverseSublists(boolean reverse) { + mReverseSublists = reverse; + } + + /** + * Sets whether this grid should add items to sublists in reverse order. + * @param reverse + */ + public void setReverseItems(boolean reverse) { + mReverseItems = reverse; + } + /** * Remove all items from this grid. */ public void removeAllItems() { for (int i = 0; i < getChildCount(); i++) { - ViewGroup subList = (ViewGroup) getChildAt(i); + ViewGroup subList = getSublist(i); if (subList != null) { subList.removeAllViews(); subList.setVisibility(View.GONE); } } + mCurrentCount = 0; + } + + /** + * Adds a view item to this grid, placing it in the correct sublist and ensuring that the + * sublist is visible. + * + * This function is stateful, since it tracks how many items have been added thus far, to + * determine which sublist they should be added to. To ensure that this works correctly, call + * removeAllItems() instead of removing views individually with removeView() to ensure that the + * counter gets reset correctly. + * @param item + */ + public void addItem(View item) { + ViewGroup parent = getParentView(mCurrentCount, mReverseSublists, mSwapRowsAndColumns); + if (mReverseItems) { + parent.addView(item, 0); + } else { + parent.addView(item); + } + parent.setVisibility(View.VISIBLE); + mCurrentCount++; } /** @@ -83,13 +133,20 @@ public class ListGridLayout extends LinearLayout { * true will cause rows to fill first, adding one item to each column. * @return */ - public ViewGroup getParentView(int index, boolean reverseSublists, boolean swapRowsAndColumns) { + @VisibleForTesting + protected ViewGroup getParentView(int index, boolean reverseSublists, + boolean swapRowsAndColumns) { if (getRowCount() == 0 || index < 0) { return null; } int targetIndex = Math.min(index, getMaxElementCount() - 1); int row = getParentViewIndex(targetIndex, reverseSublists, swapRowsAndColumns); - return (ViewGroup) getChildAt(row); + return getSublist(row); + } + + @VisibleForTesting + protected ViewGroup getSublist(int index) { + return (ViewGroup) getChildAt(index); } private int reverseSublistIndex(int index) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java new file mode 100644 index 0000000000000..3c52e9d86c150 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java @@ -0,0 +1,307 @@ +/* + * 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 static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.view.LayoutInflater; +import android.view.View; +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; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link ListGridLayout}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +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(); + } + + @Test + public void testShouldSwapRowsAndColumns() { + doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation(); + assertEquals(false, mGridLayout.shouldSwapRowsAndColumns()); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(true, mGridLayout.shouldSwapRowsAndColumns()); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(true, mGridLayout.shouldSwapRowsAndColumns()); + } + + @Test + public void testShouldReverseListItems() { + doReturn(View.LAYOUT_DIRECTION_LTR).when(mGridLayout).getLayoutDirection(); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(false, mGridLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation(); + assertEquals(true, mGridLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(true, mGridLayout.shouldReverseListItems()); + + doReturn(View.LAYOUT_DIRECTION_RTL).when(mGridLayout).getLayoutDirection(); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(true, mGridLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation(); + assertEquals(false, mGridLayout.shouldReverseListItems()); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(false, mGridLayout.shouldReverseListItems()); + } + + @Test + public void testShouldReverseSublists() { + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(false, mGridLayout.shouldReverseSublists()); + + doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation(); + assertEquals(false, mGridLayout.shouldReverseSublists()); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(true, mGridLayout.shouldReverseSublists()); + } + + @Test + public void testGetAnimationOffsetX() { + doReturn(50f).when(mGridLayout).getAnimationDistance(); + + doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation(); + assertEquals(0f, mGridLayout.getAnimationOffsetX(), .01); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(50f, mGridLayout.getAnimationOffsetX(), .01); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(-50f, mGridLayout.getAnimationOffsetX(), .01); + } + + @Test + public void testGetAnimationOffsetY() { + doReturn(50f).when(mGridLayout).getAnimationDistance(); + + doReturn(RotationUtils.ROTATION_NONE).when(mGridLayout).getCurrentRotation(); + assertEquals(50f, mGridLayout.getAnimationOffsetY(), .01); + + doReturn(RotationUtils.ROTATION_LANDSCAPE).when(mGridLayout).getCurrentRotation(); + assertEquals(0f, mGridLayout.getAnimationOffsetY(), .01); + + doReturn(RotationUtils.ROTATION_SEASCAPE).when(mGridLayout).getCurrentRotation(); + 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(); + 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); + + mGridLayout.updateList(); + + 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); + + mGridLayout.updateList(); + + 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/ListGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java index 26f1de857302e..746140fc725d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java @@ -125,27 +125,27 @@ public class ListGridLayoutTest extends SysuiTestCase { assertEquals(null, mListGridLayout.getParentView(-1, false, false)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(0, false, false)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(1, false, false)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(2, false, false)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(3, false, false)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(4, false, false)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(5, false, false)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(6, false, false)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(7, false, false)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(8, false, false)); // above valid range - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(9, false, false)); } @@ -157,27 +157,27 @@ public class ListGridLayoutTest extends SysuiTestCase { assertEquals(null, mListGridLayout.getParentView(-1, true, false)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(0, true, false)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(1, true, false)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(2, true, false)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(3, true, false)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(4, true, false)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(5, true, false)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(6, true, false)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(7, true, false)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(8, true, false)); // above valid range - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(9, true, false)); } @@ -189,27 +189,27 @@ public class ListGridLayoutTest extends SysuiTestCase { assertEquals(null, mListGridLayout.getParentView(-1, false, true)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(0, false, true)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(1, false, true)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(2, false, true)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(3, false, true)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(4, false, true)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(5, false, true)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(6, false, true)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(7, false, true)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(8, false, true)); // above valid range - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(9, false, true)); } @@ -221,35 +221,38 @@ public class ListGridLayoutTest extends SysuiTestCase { assertEquals(null, mListGridLayout.getParentView(-1, true, true)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(0, true, true)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(1, true, true)); - assertEquals(mListGridLayout.getChildAt(2), + assertEquals(mListGridLayout.getSublist(2), mListGridLayout.getParentView(2, true, true)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(3, true, true)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(4, true, true)); - assertEquals(mListGridLayout.getChildAt(1), + assertEquals(mListGridLayout.getSublist(1), mListGridLayout.getParentView(5, true, true)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(6, true, true)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(7, true, true)); - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(8, true, true)); // above valid range - assertEquals(mListGridLayout.getChildAt(0), + assertEquals(mListGridLayout.getSublist(0), mListGridLayout.getParentView(9, true, true)); } @Test public void testRemoveAllItems() { - ViewGroup row1 = (ViewGroup) mListGridLayout.getChildAt(0); - ViewGroup row2 = (ViewGroup) mListGridLayout.getChildAt(1); - ViewGroup row3 = (ViewGroup) mListGridLayout.getChildAt(2); + ViewGroup row1 = mListGridLayout.getSublist(0); + row1.setVisibility(View.VISIBLE); + ViewGroup row2 = mListGridLayout.getSublist(1); + row2.setVisibility(View.VISIBLE); + ViewGroup row3 = mListGridLayout.getSublist(2); + row3.setVisibility(View.VISIBLE); View item1 = new View(mContext, null); View item2 = new View(mContext, null); View item3 = new View(mContext, null); @@ -265,7 +268,66 @@ public class ListGridLayoutTest extends SysuiTestCase { mListGridLayout.removeAllItems(); assertEquals(0, row1.getChildCount()); + assertEquals(View.GONE, row1.getVisibility()); assertEquals(0, row2.getChildCount()); - assertEquals(0, row2.getChildCount()); + assertEquals(View.GONE, row2.getVisibility()); + assertEquals(0, row3.getChildCount()); + assertEquals(View.GONE, row3.getVisibility()); + } + + @Test + public void testAddItem() { + mListGridLayout.setExpectedCount(4); + + View item1 = new View(mContext, null); + View item2 = new View(mContext, null); + View item3 = new View(mContext, null); + View item4 = new View(mContext, null); + + mListGridLayout.addItem(item1); + mListGridLayout.addItem(item2); + mListGridLayout.addItem(item3); + mListGridLayout.addItem(item4); + assertEquals(2, mListGridLayout.getSublist(0).getChildCount()); + assertEquals(2, mListGridLayout.getSublist(1).getChildCount()); + assertEquals(0, mListGridLayout.getSublist(2).getChildCount()); + + mListGridLayout.removeAllItems(); + mListGridLayout.addItem(item1); + + assertEquals(1, mListGridLayout.getSublist(0).getChildCount()); + assertEquals(0, mListGridLayout.getSublist(1).getChildCount()); + assertEquals(0, mListGridLayout.getSublist(2).getChildCount()); + } + + @Test + public void testAddItem_reverseItems() { + mListGridLayout.setExpectedCount(3); + + View item1 = new View(mContext, null); + View item2 = new View(mContext, null); + View item3 = new View(mContext, null); + + mListGridLayout.addItem(item1); + mListGridLayout.addItem(item2); + mListGridLayout.addItem(item3); + + ViewGroup sublist = mListGridLayout.getSublist(0); + + assertEquals(item1, sublist.getChildAt(0)); + assertEquals(item2, sublist.getChildAt(1)); + assertEquals(item3, sublist.getChildAt(2)); + + + mListGridLayout.removeAllItems(); + mListGridLayout.setReverseItems(true); + + mListGridLayout.addItem(item1); + mListGridLayout.addItem(item2); + mListGridLayout.addItem(item3); + + assertEquals(item3, sublist.getChildAt(0)); + assertEquals(item2, sublist.getChildAt(1)); + assertEquals(item1, sublist.getChildAt(2)); } }