Merge "Implementing support for item diffing instead of creating out the complete UI on every update" into tm-dev
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 9369bdc..b6d3fc5 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -23,7 +23,6 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
-import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -197,13 +196,6 @@
if (mScrollbar != null) {
mScrollbar.reattachThumbToScroll();
}
- if (getLayoutManager() instanceof LinearLayoutManager) {
- LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
- if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
- // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
- return;
- }
- }
scrollToPosition(0);
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index e279f59..47f2dd5 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -29,11 +29,13 @@
import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.AppLauncher;
+import java.util.ArrayList;
import java.util.Objects;
/**
@@ -95,11 +97,15 @@
mHeader.reset(false);
}
- /** Invoke when the search results change. */
- public void onSearchResultsChanged() {
- for (int i = 0; i < mAH.size(); i++) {
- if (mAH.get(i).mRecyclerView != null) {
- mAH.get(i).mRecyclerView.onSearchResultsChanged();
+ /**
+ * Sets results list for search
+ */
+ public void setSearchResults(ArrayList<AdapterItem> results) {
+ if (getApps().setSearchResults(results)) {
+ for (int i = 0; i < mAH.size(); i++) {
+ if (mAH.get(i).mRecyclerView != null) {
+ mAH.get(i).mRecyclerView.onSearchResultsChanged();
+ }
}
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index f97eb28..7067fa2 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -37,10 +37,10 @@
* Smooth scrolls the recycler view to the given section.
*/
public void smoothScrollToSection(FastScrollSectionInfo info) {
- if (mTargetFastScrollPosition == info.fastScrollToItem.position) {
+ if (mTargetFastScrollPosition == info.position) {
return;
}
- mTargetFastScrollPosition = info.fastScrollToItem.position;
+ mTargetFastScrollPosition = info.position;
mRv.getLayoutManager().startSmoothScroll(new MyScroller(mTargetFastScrollPosition));
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 7dbe711..c4e977b 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -71,6 +71,26 @@
public void onChanged() {
mCachedScrollPositions.clear();
}
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ onChanged();
+ }
};
// The empty-search result background
@@ -241,17 +261,14 @@
// Find the fastscroll section that maps to this touch fraction
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
mApps.getFastScrollerSections();
- AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
- for (int i = 1; i < fastScrollSections.size(); i++) {
- AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
- if (info.touchFraction > touchFraction) {
- break;
- }
- lastInfo = info;
+ int count = fastScrollSections.size();
+ if (count == 0) {
+ return "";
}
-
- mFastScrollHelper.smoothScrollToSection(lastInfo);
- return lastInfo.sectionName;
+ int index = Utilities.boundToRange((int) (touchFraction * count), 0, count - 1);
+ AlphabeticalAppsList.FastScrollSectionInfo section = fastScrollSections.get(index);
+ mFastScrollHelper.smoothScrollToSection(section);
+ return section.sectionName;
}
@Override
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index cdaf80a..4ccfd39 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,9 +15,14 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_EMPTY_SEARCH;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_SEARCH_MARKET;
import android.content.Context;
+import androidx.recyclerview.widget.DiffUtil;
+
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
@@ -52,14 +57,13 @@
*/
public static class FastScrollSectionInfo {
// The section name
- public String sectionName;
- // The AdapterItem to scroll to for this section
- public AdapterItem fastScrollToItem;
- // The touch fraction that should map to this fast scroll section info
- public float touchFraction;
+ public final String sectionName;
+ // The item position
+ public final int position;
- public FastScrollSectionInfo(String sectionName) {
+ public FastScrollSectionInfo(String sectionName, int position) {
this.sectionName = sectionName;
+ this.position = position;
}
}
@@ -108,13 +112,6 @@
}
/**
- * Returns all the apps.
- */
- public List<AppInfo> getApps() {
- return mApps;
- }
-
- /**
* Returns fast scroller sections of all the current filtered applications.
*/
public List<FastScrollSectionInfo> getFastScrollerSections() {
@@ -235,77 +232,49 @@
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
*/
public void updateAdapterItems() {
- refillAdapterItems();
- refreshRecyclerView();
- }
-
- private void refreshRecyclerView() {
- if (mAdapter != null) {
- mAdapter.notifyDataSetChanged();
- }
- }
-
- private void refillAdapterItems() {
- String lastSectionName = null;
- FastScrollSectionInfo lastFastScrollerSectionInfo = null;
- int position = 0;
-
+ List<AdapterItem> oldItems = new ArrayList<>(mAdapterItems);
// Prepare to update the list of sections, filtered apps, etc.
- mAccessibilityResultsCount = 0;
mFastScrollerSections.clear();
mAdapterItems.clear();
+ mAccessibilityResultsCount = 0;
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// ordered set of sections
-
if (!hasFilter()) {
- mAccessibilityResultsCount = mApps.size();
+ int position = 0;
if (mWorkAdapterProvider != null) {
position += mWorkAdapterProvider.addWorkItems(mAdapterItems);
if (!mWorkAdapterProvider.shouldShowWorkApps()) {
return;
}
}
+ String lastSectionName = null;
for (AppInfo info : mApps) {
- String sectionName = info.sectionName;
+ mAdapterItems.add(AdapterItem.asApp(info));
+ String sectionName = info.sectionName;
// Create a new section if the section names do not match
if (!sectionName.equals(lastSectionName)) {
lastSectionName = sectionName;
- lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
- mFastScrollerSections.add(lastFastScrollerSectionInfo);
+ mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, position));
}
-
- // Create an app item
- AdapterItem appItem = AdapterItem.asApp(position++, info);
- if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
- lastFastScrollerSectionInfo.fastScrollToItem = appItem;
- }
-
- mAdapterItems.add(appItem);
+ position++;
}
} else {
- int count = mSearchResults.size();
- for (int i = 0; i < count; i++) {
- AdapterItem adapterItem = mSearchResults.get(i);
- adapterItem.position = i;
- mAdapterItems.add(adapterItem);
-
- if (adapterItem.isCountedForAccessibility()) {
- mAccessibilityResultsCount++;
- }
- }
+ mAdapterItems.addAll(mSearchResults);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
// Append the search market item
if (hasNoFilteredResults()) {
- mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+ mAdapterItems.add(new AdapterItem(VIEW_TYPE_EMPTY_SEARCH));
} else {
- mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
+ mAdapterItems.add(new AdapterItem(VIEW_TYPE_ALL_APPS_DIVIDER));
}
- mAdapterItems.add(AdapterItem.asMarketSearch(position++));
-
+ mAdapterItems.add(new AdapterItem(VIEW_TYPE_SEARCH_MARKET));
}
}
+ mAccessibilityResultsCount = (int) mAdapterItems.stream()
+ .filter(AdapterItem::isCountedForAccessibility).count();
+
if (mNumAppsPerRowAllApps != 0) {
// Update the number of rows in the adapter after we do all the merging (otherwise, we
// would have to shift the values again)
@@ -328,19 +297,43 @@
}
}
mNumAppRowsInAdapter = rowIndex + 1;
+ }
- // Pre-calculate all the fast scroller fractions
- float perSectionTouchFraction = 1f / mFastScrollerSections.size();
- float cumulativeTouchFraction = 0f;
- for (FastScrollSectionInfo info : mFastScrollerSections) {
- AdapterItem item = info.fastScrollToItem;
- if (!BaseAllAppsAdapter.isIconViewType(item.viewType)) {
- info.touchFraction = 0f;
- continue;
- }
- info.touchFraction = cumulativeTouchFraction;
- cumulativeTouchFraction += perSectionTouchFraction;
- }
+ if (mAdapter != null) {
+ DiffUtil.calculateDiff(new MyDiffCallback(oldItems, mAdapterItems), false)
+ .dispatchUpdatesTo(mAdapter);
}
}
+
+ private static class MyDiffCallback extends DiffUtil.Callback {
+
+ private final List<AdapterItem> mOldList;
+ private final List<AdapterItem> mNewList;
+
+ MyDiffCallback(List<AdapterItem> oldList, List<AdapterItem> newList) {
+ mOldList = oldList;
+ mNewList = newList;
+ }
+
+ @Override
+ public int getOldListSize() {
+ return mOldList.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return mNewList.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ return mOldList.get(oldItemPosition).isSameAs(mNewList.get(newItemPosition));
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ return mOldList.get(oldItemPosition).isContentSame(mNewList.get(newItemPosition));
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 8ac2536..c7c4607 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -88,10 +88,8 @@
*/
public static class AdapterItem {
/** Common properties */
- // The index of this adapter item in the list
- public int position;
// The type of this item
- public int viewType;
+ public final int viewType;
// The row that this item shows up on
public int rowIndex;
@@ -100,50 +98,37 @@
// The associated ItemInfoWithIcon for the item
public AppInfo itemInfo = null;
+ public AdapterItem(int viewType) {
+ this.viewType = viewType;
+ }
+
/**
* Factory method for AppIcon AdapterItem
*/
- public static AdapterItem asApp(int pos, AppInfo appInfo) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_ICON;
- item.position = pos;
+ public static AdapterItem asApp(AppInfo appInfo) {
+ AdapterItem item = new AdapterItem(VIEW_TYPE_ICON);
item.itemInfo = appInfo;
return item;
}
- /**
- * Factory method for empty search results view
- */
- public static AdapterItem asEmptySearch(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_EMPTY_SEARCH;
- item.position = pos;
- return item;
- }
-
- /**
- * Factory method for a dividerView in AllAppsSearch
- */
- public static AdapterItem asAllAppsDivider(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
- item.position = pos;
- return item;
- }
-
- /**
- * Factory method for a market search button
- */
- public static AdapterItem asMarketSearch(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_SEARCH_MARKET;
- item.position = pos;
- return item;
- }
-
protected boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON || viewType == VIEW_TYPE_SEARCH_MARKET;
}
+
+ /**
+ * Returns true if the items represent the same object
+ */
+ public boolean isSameAs(AdapterItem other) {
+ return (other.viewType != viewType) && (other.getClass() == getClass());
+ }
+
+ /**
+ * This is called only if {@link #isSameAs} returns true to check if the contents are same
+ * as well. Returning true will prevent redrawing of thee item.
+ */
+ public boolean isContentSame(AdapterItem other) {
+ return itemInfo == null && other.itemInfo == null;
+ }
}
protected final T mActivityContext;
diff --git a/src/com/android/launcher3/allapps/WorkAdapterProvider.java b/src/com/android/launcher3/allapps/WorkAdapterProvider.java
index ce44958..76d08c8 100644
--- a/src/com/android/launcher3/allapps/WorkAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/WorkAdapterProvider.java
@@ -19,10 +19,10 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Button;
import android.widget.TextView;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.views.ActivityContext;
@@ -107,13 +107,9 @@
public int addWorkItems(ArrayList<AllAppsGridAdapter.AdapterItem> adapterItems) {
if (mState == WorkProfileManager.STATE_DISABLED) {
//add disabled card here.
- AllAppsGridAdapter.AdapterItem disabledCard = new AllAppsGridAdapter.AdapterItem();
- disabledCard.viewType = VIEW_TYPE_WORK_DISABLED_CARD;
- adapterItems.add(disabledCard);
+ adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
} else if (mState == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
- AllAppsGridAdapter.AdapterItem eduCard = new AllAppsGridAdapter.AdapterItem();
- eduCard.viewType = VIEW_TYPE_WORK_EDU_CARD;
- adapterItems.add(eduCard);
+ adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
}
return adapterItems.size();
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index bc2c318..6539c05 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -38,7 +38,6 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.search.SearchCallback;
@@ -57,7 +56,6 @@
private final AllAppsSearchBarController mSearchBarController;
private final SpannableStringBuilder mSearchQueryBuilder;
- private AlphabeticalAppsList<?> mApps;
private ActivityAllAppsContainerView<?> mAppsView;
// The amount of pixels to shift down and overlap with the rest of the content.
@@ -131,7 +129,6 @@
@Override
public void initializeSearch(ActivityAllAppsContainerView<?> appsView) {
- mApps = appsView.getApps();
mAppsView = appsView;
mSearchBarController.initialize(
new DefaultAppSearchAlgorithm(getContext()),
@@ -170,17 +167,14 @@
@Override
public void onSearchResult(String query, ArrayList<AdapterItem> items) {
if (items != null) {
- mApps.setSearchResults(items);
- notifyResultChanged();
+ mAppsView.setSearchResults(items);
mAppsView.setLastSearchQuery(query);
}
}
@Override
public void clearSearchResult() {
- if (mApps.setSearchResults(null)) {
- notifyResultChanged();
- }
+ mAppsView.setSearchResults(null);
// Clear the search query
mSearchQueryBuilder.clear();
@@ -189,10 +183,6 @@
mAppsView.onClearSearchResult();
}
- private void notifyResultChanged() {
- mAppsView.onSearchResultsChanged();
- }
-
@Override
public void setInsets(Rect insets) {
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 33d0082..4eceb71 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -85,8 +85,7 @@
for (int i = 0; i < total && resultCount < MAX_RESULTS_COUNT; i++) {
AppInfo info = apps.get(i);
if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
- AdapterItem appItem = AdapterItem.asApp(resultCount, info);
- result.add(appItem);
+ result.add(AdapterItem.asApp(info));
resultCount++;
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index f780f03..755e4a9 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -239,21 +239,6 @@
mHeaderViewDimensionsProvider = headerViewDimensionsProvider;
}
- @Override
- public void scrollToTop() {
- if (mScrollbar != null) {
- mScrollbar.reattachThumbToScroll();
- }
-
- if (getLayoutManager() instanceof LinearLayoutManager) {
- if (getCurrentScrollY() == 0) {
- // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
- return;
- }
- }
- scrollToPosition(0);
- }
-
/**
* Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
* {@code untilIndex}.
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index f31e4f3..7c1be1d 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -25,11 +25,14 @@
import android.util.Log;
import android.view.View;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsPagedView;
import com.android.launcher3.allapps.WorkAdapterProvider;
import com.android.launcher3.allapps.WorkEduCard;
+import com.android.launcher3.allapps.WorkPausedCard;
import com.android.launcher3.allapps.WorkProfileManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -38,6 +41,7 @@
import org.junit.Test;
import java.util.Objects;
+import java.util.function.Predicate;
public class WorkProfileTest extends AbstractLauncherUiTest {
@@ -130,6 +134,8 @@
return manager.getCurrentState() == WorkProfileManager.STATE_DISABLED;
}, LauncherInstrumentation.WAIT_TIME_MS);
+ waitForWorkCard("Work paused card not shown", view -> view instanceof WorkPausedCard);
+
// start work profile toggle ON test
executeOnLauncher(l -> {
ActivityAllAppsContainerView<?> allApps = l.getAppsView();
@@ -154,9 +160,19 @@
l.getAppsView().getWorkManager().reset();
});
- waitForLauncherCondition("Work profile education not shown",
- l -> l.getAppsView().getActiveRecyclerView()
- .findViewHolderForAdapterPosition(0).itemView instanceof WorkEduCard,
- LauncherInstrumentation.WAIT_TIME_MS);
+ waitForWorkCard("Work profile education not shown", view -> view instanceof WorkEduCard);
+ }
+
+ private void waitForWorkCard(String message, Predicate<View> workCardCheck) {
+ waitForLauncherCondition(message, l -> {
+ l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+ ViewHolder holder = l.getAppsView().getActiveRecyclerView()
+ .findViewHolderForAdapterPosition(0);
+ try {
+ return holder != null && workCardCheck.test(holder.itemView);
+ } finally {
+ l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+ }
+ }, LauncherInstrumentation.WAIT_TIME_MS);
}
}