• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.widget.picker;
17 
18 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_APP_EXPANDED;
19 
20 import android.content.Context;
21 import android.graphics.Rect;
22 import android.os.Process;
23 import android.util.Log;
24 import android.util.Size;
25 import android.util.SparseArray;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.View.OnLongClickListener;
30 import android.view.ViewGroup;
31 import android.widget.TableRow;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 import androidx.recyclerview.widget.LinearLayoutManager;
36 import androidx.recyclerview.widget.RecyclerView;
37 import androidx.recyclerview.widget.RecyclerView.Adapter;
38 import androidx.recyclerview.widget.RecyclerView.LayoutParams;
39 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
40 
41 import com.android.launcher3.BaseActivity;
42 import com.android.launcher3.DeviceProfile;
43 import com.android.launcher3.Launcher;
44 import com.android.launcher3.R;
45 import com.android.launcher3.icons.IconCache;
46 import com.android.launcher3.model.WidgetItem;
47 import com.android.launcher3.model.data.PackageItemInfo;
48 import com.android.launcher3.recyclerview.ViewHolderBinder;
49 import com.android.launcher3.util.LabelComparator;
50 import com.android.launcher3.util.PackageUserKey;
51 import com.android.launcher3.widget.CachingWidgetPreviewLoader;
52 import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
53 import com.android.launcher3.widget.WidgetCell;
54 import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
55 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
56 import com.android.launcher3.widget.model.WidgetsListContentEntry;
57 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
58 import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
59 import com.android.launcher3.widget.util.WidgetSizes;
60 
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Comparator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.OptionalInt;
67 import java.util.function.Predicate;
68 import java.util.stream.Collectors;
69 import java.util.stream.IntStream;
70 
71 /**
72  * Recycler view adapter for the widget tray.
73  *
74  * <p>This adapter supports view binding of subclasses of {@link WidgetsListBaseEntry}. There are 2
75  * subclasses: {@link WidgetsListHeader} & {@link WidgetsListContentEntry}.
76  * {@link WidgetsListHeader} entries are always visible in the recycler view. At most one
77  * {@link WidgetsListContentEntry} is shown in the recycler view at any time. Clicking a
78  * {@link WidgetsListHeader} will result in expanding / collapsing a corresponding
79  * {@link WidgetsListContentEntry} of the same app.
80  */
81 public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderClickListener {
82 
83     private static final String TAG = "WidgetsListAdapter";
84     private static final boolean DEBUG = false;
85 
86     /** Uniquely identifies widgets list view type within the app. */
87     private static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
88     private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
89     private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
90 
91     private final Context mContext;
92     private final Launcher mLauncher;
93     private final CachingWidgetPreviewLoader mCachingPreviewLoader;
94     private final WidgetsDiffReporter mDiffReporter;
95     private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
96     private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
97     private final WidgetListBaseRowEntryComparator mRowComparator =
98             new WidgetListBaseRowEntryComparator();
99 
100     private List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
101     private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
102     @Nullable private PackageUserKey mWidgetsContentVisiblePackageUserKey = null;
103 
104     private Predicate<WidgetsListBaseEntry> mHeaderAndSelectedContentFilter = entry ->
105             entry instanceof WidgetsListHeaderEntry
106                     || entry instanceof WidgetsListSearchHeaderEntry
107                     || new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
108                             .equals(mWidgetsContentVisiblePackageUserKey);
109     @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
110     @Nullable private RecyclerView mRecyclerView;
111     @Nullable private PackageUserKey mPendingClickHeader;
112     private final int mShortcutPreviewPadding;
113     private final int mSpacingBetweenEntries;
114     private int mMaxSpanSize = 4;
115 
116     private final WidgetPreviewLoadedCallback mPreviewLoadedCallback =
117             ignored -> updateVisibleEntries();
118 
WidgetsListAdapter(Context context, LayoutInflater layoutInflater, DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache, OnClickListener iconClickListener, OnLongClickListener iconLongClickListener)119     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
120             DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
121             OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
122         mContext = context;
123         mLauncher = Launcher.getLauncher(context);
124         mCachingPreviewLoader = new CachingWidgetPreviewLoader(widgetPreviewLoader);
125         mDiffReporter = new WidgetsDiffReporter(iconCache, this);
126         WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
127         mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(
128                 layoutInflater, iconClickListener, iconLongClickListener,
129                 mCachingPreviewLoader, listDrawableFactory, /* listAdapter= */ this);
130         mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
131         mViewHolderBinders.put(
132                 VIEW_TYPE_WIDGETS_HEADER,
133                 new WidgetsListHeaderViewHolderBinder(
134                         layoutInflater,
135                         /* onHeaderClickListener= */ this,
136                         listDrawableFactory,
137                         /* listAdapter= */ this));
138         mViewHolderBinders.put(
139                 VIEW_TYPE_WIDGETS_SEARCH_HEADER,
140                 new WidgetsListSearchHeaderViewHolderBinder(
141                         layoutInflater,
142                         /* onHeaderClickListener= */ this,
143                         listDrawableFactory,
144                         /* listAdapter= */ this));
145         mShortcutPreviewPadding =
146                 2 * context.getResources()
147                         .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
148         mSpacingBetweenEntries =
149                 context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
150     }
151 
152     @Override
onAttachedToRecyclerView(@onNull RecyclerView recyclerView)153     public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
154         mRecyclerView = recyclerView;
155 
156         mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
157             @Override
158             public void getItemOffsets(
159                     @NonNull Rect outRect,
160                     @NonNull View view,
161                     @NonNull RecyclerView parent,
162                     @NonNull RecyclerView.State state) {
163                 super.getItemOffsets(outRect, view, parent, state);
164                 int position = ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
165                 boolean isHeader =
166                         view.getTag(R.id.tag_widget_entry) instanceof WidgetsListBaseEntry.Header;
167                 outRect.top += position > 0 && isHeader ? mSpacingBetweenEntries : 0;
168             }
169         });
170     }
171 
172     @Override
onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)173     public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
174         mRecyclerView = null;
175     }
176 
setFilter(Predicate<WidgetsListBaseEntry> filter)177     public void setFilter(Predicate<WidgetsListBaseEntry> filter) {
178         mFilter = filter;
179     }
180 
181     /**
182      * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}.
183      *
184      * @see WidgetCell#setApplyBitmapDeferred(boolean)
185      */
setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv)186     public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
187         mWidgetsListTableViewHolderBinder.setApplyBitmapDeferred(isDeferred);
188 
189         for (int i = rv.getChildCount() - 1; i >= 0; i--) {
190             ViewHolder viewHolder = rv.getChildViewHolder(rv.getChildAt(i));
191             if (viewHolder.getItemViewType() == VIEW_TYPE_WIDGETS_LIST) {
192                 WidgetsRowViewHolder holder = (WidgetsRowViewHolder) viewHolder;
193                 for (int j = holder.mTableContainer.getChildCount() - 1; j >= 0; j--) {
194                     TableRow row =  (TableRow) holder.mTableContainer.getChildAt(j);
195                     for (int k = row.getChildCount() - 1; k >= 0; k--) {
196                         ((WidgetCell) row.getChildAt(k)).setApplyBitmapDeferred(isDeferred);
197                     }
198                 }
199             }
200         }
201     }
202 
203     @Override
getItemCount()204     public int getItemCount() {
205         return mVisibleEntries.size();
206     }
207 
208     /** Returns all items that will be drawn in a recycler view. */
getItems()209     public List<WidgetsListBaseEntry> getItems() {
210         return mVisibleEntries;
211     }
212 
213     /** Gets the section name for {@link com.android.launcher3.views.RecyclerViewFastScroller}. */
getSectionName(int pos)214     public String getSectionName(int pos) {
215         return mVisibleEntries.get(pos).mTitleSectionName;
216     }
217 
218     /** Updates the widget list based on {@code tempEntries}. */
setWidgets(List<WidgetsListBaseEntry> tempEntries)219     public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
220         mCachingPreviewLoader.clearAll();
221         mAllEntries = tempEntries.stream().sorted(mRowComparator)
222                 .collect(Collectors.toList());
223         if (shouldClearVisibleEntries()) {
224             mVisibleEntries.clear();
225         }
226         updateVisibleEntries();
227     }
228 
229     /** Updates the widget list based on {@code searchResults}. */
setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults)230     public void setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults) {
231         // Forget the expanded package every time widget list is refreshed in search mode.
232         mWidgetsContentVisiblePackageUserKey = null;
233         cancelLoadingPreviews();
234         setWidgets(searchResults);
235     }
236 
updateVisibleEntries()237     private void updateVisibleEntries() {
238         // If not all previews are ready, then defer this update and try again after the preview
239         // loads.
240         if (!ensureAllPreviewsReady()) return;
241 
242         // Get the current top of the header with the matching key before adjusting the visible
243         // entries.
244         OptionalInt previousPositionForPackageUserKey =
245                 getPositionForPackageUserKey(mPendingClickHeader);
246         OptionalInt topForPackageUserKey =
247                 getOffsetForPosition(previousPositionForPackageUserKey);
248 
249         List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
250                 .filter(entry -> (mFilter == null || mFilter.test(entry))
251                         && mHeaderAndSelectedContentFilter.test(entry))
252                 .map(entry -> {
253                     if (entry instanceof WidgetsListBaseEntry.Header<?>
254                             && matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
255                         // Adjust the original entries to expand headers for the selected content.
256                         return ((WidgetsListBaseEntry.Header<?>) entry).withWidgetListShown();
257                     } else if (entry instanceof WidgetsListContentEntry) {
258                         // Adjust the original content entries to accommodate for the current
259                         // maxSpanSize.
260                         return ((WidgetsListContentEntry) entry).withMaxSpanSize(mMaxSpanSize);
261                     }
262                     return entry;
263                 })
264                 .collect(Collectors.toList());
265 
266         mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
267 
268         if (mPendingClickHeader != null) {
269             // Get the position for the clicked header after adjusting the visible entries. The
270             // position may have changed if another header had previously been expanded.
271             OptionalInt positionForPackageUserKey =
272                     getPositionForPackageUserKey(mPendingClickHeader);
273             scrollToPositionAndMaintainOffset(positionForPackageUserKey, topForPackageUserKey);
274             mPendingClickHeader = null;
275         }
276     }
277 
278     /**
279      * Checks that all preview images are loaded and starts loading for those that aren't ready.
280      *
281      * @return true if all previews are ready and the data can be updated, false otherwise.
282      */
ensureAllPreviewsReady()283     private boolean ensureAllPreviewsReady() {
284         boolean allReady = true;
285         BaseActivity activity = BaseActivity.fromContext(mContext);
286         for (WidgetsListBaseEntry entry : mAllEntries) {
287             if (!(entry instanceof WidgetsListContentEntry)) continue;
288 
289             WidgetsListContentEntry contentEntry = (WidgetsListContentEntry) entry;
290             if (!matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
291                 // If the entry isn't visible, clear any loaded previews.
292                 mCachingPreviewLoader.clearPreviews(contentEntry.mWidgets);
293                 continue;
294             }
295 
296             for (int i = 0; i < entry.mWidgets.size(); i++) {
297                 WidgetItem widgetItem = entry.mWidgets.get(i);
298                 DeviceProfile deviceProfile = activity.getDeviceProfile();
299                 Size widgetSize = WidgetSizes.getWidgetItemSizePx(mContext, deviceProfile,
300                         widgetItem);
301                 if (widgetItem.isShortcut()) {
302                     widgetSize =
303                             new Size(
304                                     widgetSize.getWidth() + mShortcutPreviewPadding,
305                                     widgetSize.getHeight() + mShortcutPreviewPadding);
306                 }
307 
308                 if (widgetItem.hasPreviewLayout()
309                         || mCachingPreviewLoader.isPreviewLoaded(widgetItem, widgetSize)) {
310                     // The widget is ready if it can be rendered with a preview layout or if its
311                     // preview bitmap is in the cache.
312                     continue;
313                 }
314 
315                 // If we've reached this point, we should load the preview for the widget.
316                 allReady = false;
317                 mCachingPreviewLoader.loadPreview(
318                         activity,
319                         widgetItem,
320                         widgetSize,
321                         mPreviewLoadedCallback);
322             }
323         }
324         return allReady;
325     }
326 
327     /** Returns whether {@code entry} matches {@code key}. */
isHeaderForPackageUserKey( @onNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key)328     private static boolean isHeaderForPackageUserKey(
329             @NonNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key) {
330         return entry instanceof WidgetsListBaseEntry.Header && matchesKey(entry, key);
331     }
332 
matchesKey( @onNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key)333     private static boolean matchesKey(
334             @NonNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key) {
335         if (key == null) return false;
336         return entry.mPkgItem.packageName.equals(key.mPackageName)
337                 && entry.mPkgItem.user.equals(key.mUser);
338     }
339 
340     /**
341      * Resets any expanded widget header.
342      */
resetExpandedHeader()343     public void resetExpandedHeader() {
344         if (mWidgetsContentVisiblePackageUserKey != null) {
345             mWidgetsContentVisiblePackageUserKey = null;
346             cancelLoadingPreviews();
347             updateVisibleEntries();
348         }
349     }
350 
351     @Override
onBindViewHolder(ViewHolder holder, int pos)352     public void onBindViewHolder(ViewHolder holder, int pos) {
353         ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
354         WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
355         viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos);
356         holder.itemView.setTag(R.id.tag_widget_entry, entry);
357     }
358 
359     @Override
onCreateViewHolder(ViewGroup parent, int viewType)360     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
361         if (DEBUG) {
362             Log.v(TAG, "\nonCreateViewHolder");
363         }
364 
365         return mViewHolderBinders.get(viewType).newViewHolder(parent);
366     }
367 
368     @Override
onViewRecycled(ViewHolder holder)369     public void onViewRecycled(ViewHolder holder) {
370         mViewHolderBinders.get(holder.getItemViewType()).unbindViewHolder(holder);
371     }
372 
373     @Override
onFailedToRecycleView(ViewHolder holder)374     public boolean onFailedToRecycleView(ViewHolder holder) {
375         // If child views are animating, then the RecyclerView may choose not to recycle the view,
376         // causing extraneous onCreateViewHolder() calls.  It is safe in this case to continue
377         // recycling this view, and take care in onViewRecycled() to cancel any existing
378         // animations.
379         return true;
380     }
381 
382     @Override
getItemId(int pos)383     public long getItemId(int pos) {
384         return Arrays.hashCode(new Object[]{
385                 mVisibleEntries.get(pos).mPkgItem.hashCode(),
386                 getItemViewType(pos)});
387     }
388 
389     @Override
getItemViewType(int pos)390     public int getItemViewType(int pos) {
391         WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
392         if (entry instanceof WidgetsListContentEntry) {
393             return VIEW_TYPE_WIDGETS_LIST;
394         } else if (entry instanceof WidgetsListHeaderEntry) {
395             return VIEW_TYPE_WIDGETS_HEADER;
396         } else if (entry instanceof WidgetsListSearchHeaderEntry) {
397             return VIEW_TYPE_WIDGETS_SEARCH_HEADER;
398         }
399         throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
400     }
401 
402     @Override
onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey)403     public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) {
404         // Ignore invalid clicks, such as collapsing a package that isn't currently expanded.
405         if (!showWidgets && !packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return;
406 
407         cancelLoadingPreviews();
408 
409         if (showWidgets) {
410             mWidgetsContentVisiblePackageUserKey = packageUserKey;
411             mLauncher.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_APP_EXPANDED);
412         } else {
413             mWidgetsContentVisiblePackageUserKey = null;
414         }
415 
416         // Store the header that was clicked so that its position will be maintained the next time
417         // we update the entries.
418         mPendingClickHeader = packageUserKey;
419 
420         updateVisibleEntries();
421     }
422 
cancelLoadingPreviews()423     private void cancelLoadingPreviews() {
424         mCachingPreviewLoader.clearAll();
425     }
426 
427     /** Returns the position of the currently expanded header, or empty if it's not present. */
getSelectedHeaderPosition()428     public OptionalInt getSelectedHeaderPosition() {
429         if (mWidgetsContentVisiblePackageUserKey == null) return OptionalInt.empty();
430         return getPositionForPackageUserKey(mWidgetsContentVisiblePackageUserKey);
431     }
432 
433     /**
434      * Returns the position of {@code key} in {@link #mVisibleEntries}, or  empty if it's not
435      * present.
436      */
437     @NonNull
getPositionForPackageUserKey(@ullable PackageUserKey key)438     private OptionalInt getPositionForPackageUserKey(@Nullable PackageUserKey key) {
439         return IntStream.range(0, mVisibleEntries.size())
440                 .filter(index -> isHeaderForPackageUserKey(mVisibleEntries.get(index), key))
441                 .findFirst();
442     }
443 
444     /**
445      * Returns the top of {@code positionOptional} in the recycler view, or empty if its view
446      * can't be found for any reason, including the position not being currently visible. The
447      * returned value does not include the top padding of the recycler view.
448      */
getOffsetForPosition(OptionalInt positionOptional)449     private OptionalInt getOffsetForPosition(OptionalInt positionOptional) {
450         if (!positionOptional.isPresent() || mRecyclerView == null) return OptionalInt.empty();
451 
452         RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
453         if (layoutManager == null) return OptionalInt.empty();
454 
455         View view = layoutManager.findViewByPosition(positionOptional.getAsInt());
456         if (view == null) return OptionalInt.empty();
457 
458         return OptionalInt.of(layoutManager.getDecoratedTop(view));
459     }
460 
461     /**
462      * Scrolls to the selected header position with the provided offset. LinearLayoutManager
463      * scrolls the minimum distance necessary, so this will keep the selected header in place during
464      * clicks, without interrupting the animation.
465      *
466      * @param positionOptional The position too scroll to. No scrolling will be done if empty.
467      * @param offsetOptional The offset from the top to maintain. If empty, then the list will
468      *                       scroll to the top of the position.
469      */
scrollToPositionAndMaintainOffset( OptionalInt positionOptional, OptionalInt offsetOptional)470     private void scrollToPositionAndMaintainOffset(
471             OptionalInt positionOptional,
472             OptionalInt offsetOptional) {
473         if (!positionOptional.isPresent() || mRecyclerView == null) return;
474         int position = positionOptional.getAsInt();
475 
476         LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
477         if (layoutManager == null) return;
478 
479         if (position == mVisibleEntries.size() - 2
480                 && mVisibleEntries.get(mVisibleEntries.size() - 1)
481                 instanceof WidgetsListContentEntry) {
482             // If the selected header is in the last position and its content is showing, then
483             // scroll to the final position so the last list of widgets will show.
484             layoutManager.scrollToPosition(mVisibleEntries.size() - 1);
485             return;
486         }
487 
488         // Scroll to the header view's current offset, accounting for the recycler view's padding.
489         // If the header view couldn't be found, then it will appear at the top of the list.
490         layoutManager.scrollToPositionWithOffset(
491                 position,
492                 offsetOptional.orElse(0) - mRecyclerView.getPaddingTop());
493     }
494 
495     /**
496      * Sets the max horizontal span in cells that is allowed for grouping more than one widget in a
497      * table row.
498      */
setMaxHorizontalSpansPerRow(int maxHorizontalSpans)499     public void setMaxHorizontalSpansPerRow(int maxHorizontalSpans) {
500         mMaxSpanSize = maxHorizontalSpans;
501         updateVisibleEntries();
502     }
503 
504     /**
505      * Returns {@code true} if there is a change in {@link #mAllEntries} that results in an
506      * invalidation of {@link #mVisibleEntries}. e.g. there is change in the device language.
507      */
shouldClearVisibleEntries()508     private boolean shouldClearVisibleEntries() {
509         Map<PackageUserKey, PackageItemInfo> packagesInfo =
510                 mAllEntries.stream()
511                         .filter(entry -> entry instanceof WidgetsListHeaderEntry)
512                         .map(entry -> entry.mPkgItem)
513                         .collect(Collectors.toMap(
514                                 entry -> new PackageUserKey(entry.packageName, entry.user),
515                                 entry -> entry));
516         for (WidgetsListBaseEntry visibleEntry: mVisibleEntries) {
517             PackageUserKey key = new PackageUserKey(visibleEntry.mPkgItem.packageName,
518                     visibleEntry.mPkgItem.user);
519             PackageItemInfo packageItemInfo = packagesInfo.get(key);
520             if (packageItemInfo != null
521                     && !visibleEntry.mPkgItem.title.equals(packageItemInfo.title)) {
522                 return true;
523             }
524         }
525         return false;
526     }
527 
528     /** Comparator for sorting WidgetListRowEntry based on package title. */
529     public static class WidgetListBaseRowEntryComparator implements
530             Comparator<WidgetsListBaseEntry> {
531 
532         private final LabelComparator mComparator = new LabelComparator();
533 
534         @Override
compare(WidgetsListBaseEntry a, WidgetsListBaseEntry b)535         public int compare(WidgetsListBaseEntry a, WidgetsListBaseEntry b) {
536             int i = mComparator.compare(a.mPkgItem.title.toString(), b.mPkgItem.title.toString());
537             if (i != 0) {
538                 return i;
539             }
540             // Prioritize entries from current user over other users if the entries are same.
541             if (a.mPkgItem.user.equals(b.mPkgItem.user)) return 0;
542             if (a.mPkgItem.user.equals(Process.myUserHandle())) return -1;
543             return 1;
544         }
545     }
546 }
547