Merge "Using DiffUtil for calculating widget diff instead of a custom implementation" into tm-qpr-dev
diff --git a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
index 7f24905..5b1da5b 100644
--- a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
@@ -33,9 +33,4 @@
Collections.EMPTY_LIST);
mPkgItem.title = "";
}
-
- @Override
- public int getRank() {
- return RANK_WIDGETS_TOP_SPACE;
- }
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index f09f4c6..0003b76 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -16,16 +16,11 @@
package com.android.launcher3.widget.model;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import androidx.annotation.IntDef;
-
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.widget.WidgetItemComparator;
-import java.lang.annotation.Retention;
import java.util.List;
import java.util.stream.Collectors;
@@ -48,23 +43,4 @@
this.mWidgets =
items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
}
-
- /**
- * Returns the ranking of this entry in the
- * {@link com.android.launcher3.widget.picker.WidgetsListAdapter}.
- *
- * <p>Entries with smaller value should be shown first. See
- * {@link com.android.launcher3.widget.picker.WidgetsDiffReporter} for more details.
- */
- @Rank
- public abstract int getRank();
-
- @Retention(SOURCE)
- @IntDef({RANK_WIDGETS_TOP_SPACE, RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_CONTENT})
- public @interface Rank {
- }
-
- public static final int RANK_WIDGETS_TOP_SPACE = 1;
- public static final int RANK_WIDGETS_LIST_HEADER = 2;
- public static final int RANK_WIDGETS_LIST_CONTENT = 3;
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
index 73b17f1..626e0b9 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -61,12 +61,6 @@
+ mMaxSpanSizeInCells;
}
- @Override
- @Rank
- public int getRank() {
- return RANK_WIDGETS_LIST_CONTENT;
- }
-
/**
* Returns a copy of this {@link WidgetsListContentEntry} with updated
* {@param maxSpanSizeInCells}.
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
index bb0cf92..68f18ae 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
@@ -85,12 +85,6 @@
return "Header:" + mPkgItem.packageName + ":" + mWidgets.size();
}
- @Override
- @Rank
- public int getRank() {
- return RANK_WIDGETS_LIST_HEADER;
- }
-
public boolean isSearchEntry() {
return mIsSearchEntry;
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsDiffCallback.java b/src/com/android/launcher3/widget/picker/WidgetsDiffCallback.java
new file mode 100644
index 0000000..e610ea9
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsDiffCallback.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.launcher3.widget.picker;
+
+import androidx.recyclerview.widget.DiffUtil.Callback;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+
+/**
+ * DiffUtil callback to compare widgets
+ */
+public class WidgetsDiffCallback extends Callback {
+
+ private final List<WidgetsListBaseEntry> mOldEntries;
+ private final List<WidgetsListBaseEntry> mNewEntries;
+
+ public WidgetsDiffCallback(
+ List<WidgetsListBaseEntry> oldEntries,
+ List<WidgetsListBaseEntry> newEntries) {
+ mOldEntries = oldEntries;
+ mNewEntries = newEntries;
+ }
+
+ @Override
+ public int getOldListSize() {
+ return mOldEntries.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return mNewEntries.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ // Items are same if they point to the same package entry
+ WidgetsListBaseEntry oldItem = mOldEntries.get(oldItemPosition);
+ WidgetsListBaseEntry newItem = mNewEntries.get(newItemPosition);
+ return oldItem.getClass().equals(newItem.getClass())
+ && oldItem.mPkgItem.equals(newItem.mPkgItem);
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ // Always update all entries since the icon may have changed
+ return false;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
deleted file mode 100644
index d09fe84..0000000
--- a/src/com/android/launcher3/widget/picker/WidgetsDiffReporter.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2017 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.launcher3.widget.picker;
-
-import android.util.Log;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter}
- * methods accordingly.
- */
-public class WidgetsDiffReporter {
- private static final boolean DEBUG = false;
- private static final String TAG = "WidgetsDiffReporter";
-
- private final IconCache mIconCache;
- private final RecyclerView.Adapter mListener;
-
- public WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener) {
- mIconCache = iconCache;
- mListener = listener;
- }
-
- /**
- * Notifies the difference between {@code currentEntries} & {@code newEntries} by calling the
- * relevant {@link androidx.recyclerview.widget.RecyclerView.RecyclerViewDataObserver} methods.
- */
- public void process(ArrayList<WidgetsListBaseEntry> currentEntries,
- List<WidgetsListBaseEntry> newEntries,
- WidgetListBaseRowEntryComparator comparator) {
- if (DEBUG) {
- Log.d(TAG, "process oldEntries#=" + currentEntries.size()
- + " newEntries#=" + newEntries.size());
- }
- // Early exit if either of the list is empty
- if (currentEntries.isEmpty() || newEntries.isEmpty()) {
- // Skip if both list are empty.
- // On rotation, we open the widget tray with empty. Then try to fetch the list again
- // when the animation completes (which still gives empty). And we get the final result
- // when the bind actually completes.
- if (currentEntries.size() != newEntries.size()) {
- currentEntries.clear();
- currentEntries.addAll(newEntries);
- mListener.notifyDataSetChanged();
- }
- return;
- }
- ArrayList<WidgetsListBaseEntry> orgEntries =
- (ArrayList<WidgetsListBaseEntry>) currentEntries.clone();
- Iterator<WidgetsListBaseEntry> orgIter = orgEntries.iterator();
- Iterator<WidgetsListBaseEntry> newIter = newEntries.iterator();
-
- WidgetsListBaseEntry orgRowEntry = orgIter.next();
- WidgetsListBaseEntry newRowEntry = newIter.next();
-
- do {
- int diff = compareAppNameAndType(orgRowEntry, newRowEntry, comparator);
- if (DEBUG) {
- Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)",
- diff, orgRowEntry != null ? orgRowEntry.toString() : null,
- newRowEntry != null ? newRowEntry.toString() : null));
- }
- int index = -1;
- if (diff < 0) {
- index = currentEntries.indexOf(orgRowEntry);
- mListener.notifyItemRemoved(index);
- if (DEBUG) {
- Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index,
- orgRowEntry.mTitleSectionName));
- }
- currentEntries.remove(index);
- orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
- } else if (diff > 0) {
- index = orgRowEntry != null ? currentEntries.indexOf(orgRowEntry)
- : currentEntries.size();
- currentEntries.add(index, newRowEntry);
- if (DEBUG) {
- Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index,
- newRowEntry.mTitleSectionName));
- }
- newRowEntry = newIter.hasNext() ? newIter.next() : null;
- mListener.notifyItemInserted(index);
-
- } else {
- // same app name & type but,
- // did the icon, title, etc, change?
- // or did the header view changed due to user interactions?
- // or did the widget size and desc, span, etc change?
- if (!isSamePackageItemInfo(orgRowEntry.mPkgItem, newRowEntry.mPkgItem)
- || hasHeaderUpdated(orgRowEntry, newRowEntry)
- || hasWidgetsListContentChanged(orgRowEntry, newRowEntry)) {
- index = currentEntries.indexOf(orgRowEntry);
- currentEntries.set(index, newRowEntry);
- mListener.notifyItemChanged(index);
- if (DEBUG) {
- Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index,
- newRowEntry.mTitleSectionName));
- }
- }
- orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
- newRowEntry = newIter.hasNext() ? newIter.next() : null;
- }
- } while(orgRowEntry != null || newRowEntry != null);
- }
-
- /**
- * Compares the app name and then entry type for the given {@link WidgetsListBaseEntry}s.
- *
- * @Return 0 if both entries' order is the same. Negative integer if {@code newRowEntry} should
- * order before {@code orgRowEntry}. Positive integer if {@code orgRowEntry} should
- * order before {@code newRowEntry}.
- */
- private int compareAppNameAndType(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow,
- WidgetListBaseRowEntryComparator comparator) {
- if (curRow == null && newRow == null) {
- throw new IllegalStateException(
- "Cannot compare PackageItemInfo if both rows are null.");
- }
-
- if (curRow == null && newRow != null) {
- return 1; // new row needs to be inserted
- } else if (curRow != null && newRow == null) {
- return -1; // old row needs to be deleted
- }
- int diff = comparator.compare(curRow, newRow);
- if (diff == 0) {
- return newRow.getRank() - curRow.getRank();
- }
- return diff;
- }
-
- /**
- * Returns {@code true} if both {@code curRow} & {@code newRow} are
- * {@link WidgetsListContentEntry}s with a different list or arrangement of widgets.
- */
- private boolean hasWidgetsListContentChanged(WidgetsListBaseEntry curRow,
- WidgetsListBaseEntry newRow) {
- if (!(curRow instanceof WidgetsListContentEntry)
- || !(newRow instanceof WidgetsListContentEntry)) {
- return false;
- }
- return !curRow.equals(newRow);
- }
-
- /**
- * Returns {@code true} if {@code newRow} is {@link WidgetsListHeaderEntry} and its content has
- * been changed due to user interactions.
- */
- private boolean hasHeaderUpdated(WidgetsListBaseEntry curRow, WidgetsListBaseEntry newRow) {
- if (newRow instanceof WidgetsListHeaderEntry && curRow instanceof WidgetsListHeaderEntry) {
- // Always refresh search header entries to reset rounded corners in their view holder.
- return !curRow.equals(newRow) || ((WidgetsListHeaderEntry) curRow).isSearchEntry();
- }
- return false;
- }
-
- private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
- return curInfo.bitmap.icon.equals(newInfo.bitmap.icon)
- && !mIconCache.isDefaultIcon(curInfo.bitmap, curInfo.user);
- }
-}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 66e3a43..545e661 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -59,7 +59,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
@@ -961,8 +960,6 @@
AdapterHolder(int adapterType) {
mAdapterType = adapterType;
Context context = getContext();
- LauncherAppState apps = LauncherAppState.getInstance(context);
-
HeaderChangeListener headerChangeListener = new HeaderChangeListener() {
@Override
public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
@@ -993,7 +990,6 @@
mWidgetsListAdapter = new WidgetsListAdapter(
context,
LayoutInflater.from(context),
- apps.getIconCache(),
this::getEmptySpaceHeight,
/* iconClickListener= */ WidgetsFullSheet.this,
/* iconLongClickListener= */ WidgetsFullSheet.this,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index b5ff719..20b1d9b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -32,13 +32,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.DiffUtil.DiffResult;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.launcher3.R;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.util.LabelComparator;
@@ -82,7 +83,6 @@
public static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
private final Context mContext;
- private final WidgetsDiffReporter mDiffReporter;
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
private final WidgetListBaseRowEntryComparator mRowComparator =
new WidgetListBaseRowEntryComparator();
@@ -102,12 +102,11 @@
private int mMaxSpanSize = 4;
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
- IconCache iconCache, IntSupplier emptySpaceHeightProvider,
- OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
+ IntSupplier emptySpaceHeightProvider, OnClickListener iconClickListener,
+ OnLongClickListener iconLongClickListener,
WidgetsFullSheet.HeaderChangeListener headerChangeListener) {
mHeaderChangeListener = headerChangeListener;
mContext = context;
- mDiffReporter = new WidgetsDiffReporter(iconCache, this);
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_LIST,
@@ -205,7 +204,11 @@
})
.collect(Collectors.toList());
- mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
+ DiffResult diffResult = DiffUtil.calculateDiff(
+ new WidgetsDiffCallback(mVisibleEntries, newVisibleEntries), false);
+ mVisibleEntries.clear();
+ mVisibleEntries.addAll(newVisibleEntries);
+ diffResult.dispatchUpdatesTo(this);
if (mPendingClickHeader != null) {
// Get the position for the clicked header after adjusting the visible entries. The
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index f490333..1b743e8 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -40,7 +40,6 @@
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsDiffReporter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -73,8 +72,7 @@
/**
* Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row
* are sorted (based on label and user), but the overall list of
- * {@link WidgetsListBaseEntry}s is not sorted. This list is sorted at the UI when using
- * {@link WidgetsDiffReporter}
+ * {@link WidgetsListBaseEntry}s is not sorted.
*
* @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
*/
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
deleted file mode 100644
index 8c87957..0000000
--- a/tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2021 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.launcher3.widget.picker;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
-import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.os.UserHandle;
-
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import com.android.launcher3.widget.picker.WidgetsListAdapter.WidgetListBaseRowEntryComparator;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class WidgetsDiffReporterTest {
- private static final String TEST_PACKAGE_PREFIX = "com.android.test";
- private static final WidgetListBaseRowEntryComparator COMPARATOR =
- new WidgetListBaseRowEntryComparator();
-
- @Mock private IconCache mIconCache;
- @Mock private RecyclerView.Adapter mAdapter;
-
- private InvariantDeviceProfile mTestProfile;
- private WidgetsDiffReporter mWidgetsDiffReporter;
- private Context mContext;
- private WidgetsListHeaderEntry mHeaderA;
- private WidgetsListHeaderEntry mHeaderB;
- private WidgetsListHeaderEntry mHeaderC;
- private WidgetsListHeaderEntry mHeaderD;
- private WidgetsListHeaderEntry mHeaderE;
- private WidgetsListContentEntry mContentC;
- private WidgetsListContentEntry mContentE;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mTestProfile = new InvariantDeviceProfile();
- mTestProfile.numRows = 5;
- mTestProfile.numColumns = 5;
-
- doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
- .getComponent().getPackageName())
- .when(mIconCache).getTitleNoCache(any());
-
- mContext = getApplicationContext();
- mWidgetsDiffReporter = new WidgetsDiffReporter(mIconCache, mAdapter);
- mHeaderA = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "A",
- /* appName= */ "A", /* numOfWidgets= */ 3);
- mHeaderB = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "B",
- /* appName= */ "B", /* numOfWidgets= */ 3);
- mHeaderC = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "C",
- /* appName= */ "C", /* numOfWidgets= */ 3);
- mContentC = createWidgetsContentEntry(TEST_PACKAGE_PREFIX + "C",
- /* appName= */ "C", /* numOfWidgets= */ 3);
- mHeaderD = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "D",
- /* appName= */ "D", /* numOfWidgets= */ 3);
- mHeaderE = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "E",
- /* appName= */ "E", /* numOfWidgets= */ 3);
- mContentE = createWidgetsContentEntry(TEST_PACKAGE_PREFIX + "E",
- /* appName= */ "E", /* numOfWidgets= */ 3);
- }
-
- @Test
- public void listNotChanged_shouldNotInvokeAnyCallbacks() {
- // GIVEN the current list has app headers [A, B, C].
- ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
- List.of(mHeaderA, mHeaderB, mHeaderC));
-
- // WHEN computing the list difference.
- mWidgetsDiffReporter.process(currentList, currentList, COMPARATOR);
-
- // THEN there is no adaptor callback.
- verifyZeroInteractions(mAdapter);
- // THEN the current list contains the same entries.
- assertThat(currentList).containsExactly(mHeaderA, mHeaderB, mHeaderC);
- }
-
- @Test
- public void headersOnly_emptyListToNonEmpty_shouldInvokeNotifyDataSetChanged() {
- // GIVEN the current list has app headers [A, B, C].
- ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>();
-
- List<WidgetsListBaseEntry> newList = List.of(
- createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "A", "A", 3),
- createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "B", "B", 3),
- createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "C", "C", 3));
-
- // WHEN computing the list difference.
- mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
-
- // THEN notifyDataSetChanged is called
- verify(mAdapter).notifyDataSetChanged();
- // THEN the current list contains all elements from the new list.
- assertThat(currentList).containsExactlyElementsIn(newList);
- }
-
- @Test
- public void headersOnly_nonEmptyToEmptyList_shouldInvokeNotifyDataSetChanged() {
- // GIVEN the current list has app headers [A, B, C].
- ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
- List.of(mHeaderA, mHeaderB, mHeaderC));
- // GIVEN the new list is empty.
- List<WidgetsListBaseEntry> newList = List.of();
-
- // WHEN computing the list difference.
- mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
-
- // THEN notifyDataSetChanged is called.
- verify(mAdapter).notifyDataSetChanged();
- // THEN the current list isEmpty.
- assertThat(currentList).isEmpty();
- }
-
- @Test
- public void headersOnly_itemAddedAndRemovedInTheNewList_shouldInvokeCorrectCallbacks() {
- // GIVEN the current list has app headers [A, B, D].
- ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
- List.of(mHeaderA, mHeaderB, mHeaderD));
- // GIVEN the new list has app headers [A, C, E].
- List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mHeaderC, mHeaderE);
-
- // WHEN computing the list difference.
- mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
-
- // THEN "B" is removed from position 1.
- verify(mAdapter).notifyItemRemoved(/* position= */ 1);
- // THEN "D" is removed from position 2.
- verify(mAdapter).notifyItemRemoved(/* position= */ 2);
- // THEN "C" is inserted at position 1.
- verify(mAdapter).notifyItemInserted(/* position= */ 1);
- // THEN "E" is inserted at position 2.
- verify(mAdapter).notifyItemInserted(/* position= */ 2);
- // THEN the current list contains all elements from the new list.
- assertThat(currentList).containsExactlyElementsIn(newList);
- }
-
- @Test
- public void headersContentsMix_itemAddedAndRemovedInTheNewList_shouldInvokeCorrectCallbacks() {
- // GIVEN the current list has app headers [A, B, E content].
- ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
- List.of(mHeaderA, mHeaderB, mContentE));
- // GIVEN the new list has app headers [A, C content, D].
- List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mContentC, mHeaderD);
-
- // WHEN computing the list difference.
- mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
-
- // THEN "B" is removed from position 1.
- verify(mAdapter).notifyItemRemoved(/* position= */ 1);
- // THEN "C content" is inserted at position 1.
- verify(mAdapter).notifyItemInserted(/* position= */ 1);
- // THEN "D" is inserted at position 2.
- verify(mAdapter).notifyItemInserted(/* position= */ 2);
- // THEN "E content" is removed from position 3.
- verify(mAdapter).notifyItemRemoved(/* position= */ 3);
- // THEN the current list contains all elements from the new list.
- assertThat(currentList).containsExactlyElementsIn(newList);
- }
-
- @Test
- public void headersContentsMix_userInteractWithHeader_shouldInvokeCorrectCallbacks() {
- // GIVEN the current list has app headers [A, B, E content].
- ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
- List.of(mHeaderA, mHeaderB, mContentE));
- // GIVEN the new list has app headers [A, B, E content] and the user has interacted with B.
- List<WidgetsListBaseEntry> newList =
- List.of(mHeaderA, mHeaderB.withWidgetListShown(), mContentE);
-
- // WHEN computing the list difference.
- mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
-
- // THEN notify "B" has been changed.
- verify(mAdapter).notifyItemChanged(/* position= */ 1);
- // THEN the current list contains all elements from the new list.
- assertThat(currentList).containsExactlyElementsIn(newList);
- }
-
- @Test
- public void headersContentsMix_headerWidgetsModified_shouldInvokeCorrectCallbacks() {
- // GIVEN the current list has app headers [A, B, E content].
- ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
- List.of(mHeaderA, mHeaderB, mContentE));
- // GIVEN the new list has one of the headers widgets list modified.
- List<WidgetsListBaseEntry> newList = List.of(
- WidgetsListHeaderEntry.create(
- mHeaderA.mPkgItem, mHeaderA.mTitleSectionName,
- mHeaderA.mWidgets.subList(0, 1)),
- mHeaderB, mContentE);
-
- // WHEN computing the list difference.
- mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
-
- // THEN notify "A" has been changed.
- verify(mAdapter).notifyItemChanged(/* position= */ 0);
- // THEN the current list contains all elements from the new list.
- assertThat(currentList).containsExactlyElementsIn(newList);
- }
-
- @Test
- public void headersContentsMix_contentMaxSpanSizeModified_shouldInvokeCorrectCallbacks() {
- // GIVEN the current list has app headers [A, B, E content].
- ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
- List.of(mHeaderA, mHeaderB, mContentE));
- // GIVEN the new list has max span size in "E content" modified.
- List<WidgetsListBaseEntry> newList = List.of(
- mHeaderA,
- mHeaderB,
- new WidgetsListContentEntry(
- mContentE.mPkgItem,
- mContentE.mTitleSectionName,
- mContentE.mWidgets,
- mContentE.getMaxSpanSizeInCells() + 1));
-
- // WHEN computing the list difference.
- mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
-
- // THEN notify "E content" has been changed.
- verify(mAdapter).notifyItemChanged(/* position= */ 2);
- // THEN the current list contains all elements from the new list.
- assertThat(currentList).containsExactlyElementsIn(newList);
- }
-
-
- private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
- int numOfWidgets) {
- List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
- PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
- widgetItems.get(0).user);
-
- return WidgetsListHeaderEntry.create(pInfo, /* titleSectionName= */ "", widgetItems);
- }
-
- private WidgetsListContentEntry createWidgetsContentEntry(String packageName, String appName,
- int numOfWidgets) {
- List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
- PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
- widgetItems.get(0).user);
-
- return new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems);
- }
-
- private PackageItemInfo createPackageItemInfo(String packageName, String appName,
- UserHandle userHandle) {
- PackageItemInfo pInfo = new PackageItemInfo(packageName, userHandle);
- pInfo.title = appName;
- pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
- return pInfo;
- }
-
- private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
- ArrayList<WidgetItem> widgetItems = new ArrayList<>();
- for (int i = 0; i < numOfWidgets; i++) {
- ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
- AppWidgetProviderInfo widgetInfo = createAppWidgetProviderInfo(cn);
-
- WidgetItem widgetItem = new WidgetItem(
- LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
- mTestProfile, mIconCache);
- widgetItems.add(widgetItem);
- }
- return widgetItems;
- }
-}
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
deleted file mode 100644
index 0044d04..0000000
--- a/tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2017 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.launcher3.widget.picker;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.verify;
-
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.os.Process;
-import android.os.UserHandle;
-import android.view.LayoutInflater;
-
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.util.ActivityContextWrapper;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.WidgetUtils;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Unit tests for WidgetsListAdapter
- * Note that all indices matching are shifted by 1 to account for the empty space at the start.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class WidgetsListAdapterTest {
- private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test";
-
- @Mock private LayoutInflater mMockLayoutInflater;
- @Mock private RecyclerView.AdapterDataObserver mListener;
- @Mock private IconCache mIconCache;
-
- private WidgetsListAdapter mAdapter;
- private InvariantDeviceProfile mTestProfile;
- private UserHandle mUserHandle;
- private Context mContext;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mContext = new ActivityContextWrapper(getApplicationContext());
- mTestProfile = new InvariantDeviceProfile();
- mTestProfile.numRows = 5;
- mTestProfile.numColumns = 5;
- mUserHandle = Process.myUserHandle();
- mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater,
- mIconCache, () -> 0, null, null, null);
- mAdapter.registerAdapterDataObserver(mListener);
-
- doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
- .getComponent().getPackageName())
- .when(mIconCache).getTitleNoCache(any());
- }
-
- @Test
- public void setWidgets_shouldNotifyDataSetChanged() {
- mAdapter.setWidgets(generateSampleMap(1));
-
- verify(mListener).onChanged();
- }
-
- @Test
- public void setWidgets_withItemInserted_shouldNotifyItemInserted() {
- mAdapter.setWidgets(generateSampleMap(1));
- mAdapter.setWidgets(generateSampleMap(2));
-
- verify(mListener).onItemRangeInserted(eq(2), eq(1));
- }
-
- @Test
- public void setWidgets_withItemRemoved_shouldNotifyItemRemoved() {
- mAdapter.setWidgets(generateSampleMap(2));
- mAdapter.setWidgets(generateSampleMap(1));
-
- verify(mListener).onItemRangeRemoved(eq(2), eq(1));
- }
-
- @Test
- public void setWidgets_appIconChanged_shouldNotifyItemChanged() {
- mAdapter.setWidgets(generateSampleMap(1));
- mAdapter.setWidgets(generateSampleMap(1));
-
- verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
- }
-
- @Test
- public void headerClick_expanded_shouldNotifyItemChange() {
- // GIVEN a list of widgets entries:
- // [com.google.test0, com.google.test0 content,
- // com.google.test1, com.google.test1 content,
- // com.google.test2, com.google.test2 content]
- // The visible widgets entries: [com.google.test0, com.google.test1, com.google.test2].
- mAdapter.setWidgets(generateSampleMap(3));
-
- // WHEN com.google.test.1 header is expanded.
- mAdapter.onHeaderClicked(/* showWidgets= */ true,
- new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
-
- // THEN the visible entries list becomes:
- // [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
- // com.google.test.1 content is inserted into position 2.
- verify(mListener).onItemRangeInserted(eq(3), eq(1));
- }
-
- @Test
- public void setWidgets_expandedApp_moreWidgets_shouldNotifyItemChangedWithWidgetItemInfoDiff() {
- // GIVEN the adapter was first populated with com.google.test0 & com.google.test1. Each app
- // has one widget.
- ArrayList<WidgetsListBaseEntry> allEntries = generateSampleMap(2);
- mAdapter.setWidgets(allEntries);
- // GIVEN test com.google.test1 is expanded.
- // Visible entries in the adapter are:
- // [com.google.test0, com.google.test1, com.google.test1 content]
- mAdapter.onHeaderClicked(/* showWidgets= */ true,
- new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
- Mockito.reset(mListener);
-
- // WHEN the adapter is updated with the same list of apps but com.google.test1 has 2 widgets
- // now.
- WidgetsListContentEntry testPackage1ContentEntry =
- (WidgetsListContentEntry) allEntries.get(3);
- WidgetItem widgetItem = testPackage1ContentEntry.mWidgets.get(0);
- WidgetsListContentEntry newTestPackage1ContentEntry = new WidgetsListContentEntry(
- testPackage1ContentEntry.mPkgItem,
- testPackage1ContentEntry.mTitleSectionName, List.of(widgetItem, widgetItem));
- allEntries.set(3, newTestPackage1ContentEntry);
- mAdapter.setWidgets(allEntries);
-
- // THEN the onItemRangeChanged is invoked for "com.google.test1 content" at index 2.
- verify(mListener).onItemRangeChanged(eq(3), eq(1), isNull());
- }
-
- @Test
- public void setWidgets_hodgepodge_shouldInvokeExpectedDataObserverCallbacks() {
- // GIVEN a widgets entry list:
- // Index: 0| 1 | 2| 3 | 4| 5 | 6| 7 | 8| 9 |
- // [A, A content, B, B content, C, C content, D, D content, E, E content]
- List<WidgetsListBaseEntry> allAppsWithWidgets = generateSampleMap(5);
- // GIVEN the current widgets list consist of [A, A content, B, B content, E, E content].
- // GIVEN the visible widgets list consist of [A, B, E]
- List<WidgetsListBaseEntry> currentList = List.of(
- // A & A content
- allAppsWithWidgets.get(0), allAppsWithWidgets.get(1),
- // B & B content
- allAppsWithWidgets.get(2), allAppsWithWidgets.get(3),
- // E & E content
- allAppsWithWidgets.get(8), allAppsWithWidgets.get(9));
- mAdapter.setWidgets(currentList);
-
- // WHEN the widgets list is updated to [A, A content, C, C content, D, D content].
- // WHEN the visible widgets list is updated to [A, C, D].
- List<WidgetsListBaseEntry> newList = List.of(
- // A & A content
- allAppsWithWidgets.get(0), allAppsWithWidgets.get(1),
- // C & C content
- allAppsWithWidgets.get(4), allAppsWithWidgets.get(5),
- // D & D content
- allAppsWithWidgets.get(6), allAppsWithWidgets.get(7));
- mAdapter.setWidgets(newList);
-
- // Account for 1st items as empty space
- // Computation logic | [Intermediate list during computation]
- // THEN B <> C < 0, removed B from index 1 | [A, E]
- verify(mListener).onItemRangeRemoved(/* positionStart= */ 2, /* itemCount= */ 1);
- // THEN E <> C > 0, C inserted to index 1 | [A, C, E]
- verify(mListener).onItemRangeInserted(/* positionStart= */ 2, /* itemCount= */ 1);
- // THEN E <> D > 0, D inserted to index 2 | [A, C, D, E]
- verify(mListener).onItemRangeInserted(/* positionStart= */ 3, /* itemCount= */ 1);
- // THEN E <> null = -1, E deleted from index 3 | [A, C, D]
- verify(mListener).onItemRangeRemoved(/* positionStart= */ 4, /* itemCount= */ 1);
- }
-
- @Test
- public void setWidgetsOnSearch_expandedApp_shouldResetExpandedApp() {
- // GIVEN a list of widgets entries:
- // [Empty item
- // com.google.test0,
- // com.google.test0 content,
- // com.google.test1,
- // com.google.test1 content,
- // com.google.test2,
- // com.google.test2 content]
- // The visible widgets entries:
- // [Empty item,
- // com.google.test0,
- // com.google.test1,
- // com.google.test2].
- ArrayList<WidgetsListBaseEntry> allEntries = generateSampleMap(3);
- mAdapter.setWidgetsOnSearch(allEntries);
- // GIVEN com.google.test.1 header is expanded. The visible entries list becomes:
- // [Empty item, com.google.test0, com.google.test1, com.google.test1 content,
- // com.google.test2]
- mAdapter.onHeaderClicked(/* showWidgets= */ true,
- new PackageUserKey(TEST_PACKAGE_PLACEHOLDER + 1, mUserHandle));
- Mockito.reset(mListener);
-
- // WHEN same widget entries are set again.
- mAdapter.setWidgetsOnSearch(allEntries);
-
- // THEN expanded app is reset and the visible entries list becomes:
- // [Empty item, com.google.test0, com.google.test1, com.google.test2]
- verify(mListener).onItemRangeChanged(eq(2), eq(1), isNull());
- verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
- }
-
- /**
- * Generates a list of sample widget entries.
- *
- * <p>Each sample app has 1 widget only. An app is represented by 2 entries,
- * {@link WidgetsListHeaderEntry} & {@link WidgetsListContentEntry}. Only
- * {@link WidgetsListHeaderEntry} is always visible in the {@link WidgetsListAdapter}.
- * {@link WidgetsListContentEntry} is only shown upon clicking the corresponding app's
- * {@link WidgetsListHeaderEntry}. Only at most one {@link WidgetsListContentEntry} is shown at
- * a time.
- *
- * @param num the number of apps that have widgets.
- */
- private ArrayList<WidgetsListBaseEntry> generateSampleMap(int num) {
- ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
- if (num <= 0) return result;
-
- for (int i = 0; i < num; i++) {
- String packageName = TEST_PACKAGE_PLACEHOLDER + i;
-
- List<WidgetItem> widgetItems = generateWidgetItems(packageName, /* numOfWidgets= */ 1);
-
- PackageItemInfo pInfo = new PackageItemInfo(packageName, widgetItems.get(0).user);
- pInfo.title = pInfo.packageName;
- pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
-
- result.add(WidgetsListHeaderEntry.create(
- pInfo, /* titleSectionName= */ "", widgetItems));
- result.add(new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems));
- }
-
- return result;
- }
-
- private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
- ArrayList<WidgetItem> widgetItems = new ArrayList<>();
- for (int i = 0; i < numOfWidgets; i++) {
- ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
- AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
-
- widgetItems.add(new WidgetItem(
- LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
- mTestProfile, mIconCache));
- }
- return widgetItems;
- }
-}