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
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 0000000..99a4e13
--- /dev/null
+++ b/packages/SystemUI/res/layout-land/global_actions_column.xml
@@ -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>
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 0000000..0f86131
--- /dev/null
+++ b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml
@@ -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>
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 0000000..b58146b
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_column.xml
@@ -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>
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index 802903d..ad2e002 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -80,16 +80,6 @@
}
@Override
- public void removeAllItems() {
- if (mList != null) {
- mList.removeAllViews();
- }
- if (mSeparatedView != null) {
- mSeparatedView.removeAllViews();
- }
- }
-
- @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateSettings();
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index f8287a4..d153fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -45,11 +45,6 @@
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.
*/
public abstract void setDivisionView(View v);
@@ -110,6 +105,25 @@
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 0000000..5907028
--- /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 9b69dc5..2b006ce 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 @@
}
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;
}
- return com.android.systemui.R.layout.global_actions_grid;
+ } 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_wrapped;
}
@Override
@@ -1711,7 +1718,7 @@
}
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 554ed73..e1462d1 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 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 @@
@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 @@
* 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 @@
}
@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 @@
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 @@
|| 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 0000000..f755a93
--- /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 0000000..16d665c
--- /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 3c52e9d..a396f3e 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 @@
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 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 @@
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 @@
@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 @@
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 @@
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 0000000..16dcd65
--- /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<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));
+ }
+}