1 /* 2 * Copyright (C) 2017 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 17 package com.android.launcher3.widget; 18 19 import android.support.v7.widget.RecyclerView; 20 import android.util.Log; 21 22 import com.android.launcher3.IconCache; 23 import com.android.launcher3.model.PackageItemInfo; 24 import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator; 25 26 import java.util.ArrayList; 27 import java.util.Iterator; 28 29 /** 30 * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter} 31 * methods accordingly. 32 */ 33 public class WidgetsDiffReporter { 34 private static final boolean DEBUG = false; 35 private static final String TAG = "WidgetsDiffReporter"; 36 37 private final IconCache mIconCache; 38 private final RecyclerView.Adapter mListener; 39 WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener)40 public WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener) { 41 mIconCache = iconCache; 42 mListener = listener; 43 } 44 process(ArrayList<WidgetListRowEntry> currentEntries, ArrayList<WidgetListRowEntry> newEntries, WidgetListRowEntryComparator comparator)45 public void process(ArrayList<WidgetListRowEntry> currentEntries, 46 ArrayList<WidgetListRowEntry> newEntries, WidgetListRowEntryComparator comparator) { 47 if (DEBUG) { 48 Log.d(TAG, "process oldEntries#=" + currentEntries.size() 49 + " newEntries#=" + newEntries.size()); 50 } 51 // Early exit if either of the list is empty 52 if (currentEntries.isEmpty() || newEntries.isEmpty()) { 53 // Skip if both list are empty. 54 // On rotation, we open the widget tray with empty. Then try to fetch the list again 55 // when the animation completes (which still gives empty). And we get the final result 56 // when the bind actually completes. 57 if (currentEntries.size() != newEntries.size()) { 58 currentEntries.clear(); 59 currentEntries.addAll(newEntries); 60 mListener.notifyDataSetChanged(); 61 } 62 return; 63 } 64 ArrayList<WidgetListRowEntry> orgEntries = 65 (ArrayList<WidgetListRowEntry>) currentEntries.clone(); 66 Iterator<WidgetListRowEntry> orgIter = orgEntries.iterator(); 67 Iterator<WidgetListRowEntry> newIter = newEntries.iterator(); 68 69 WidgetListRowEntry orgRowEntry = orgIter.next(); 70 WidgetListRowEntry newRowEntry = newIter.next(); 71 72 do { 73 int diff = comparePackageName(orgRowEntry, newRowEntry, comparator); 74 if (DEBUG) { 75 Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)", 76 diff, orgRowEntry != null? orgRowEntry.toString() : null, 77 newRowEntry != null? newRowEntry.toString() : null)); 78 } 79 int index = -1; 80 if (diff < 0) { 81 index = currentEntries.indexOf(orgRowEntry); 82 mListener.notifyItemRemoved(index); 83 if (DEBUG) { 84 Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index, 85 orgRowEntry.titleSectionName)); 86 } 87 currentEntries.remove(index); 88 orgRowEntry = orgIter.hasNext() ? orgIter.next() : null; 89 } else if (diff > 0) { 90 index = orgRowEntry != null? currentEntries.indexOf(orgRowEntry): 91 currentEntries.size(); 92 currentEntries.add(index, newRowEntry); 93 if (DEBUG) { 94 Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index, 95 newRowEntry.titleSectionName)); 96 } 97 newRowEntry = newIter.hasNext() ? newIter.next() : null; 98 mListener.notifyItemInserted(index); 99 100 } else { 101 // same package name but, 102 // did the icon, title, etc, change? 103 // or did the widget size and desc, span, etc change? 104 if (!isSamePackageItemInfo(orgRowEntry.pkgItem, newRowEntry.pkgItem) || 105 !orgRowEntry.widgets.equals(newRowEntry.widgets)) { 106 index = currentEntries.indexOf(orgRowEntry); 107 currentEntries.set(index, newRowEntry); 108 mListener.notifyItemChanged(index); 109 if (DEBUG) { 110 Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index, 111 newRowEntry.titleSectionName)); 112 } 113 } 114 orgRowEntry = orgIter.hasNext() ? orgIter.next() : null; 115 newRowEntry = newIter.hasNext() ? newIter.next() : null; 116 } 117 } while(orgRowEntry != null || newRowEntry != null); 118 } 119 120 /** 121 * Compare package name using the same comparator as in {@link WidgetsListAdapter}. 122 * Also handle null row pointers. 123 */ comparePackageName(WidgetListRowEntry curRow, WidgetListRowEntry newRow, WidgetListRowEntryComparator comparator)124 private int comparePackageName(WidgetListRowEntry curRow, WidgetListRowEntry newRow, 125 WidgetListRowEntryComparator comparator) { 126 if (curRow == null && newRow == null) { 127 throw new IllegalStateException("Cannot compare PackageItemInfo if both rows are null."); 128 } 129 130 if (curRow == null && newRow != null) { 131 return 1; // new row needs to be inserted 132 } else if (curRow != null && newRow == null) { 133 return -1; // old row needs to be deleted 134 } 135 return comparator.compare(curRow, newRow); 136 } 137 isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo)138 private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) { 139 return curInfo.iconBitmap.equals(newInfo.iconBitmap) && 140 !mIconCache.isDefaultIcon(curInfo.iconBitmap, curInfo.user); 141 } 142 } 143