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; 17 18 import android.content.Context; 19 import android.support.v7.widget.RecyclerView; 20 import android.support.v7.widget.RecyclerView.Adapter; 21 import android.util.Log; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.view.ViewGroup; 25 26 import com.android.launcher3.LauncherAppState; 27 import com.android.launcher3.R; 28 import com.android.launcher3.WidgetPreviewLoader; 29 import com.android.launcher3.compat.AlphabeticIndexCompat; 30 import com.android.launcher3.model.PackageItemInfo; 31 import com.android.launcher3.model.WidgetItem; 32 import com.android.launcher3.util.LabelComparator; 33 import com.android.launcher3.util.MultiHashMap; 34 import com.android.launcher3.util.PackageUserKey; 35 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.Comparator; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Map; 42 43 /** 44 * List view adapter for the widget tray. 45 * 46 * <p>Memory vs. Performance: 47 * The less number of types of views are inserted into a {@link RecyclerView}, the more recycling 48 * happens and less memory is consumed. {@link #getItemViewType} was not overridden as there is 49 * only a single type of view. 50 */ 51 public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { 52 53 private static final String TAG = "WidgetsListAdapter"; 54 private static final boolean DEBUG = false; 55 56 private final WidgetPreviewLoader mWidgetPreviewLoader; 57 private final LayoutInflater mLayoutInflater; 58 59 private final View.OnClickListener mIconClickListener; 60 private final View.OnLongClickListener mIconLongClickListener; 61 62 private final ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>(); 63 private final AlphabeticIndexCompat mIndexer; 64 65 private final int mIndent; 66 WidgetsListAdapter(View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener, Context context)67 public WidgetsListAdapter(View.OnClickListener iconClickListener, 68 View.OnLongClickListener iconLongClickListener, 69 Context context) { 70 mLayoutInflater = LayoutInflater.from(context); 71 mWidgetPreviewLoader = LauncherAppState.getInstance(context).getWidgetCache(); 72 73 mIndexer = new AlphabeticIndexCompat(context); 74 75 mIconClickListener = iconClickListener; 76 mIconLongClickListener = iconLongClickListener; 77 mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent); 78 } 79 setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets)80 public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) { 81 mEntries.clear(); 82 WidgetItemComparator widgetComparator = new WidgetItemComparator(); 83 84 for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) { 85 WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue()); 86 row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title); 87 Collections.sort(row.widgets, widgetComparator); 88 mEntries.add(row); 89 } 90 91 Collections.sort(mEntries, new WidgetListRowEntryComparator()); 92 } 93 94 @Override getItemCount()95 public int getItemCount() { 96 return mEntries.size(); 97 } 98 getSectionName(int pos)99 public String getSectionName(int pos) { 100 return mEntries.get(pos).titleSectionName; 101 } 102 103 /** 104 * Copies and returns the widgets associated with the package and user of the ComponentKey. 105 */ copyWidgetsForPackageUser(PackageUserKey packageUserKey)106 public List<WidgetItem> copyWidgetsForPackageUser(PackageUserKey packageUserKey) { 107 for (WidgetListRowEntry entry : mEntries) { 108 if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) { 109 ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets); 110 // Remove widgets not associated with the correct user. 111 Iterator<WidgetItem> iterator = widgets.iterator(); 112 while (iterator.hasNext()) { 113 if (!iterator.next().user.equals(packageUserKey.mUser)) { 114 iterator.remove(); 115 } 116 } 117 return widgets.isEmpty() ? null : widgets; 118 } 119 } 120 return null; 121 } 122 123 @Override onBindViewHolder(WidgetsRowViewHolder holder, int pos)124 public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) { 125 WidgetListRowEntry entry = mEntries.get(pos); 126 List<WidgetItem> infoList = entry.widgets; 127 128 ViewGroup row = holder.cellContainer; 129 if (DEBUG) { 130 Log.d(TAG, String.format( 131 "onBindViewHolder [pos=%d, widget#=%d, row.getChildCount=%d]", 132 pos, infoList.size(), row.getChildCount())); 133 } 134 135 // Add more views. 136 // if there are too many, hide them. 137 int expectedChildCount = infoList.size() + Math.max(0, infoList.size() - 1); 138 int childCount = row.getChildCount(); 139 140 if (expectedChildCount > childCount) { 141 for (int i = childCount ; i < expectedChildCount; i++) { 142 if ((i & 1) == 1) { 143 // Add a divider for odd index 144 mLayoutInflater.inflate(R.layout.widget_list_divider, row); 145 } else { 146 // Add cell for even index 147 WidgetCell widget = (WidgetCell) mLayoutInflater.inflate( 148 R.layout.widget_cell, row, false); 149 150 // set up touch. 151 widget.setOnClickListener(mIconClickListener); 152 widget.setOnLongClickListener(mIconLongClickListener); 153 row.addView(widget); 154 } 155 } 156 } else if (expectedChildCount < childCount) { 157 for (int i = expectedChildCount ; i < childCount; i++) { 158 row.getChildAt(i).setVisibility(View.GONE); 159 } 160 } 161 162 // Bind the views in the application info section. 163 holder.title.applyFromPackageItemInfo(entry.pkgItem); 164 165 // Bind the view in the widget horizontal tray region. 166 for (int i=0; i < infoList.size(); i++) { 167 WidgetCell widget = (WidgetCell) row.getChildAt(2*i); 168 widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader); 169 widget.ensurePreview(); 170 widget.setVisibility(View.VISIBLE); 171 172 if (i > 0) { 173 row.getChildAt(2*i - 1).setVisibility(View.VISIBLE); 174 } 175 } 176 } 177 178 @Override onCreateViewHolder(ViewGroup parent, int viewType)179 public WidgetsRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 180 if (DEBUG) { 181 Log.v(TAG, "\nonCreateViewHolder"); 182 } 183 184 ViewGroup container = (ViewGroup) mLayoutInflater.inflate( 185 R.layout.widgets_list_row_view, parent, false); 186 187 // if the end padding is 0, then container view (horizontal scroll view) doesn't respect 188 // the end of the linear layout width + the start padding and doesn't allow scrolling. 189 container.findViewById(R.id.widgets_cell_list).setPaddingRelative(mIndent, 0, 1, 0); 190 191 return new WidgetsRowViewHolder(container); 192 } 193 194 @Override onViewRecycled(WidgetsRowViewHolder holder)195 public void onViewRecycled(WidgetsRowViewHolder holder) { 196 int total = holder.cellContainer.getChildCount(); 197 for (int i = 0; i < total; i+=2) { 198 WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i); 199 widget.clear(); 200 } 201 } 202 onFailedToRecycleView(WidgetsRowViewHolder holder)203 public boolean onFailedToRecycleView(WidgetsRowViewHolder holder) { 204 // If child views are animating, then the RecyclerView may choose not to recycle the view, 205 // causing extraneous onCreateViewHolder() calls. It is safe in this case to continue 206 // recycling this view, and take care in onViewRecycled() to cancel any existing 207 // animations. 208 return true; 209 } 210 211 @Override getItemId(int pos)212 public long getItemId(int pos) { 213 return pos; 214 } 215 216 /** 217 * Comparator for sorting WidgetListRowEntry based on package title 218 */ 219 public static class WidgetListRowEntryComparator implements Comparator<WidgetListRowEntry> { 220 221 private final LabelComparator mComparator = new LabelComparator(); 222 223 @Override compare(WidgetListRowEntry a, WidgetListRowEntry b)224 public int compare(WidgetListRowEntry a, WidgetListRowEntry b) { 225 return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString()); 226 } 227 } 228 229 } 230