• 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.allapps;
17 
18 import static android.multiuser.Flags.enableMovingContentIntoPrivateSpace;
19 
20 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO;
21 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER;
22 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
23 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
24 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
25 import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE;
26 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT;
27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT;
28 
29 import android.content.Context;
30 import android.text.Spannable;
31 import android.text.SpannableString;
32 import android.text.style.ImageSpan;
33 import android.util.Log;
34 
35 import androidx.annotation.Nullable;
36 import androidx.annotation.VisibleForTesting;
37 import androidx.recyclerview.widget.DiffUtil;
38 
39 import com.android.launcher3.Flags;
40 import com.android.launcher3.R;
41 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
42 import com.android.launcher3.model.data.AppInfo;
43 import com.android.launcher3.model.data.ItemInfo;
44 import com.android.launcher3.util.LabelComparator;
45 import com.android.launcher3.views.ActivityContext;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.Locale;
51 import java.util.Map;
52 import java.util.Objects;
53 import java.util.TreeMap;
54 import java.util.function.Predicate;
55 import java.util.stream.Collectors;
56 import java.util.stream.Stream;
57 
58 /**
59  * The alphabetically sorted list of applications.
60  *
61  * @param <T> Type of context inflating this view.
62  */
63 public class AlphabeticalAppsList<T extends Context & ActivityContext> implements
64         AllAppsStore.OnUpdateListener {
65 
66     public static final String TAG = "AlphabeticalAppsList";
67     public static final String PRIVATE_SPACE_PACKAGE = "com.android.privatespace";
68 
69     private final WorkProfileManager mWorkProviderManager;
70 
71     private final PrivateProfileManager mPrivateProviderManager;
72 
73     /**
74      * Info about a fast scroller section, depending if sections are merged, the fast scroller
75      * sections will not be the same set as the section headers.
76      */
77     public static class FastScrollSectionInfo {
78         // The section name
79         public final CharSequence sectionName;
80         // The item position
81         public final int position;
82         // The view id associated with this section
83         public int id = -1;
84 
FastScrollSectionInfo(CharSequence sectionName, int position)85         public FastScrollSectionInfo(CharSequence sectionName, int position) {
86             this.sectionName = sectionName;
87             this.position = position;
88         }
89 
setId(int id)90         public void setId(int id) {
91             this.id = id;
92         }
93     }
94 
95 
96     private final T mActivityContext;
97 
98     // The set of apps from the system
99     private final List<AppInfo> mApps = new ArrayList<>();
100     private final List<AppInfo> mPrivateApps = new ArrayList<>();
101     @Nullable
102     private final AllAppsStore<T> mAllAppsStore;
103 
104     // The number of results in current adapter
105     private int mAccessibilityResultsCount = 0;
106     // The current set of adapter items
107     private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
108     // The set of sections that we allow fast-scrolling to (includes non-merged sections)
109     private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
110 
111     // The of ordered component names as a result of a search query
112     private final ArrayList<AdapterItem> mSearchResults = new ArrayList<>();
113     private final SpannableString mPrivateProfileAppScrollerBadge;
114     private final SpannableString mPrivateProfileDividerBadge;
115     private BaseAllAppsAdapter<T> mAdapter;
116     private AppInfoComparator mAppNameComparator;
117     private int mNumAppsPerRowAllApps;
118     private int mNumAppRowsInAdapter;
119     private Predicate<ItemInfo> mItemFilter;
120 
AlphabeticalAppsList(Context context, @Nullable AllAppsStore<T> appsStore, WorkProfileManager workProfileManager, PrivateProfileManager privateProfileManager)121     public AlphabeticalAppsList(Context context, @Nullable AllAppsStore<T> appsStore,
122             WorkProfileManager workProfileManager, PrivateProfileManager privateProfileManager) {
123         mAllAppsStore = appsStore;
124         mActivityContext = ActivityContext.lookupContext(context);
125         mAppNameComparator = new AppInfoComparator(context);
126         mWorkProviderManager = workProfileManager;
127         mPrivateProviderManager = privateProfileManager;
128         mNumAppsPerRowAllApps = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
129         if (mAllAppsStore != null) {
130             mAllAppsStore.addUpdateListener(this);
131         }
132         mPrivateProfileAppScrollerBadge = new SpannableString(" ");
133         mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context, Flags.letterFastScroller()
134                         ? R.drawable.ic_private_profile_letter_list_fast_scroller_badge :
135                         R.drawable.ic_private_profile_app_scroller_badge, ImageSpan.ALIGN_CENTER),
136                 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
137         mPrivateProfileDividerBadge = new SpannableString(" ");
138         mPrivateProfileDividerBadge.setSpan(new ImageSpan(context,
139                         R.drawable.ic_private_profile_divider_badge, ImageSpan.ALIGN_CENTER),
140                 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
141     }
142 
143     /** Set the number of apps per row when device profile changes. */
setNumAppsPerRowAllApps(int numAppsPerRow)144     public void setNumAppsPerRowAllApps(int numAppsPerRow) {
145         mNumAppsPerRowAllApps = numAppsPerRow;
146     }
147 
updateItemFilter(Predicate<ItemInfo> itemFilter)148     public void updateItemFilter(Predicate<ItemInfo> itemFilter) {
149         this.mItemFilter = itemFilter;
150         onAppsUpdated();
151     }
152 
153     /**
154      * Sets the adapter to notify when this dataset changes.
155      */
setAdapter(BaseAllAppsAdapter<T> adapter)156     public void setAdapter(BaseAllAppsAdapter<T> adapter) {
157         mAdapter = adapter;
158     }
159 
160     /**
161      * Returns fast scroller sections of all the current filtered applications.
162      */
getFastScrollerSections()163     public List<FastScrollSectionInfo> getFastScrollerSections() {
164         return mFastScrollerSections;
165     }
166 
167     /**
168      * Returns the current filtered list of applications broken down into their sections.
169      */
getAdapterItems()170     public List<AdapterItem> getAdapterItems() {
171         return mAdapterItems;
172     }
173 
174     /**
175      * Returns the child adapter item with IME launch focus.
176      */
getFocusedChild()177     public AdapterItem getFocusedChild() {
178         if (mAdapterItems.size() == 0 || getFocusedChildIndex() == -1) {
179             return null;
180         }
181         return mAdapterItems.get(getFocusedChildIndex());
182     }
183 
184     /**
185      * Returns the index of the child with IME launch focus.
186      */
getFocusedChildIndex()187     public int getFocusedChildIndex() {
188         for (AdapterItem item : mAdapterItems) {
189             if (item.isCountedForAccessibility()) {
190                 return mAdapterItems.indexOf(item);
191             }
192         }
193         return -1;
194     }
195 
196     /**
197      * Returns the number of rows of applications
198      */
getNumAppRows()199     public int getNumAppRows() {
200         return mNumAppRowsInAdapter;
201     }
202 
203     /**
204      * Returns the number of applications in this list.
205      */
getNumFilteredApps()206     public int getNumFilteredApps() {
207         return mAccessibilityResultsCount;
208     }
209 
210     /**
211      * Returns whether there are search results which will hide the A-Z list.
212      */
hasSearchResults()213     public boolean hasSearchResults() {
214         return !mSearchResults.isEmpty();
215     }
216 
217     /**
218      * Sets results list for search
219      */
setSearchResults(ArrayList<AdapterItem> results)220     public boolean setSearchResults(ArrayList<AdapterItem> results) {
221         if (Objects.equals(results, mSearchResults)) {
222             return false;
223         }
224         mSearchResults.clear();
225         if (results != null) {
226             mSearchResults.addAll(results);
227         }
228         updateAdapterItems();
229         return true;
230     }
231 
232     /**
233      * Updates internals when the set of apps are updated.
234      */
235     @Override
onAppsUpdated()236     public void onAppsUpdated() {
237         // Don't update apps when the private profile animations are running, otherwise the motion
238         // is canceled.
239         if (mAllAppsStore == null || (mPrivateProviderManager != null &&
240                 mPrivateProviderManager.getAnimationRunning())) {
241             return;
242         }
243         // Sort the list of apps
244         mApps.clear();
245         mPrivateApps.clear();
246 
247         Stream<AppInfo> appSteam = Stream.of(mAllAppsStore.getApps());
248         Stream<AppInfo> privateAppStream = Stream.of(mAllAppsStore.getApps());
249 
250         if (!hasSearchResults() && mItemFilter != null) {
251             appSteam = appSteam.filter(mItemFilter);
252             if (mPrivateProviderManager != null) {
253                 privateAppStream = privateAppStream
254                         .filter(mPrivateProviderManager.getItemInfoMatcher());
255             }
256         }
257         appSteam = appSteam.sorted(mAppNameComparator);
258         privateAppStream = privateAppStream.sorted(mAppNameComparator);
259 
260         // As a special case for some languages (currently only Simplified Chinese), we may need to
261         // coalesce sections
262         Locale curLocale = mActivityContext.getResources().getConfiguration().locale;
263         boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
264         if (localeRequiresSectionSorting) {
265             // Compute the section headers. We use a TreeMap with the section name comparator to
266             // ensure that the sections are ordered when we iterate over it later
267             appSteam = appSteam.collect(Collectors.groupingBy(
268                     info -> info.sectionName,
269                     () -> new TreeMap<>(new LabelComparator()),
270                     Collectors.toCollection(ArrayList::new)))
271                     .values()
272                     .stream()
273                     .flatMap(ArrayList::stream);
274         }
275 
276         appSteam.forEachOrdered(mApps::add);
277         privateAppStream.forEachOrdered(mPrivateApps::add);
278         // Recompose the set of adapter items from the current set of apps
279         if (mSearchResults.isEmpty()) {
280             updateAdapterItems();
281         }
282     }
283 
284     /**
285      * Updates the set of filtered apps with the current filter. At this point, we expect
286      * mCachedSectionNames to have been calculated for the set of all apps in mApps.
287      */
updateAdapterItems()288     public void updateAdapterItems() {
289         List<AdapterItem> oldItems = new ArrayList<>(mAdapterItems);
290         // Prepare to update the list of sections, filtered apps, etc.
291         mFastScrollerSections.clear();
292         Log.d(TAG, "Clearing FastScrollerSections.");
293         mAdapterItems.clear();
294         mAccessibilityResultsCount = 0;
295 
296         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
297         // ordered set of sections
298         if (hasSearchResults()) {
299             mAdapterItems.addAll(mSearchResults);
300         } else {
301             int position = 0;
302             boolean addApps = true;
303             if (mWorkProviderManager != null) {
304                 position += mWorkProviderManager.addWorkItems(mAdapterItems);
305                 addApps = mWorkProviderManager.shouldShowWorkApps();
306             }
307             if (addApps) {
308                 if (/* education card was added */ position == 1) {
309                     // Add work educard section with "info icon" at 0th position.
310                     mFastScrollerSections.add(new FastScrollSectionInfo(
311                             mActivityContext.getResources().getString(
312                                     R.string.work_profile_edu_section), 0));
313                     Log.d(TAG, "Adding FastScrollSection for work edu card.");
314                 }
315                 position = addAppsWithSections(mApps, position);
316             }
317             if (Flags.enablePrivateSpace()) {
318                 position = addPrivateSpaceItems(position);
319             }
320             if (!mFastScrollerSections.isEmpty()) {
321                 // After all the adapterItems are added, add a view to the bottom so that user can
322                 // scroll all the way down.
323                 mAdapterItems.add(new AdapterItem(VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO));
324                 mFastScrollerSections.add(new FastScrollSectionInfo(
325                         mFastScrollerSections.get(mFastScrollerSections.size() - 1).sectionName,
326                         position++));
327                 Log.d(TAG, "Adding FastScrollSection duplicate to scroll to the bottom.");
328             }
329         }
330         mAccessibilityResultsCount = (int) mAdapterItems.stream()
331                 .filter(AdapterItem::isCountedForAccessibility).count();
332 
333         if (mNumAppsPerRowAllApps != 0) {
334             // Update the number of rows in the adapter after we do all the merging (otherwise, we
335             // would have to shift the values again)
336             int numAppsInSection = 0;
337             int numAppsInRow = 0;
338             int rowIndex = -1;
339             for (AdapterItem item : mAdapterItems) {
340                 item.rowIndex = 0;
341                 if (BaseAllAppsAdapter.isDividerViewType(item.viewType)
342                         || BaseAllAppsAdapter.isPrivateSpaceHeaderView(item.viewType)
343                         || BaseAllAppsAdapter.isPrivateSpaceSysAppsDividerView(item.viewType)) {
344                     numAppsInSection = 0;
345                 } else if (BaseAllAppsAdapter.isIconViewType(item.viewType)) {
346                     if (numAppsInSection % mNumAppsPerRowAllApps == 0) {
347                         numAppsInRow = 0;
348                         rowIndex++;
349                     }
350                     item.rowIndex = rowIndex;
351                     item.rowAppIndex = numAppsInRow;
352                     numAppsInSection++;
353                     numAppsInRow++;
354                 }
355             }
356             mNumAppRowsInAdapter = rowIndex + 1;
357         }
358 
359         if (mAdapter != null) {
360             DiffUtil.calculateDiff(new MyDiffCallback(oldItems, mAdapterItems), false)
361                     .dispatchUpdatesTo(mAdapter);
362         }
363     }
364 
addPrivateSpaceItems(int position)365     int addPrivateSpaceItems(int position) {
366         if (mPrivateProviderManager != null
367                 && !mPrivateProviderManager.isPrivateSpaceHidden()
368                 && !mPrivateApps.isEmpty()) {
369             // Always add PS Header if Space is present and visible.
370             position = mPrivateProviderManager.addPrivateSpaceHeader(mAdapterItems);
371             Log.d(TAG, "Adding FastScrollSection for Private Space header. ");
372             mFastScrollerSections.add(new FastScrollSectionInfo(
373                     mPrivateProfileAppScrollerBadge, position));
374             int privateSpaceState = mPrivateProviderManager.getCurrentState();
375             switch (privateSpaceState) {
376                 case PrivateProfileManager.STATE_DISABLED:
377                 case PrivateProfileManager.STATE_TRANSITION:
378                     break;
379                 case PrivateProfileManager.STATE_ENABLED:
380                     // Add PS Apps only in Enabled State.
381                     position = addPrivateSpaceApps(position);
382                     break;
383             }
384         }
385         return position;
386     }
387 
addPrivateSpaceApps(int position)388     private int addPrivateSpaceApps(int position) {
389         // Add Install Apps Button first.
390         if (Flags.privateSpaceAppInstallerButton() && !enableMovingContentIntoPrivateSpace()) {
391             mPrivateProviderManager.addPrivateSpaceInstallAppButton(mAdapterItems);
392             position++;
393         }
394 
395         // Split of private space apps into user-installed and system apps.
396         Map<Boolean, List<AppInfo>> split = mPrivateApps.stream()
397                 .collect(Collectors.partitioningBy(mPrivateProviderManager
398                                 .splitIntoUserInstalledAndSystemApps(mActivityContext)));
399 
400         // TODO(b/329688630): switch to the pulled LayoutStaticSnapshot atom
401         mActivityContext
402                 .getStatsLogManager()
403                 .logger()
404                 .withCardinality(split.get(true).size())
405                 .log(LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT);
406 
407         mActivityContext
408                 .getStatsLogManager()
409                 .logger()
410                 .withCardinality(split.get(false).size())
411                 .log(LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT);
412 
413         // Add user installed apps
414         position = addAppsWithSections(split.get(true), position);
415         // Add system apps separator.
416         if (Flags.privateSpaceSysAppsSeparation()) {
417             position = mPrivateProviderManager.addSystemAppsDivider(mAdapterItems);
418             if (Flags.letterFastScroller()) {
419                 FastScrollSectionInfo sectionInfo =
420                         new FastScrollSectionInfo(mPrivateProfileDividerBadge, position);
421                 mFastScrollerSections.add(sectionInfo);
422             }
423         }
424         // Add system apps.
425         position = addAppsWithSections(split.get(false), position);
426 
427         if (enableMovingContentIntoPrivateSpace()) {
428             // Look for the private space app via package and move it after header.
429             int headerIndex = -1;
430             int privateSpaceAppIndex = -1;
431             for (int i = 0; i < mAdapterItems.size(); i++) {
432                 BaseAllAppsAdapter.AdapterItem currentItem = mAdapterItems.get(i);
433                 if (currentItem.viewType == VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER) {
434                     headerIndex = i;
435                 }
436                 if (currentItem.itemInfo != null && Objects.equals(
437                         currentItem.itemInfo.getTargetPackage(), PRIVATE_SPACE_PACKAGE)) {
438                     currentItem.itemInfo.bitmap = mPrivateProviderManager.preparePSBitmapInfo();
439                     currentItem.itemInfo.bitmap.creationFlags |= FLAG_NO_BADGE;
440                     currentItem.itemInfo.contentDescription =
441                             mPrivateProviderManager.getPsAppContentDesc();
442                     privateSpaceAppIndex = i;
443                 }
444             }
445             if (headerIndex != -1 && privateSpaceAppIndex != -1) {
446                 BaseAllAppsAdapter.AdapterItem movedItem =
447                         mAdapterItems.remove(privateSpaceAppIndex);
448                 // Move the icon after the header.
449                 mAdapterItems.add(headerIndex + 1, movedItem);
450             }
451         }
452         return position;
453     }
454 
addAppsWithSections(List<AppInfo> appList, int startPosition)455     private int addAppsWithSections(List<AppInfo> appList, int startPosition) {
456         String lastSectionName = null;
457         boolean hasPrivateApps = false;
458         int position = startPosition;
459         if (mPrivateProviderManager != null) {
460             hasPrivateApps = appList.stream().
461                     allMatch(mPrivateProviderManager.getItemInfoMatcher());
462         }
463         Log.d(TAG, "Adding apps with sections. HasPrivateApps: " + hasPrivateApps);
464         for (int i = 0; i < appList.size(); i++) {
465             AppInfo info = appList.get(i);
466             // Apply decorator to private apps.
467             if (hasPrivateApps) {
468                 mAdapterItems.add(AdapterItem.asAppWithDecorationInfo(info,
469                         new SectionDecorationInfo(mActivityContext,
470                                 getRoundRegions(i, appList.size()), true /* decorateTogether */)));
471             } else {
472                 mAdapterItems.add(AdapterItem.asApp(info));
473             }
474 
475             String sectionName = info.sectionName;
476             // Create a new section if the section names do not match
477             if (!sectionName.equals(lastSectionName)) {
478                 Log.d(TAG, "addAppsWithSections: adding sectionName: " + sectionName
479                     + " with appInfoTitle: " + info.title);
480                 lastSectionName = sectionName;
481                 boolean usePrivateAppScrollerBadge = !Flags.letterFastScroller() && hasPrivateApps;
482                 FastScrollSectionInfo sectionInfo = new FastScrollSectionInfo(
483                         usePrivateAppScrollerBadge ?
484                                 mPrivateProfileAppScrollerBadge : sectionName, position);
485                 mFastScrollerSections.add(sectionInfo);
486             }
487             position++;
488         }
489         return position;
490     }
491 
492     /**
493      * Determines the corner regions that should be rounded for a specific app icon based on its
494      * position in a grid. Apps that should only be cared about rounding are the apps in the last
495      * row. In the last row on the first column, the app should only be rounded on the bottom left.
496      * Apps in the middle would not be rounded and the last app on the last row will ALWAYS have a
497      * {@link SectionDecorationInfo#ROUND_BOTTOM_RIGHT}.
498      *
499      * @param appIndex The index of the app icon within the app list.
500      * @param appListSize The total number of apps within the app list.
501      * @return  An integer representing the corner regions to be rounded, using bitwise flags:
502      *          - {@link SectionDecorationInfo#ROUND_NOTHING}: No corners should be rounded.
503      *          - {@link SectionDecorationInfo#ROUND_TOP_LEFT}: Round the top-left corner.
504      *          - {@link SectionDecorationInfo#ROUND_TOP_RIGHT}: Round the top-right corner.
505      *          - {@link SectionDecorationInfo#ROUND_BOTTOM_LEFT}: Round the bottom-left corner.
506      *          - {@link SectionDecorationInfo#ROUND_BOTTOM_RIGHT}: Round the bottom-right corner.
507      */
508     @VisibleForTesting
getRoundRegions(int appIndex, int appListSize)509     int getRoundRegions(int appIndex, int appListSize) {
510         int numberOfAppRows = (int) Math.ceil((double) appListSize / mNumAppsPerRowAllApps);
511         int roundRegion = ROUND_NOTHING;
512         // App is in the last row.
513         if ((appIndex / mNumAppsPerRowAllApps) == numberOfAppRows - 1) {
514             if ((appIndex % mNumAppsPerRowAllApps) == 0) {
515                 // App is the first column.
516                 roundRegion = ROUND_BOTTOM_LEFT;
517             } else if ((appIndex % mNumAppsPerRowAllApps) == mNumAppsPerRowAllApps-1) {
518                 // App is in the last column.
519                 roundRegion = ROUND_BOTTOM_RIGHT;
520             }
521             // Ensure the last private app is rounded on the bottom right.
522             if (appIndex == appListSize - 1) {
523                 roundRegion |= ROUND_BOTTOM_RIGHT;
524             }
525         }
526         return roundRegion;
527     }
528 
getPrivateProfileManager()529     public PrivateProfileManager getPrivateProfileManager() {
530         return mPrivateProviderManager;
531     }
532 
dump(String prefix, PrintWriter writer)533     public void dump(String prefix, PrintWriter writer) {
534         writer.println(prefix + "SectionInfo[] size: " + mFastScrollerSections.size());
535         for (int i = 0; i < mFastScrollerSections.size(); i++) {
536             writer.println("\tFastScrollSection: " + mFastScrollerSections.get(i).sectionName);
537         }
538     }
539 
540     private static class MyDiffCallback extends DiffUtil.Callback {
541 
542         private final List<AdapterItem> mOldList;
543         private final List<AdapterItem> mNewList;
544 
MyDiffCallback(List<AdapterItem> oldList, List<AdapterItem> newList)545         MyDiffCallback(List<AdapterItem> oldList, List<AdapterItem> newList) {
546             mOldList = oldList;
547             mNewList = newList;
548         }
549 
550         @Override
getOldListSize()551         public int getOldListSize() {
552             return mOldList.size();
553         }
554 
555         @Override
getNewListSize()556         public int getNewListSize() {
557             return mNewList.size();
558         }
559 
560         @Override
areItemsTheSame(int oldItemPosition, int newItemPosition)561         public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
562             return mOldList.get(oldItemPosition).isSameAs(mNewList.get(newItemPosition));
563         }
564 
565         @Override
areContentsTheSame(int oldItemPosition, int newItemPosition)566         public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
567             return mOldList.get(oldItemPosition).isContentSame(mNewList.get(newItemPosition));
568         }
569     }
570 
571 }