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 android.content.Context; 19 import android.view.LayoutInflater; 20 import android.view.View; 21 import android.view.ViewGroup; 22 import android.view.accessibility.AccessibilityEvent; 23 24 import androidx.core.view.accessibility.AccessibilityEventCompat; 25 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 26 import androidx.core.view.accessibility.AccessibilityRecordCompat; 27 import androidx.recyclerview.widget.GridLayoutManager; 28 import androidx.recyclerview.widget.RecyclerView; 29 import androidx.recyclerview.widget.RecyclerView.Adapter; 30 31 import com.android.launcher3.allapps.search.SearchAdapterProvider; 32 import com.android.launcher3.util.ScrollableLayoutManager; 33 import com.android.launcher3.views.ActivityContext; 34 35 import java.util.List; 36 import java.util.concurrent.CopyOnWriteArrayList; 37 38 /** 39 * The grid view adapter of all the apps. 40 * 41 * @param <T> Type of context inflating all apps. 42 */ 43 public class AllAppsGridAdapter<T extends Context & ActivityContext> extends 44 BaseAllAppsAdapter<T> { 45 46 public static final String TAG = "AppsGridAdapter"; 47 private final AppsGridLayoutManager mGridLayoutMgr; 48 private final CopyOnWriteArrayList<OnLayoutCompletedListener> mOnLayoutCompletedListeners = 49 new CopyOnWriteArrayList<>(); 50 51 /** 52 * Listener for {@link RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)} 53 */ 54 public interface OnLayoutCompletedListener { onLayoutCompleted()55 void onLayoutCompleted(); 56 } 57 58 /** 59 * Adds a {@link OnLayoutCompletedListener} to receive a callback when {@link 60 * RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)} is called 61 */ addOnLayoutCompletedListener(OnLayoutCompletedListener listener)62 public void addOnLayoutCompletedListener(OnLayoutCompletedListener listener) { 63 mOnLayoutCompletedListeners.add(listener); 64 } 65 66 /** 67 * Removes a {@link OnLayoutCompletedListener} to not receive a callback when {@link 68 * RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)} is called 69 */ removeOnLayoutCompletedListener(OnLayoutCompletedListener listener)70 public void removeOnLayoutCompletedListener(OnLayoutCompletedListener listener) { 71 mOnLayoutCompletedListeners.remove(listener); 72 } 73 74 AllAppsGridAdapter(T activityContext, LayoutInflater inflater, AlphabeticalAppsList apps, SearchAdapterProvider<?> adapterProvider)75 public AllAppsGridAdapter(T activityContext, LayoutInflater inflater, 76 AlphabeticalAppsList apps, SearchAdapterProvider<?> adapterProvider) { 77 super(activityContext, inflater, apps, adapterProvider); 78 mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext); 79 mGridLayoutMgr.setSpanSizeLookup(new GridSpanSizer()); 80 setAppsPerRow(activityContext.getDeviceProfile().numShownAllAppsColumns); 81 } 82 83 /** 84 * Returns the grid layout manager. 85 */ getLayoutManager()86 public AppsGridLayoutManager getLayoutManager() { 87 return mGridLayoutMgr; 88 } 89 90 /** @return the column index that the given adapter index falls. */ getSpanIndex(int adapterIndex)91 public int getSpanIndex(int adapterIndex) { 92 AppsGridLayoutManager lm = getLayoutManager(); 93 return lm.getSpanSizeLookup().getSpanIndex(adapterIndex, lm.getSpanCount()); 94 } 95 96 /** 97 * A subclass of GridLayoutManager that overrides accessibility values during app search. 98 */ 99 public class AppsGridLayoutManager extends ScrollableLayoutManager { 100 AppsGridLayoutManager(Context context)101 public AppsGridLayoutManager(Context context) { 102 super(context); 103 } 104 105 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)106 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 107 super.onInitializeAccessibilityEvent(event); 108 109 // Ensure that we only report the number apps for accessibility not including other 110 // adapter views 111 final AccessibilityRecordCompat record = AccessibilityEventCompat 112 .asRecord(event); 113 record.setItemCount(mApps.getNumFilteredApps()); 114 record.setFromIndex(Math.max(0, 115 record.getFromIndex() - getRowsNotForAccessibility(record.getFromIndex()))); 116 record.setToIndex(Math.max(0, 117 record.getToIndex() - getRowsNotForAccessibility(record.getToIndex()))); 118 } 119 120 @Override getRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)121 public int getRowCountForAccessibility(RecyclerView.Recycler recycler, 122 RecyclerView.State state) { 123 return super.getRowCountForAccessibility(recycler, state) - 124 getRowsNotForAccessibility(mApps.getAdapterItems().size() - 1); 125 } 126 127 @Override getColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)128 public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, 129 RecyclerView.State state) { 130 return mAppsPerRow; 131 } 132 133 @Override onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info)134 public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, 135 RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) { 136 super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info); 137 138 ViewGroup.LayoutParams lp = host.getLayoutParams(); 139 AccessibilityNodeInfoCompat.CollectionItemInfoCompat cic = info.getCollectionItemInfo(); 140 if (!(lp instanceof LayoutParams) || (cic == null)) { 141 return; 142 } 143 LayoutParams glp = (LayoutParams) lp; 144 info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain( 145 cic.getRowIndex() - getRowsNotForAccessibility(glp.getViewAdapterPosition()), 146 cic.getRowSpan(), 147 cic.getColumnIndex(), 148 cic.getColumnSpan(), 149 cic.isHeading(), 150 cic.isSelected())); 151 } 152 153 /** 154 * Returns the number of rows before {@param adapterPosition}, including this position 155 * which should not be counted towards the collection info. 156 */ getRowsNotForAccessibility(int adapterPosition)157 private int getRowsNotForAccessibility(int adapterPosition) { 158 List<AdapterItem> items = mApps.getAdapterItems(); 159 adapterPosition = Math.max(adapterPosition, items.size() - 1); 160 int extraRows = 0; 161 for (int i = 0; i <= adapterPosition && i < items.size(); i++) { 162 if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_ICON)) { 163 extraRows++; 164 } 165 } 166 return extraRows; 167 } 168 169 @Override onLayoutCompleted(RecyclerView.State state)170 public void onLayoutCompleted(RecyclerView.State state) { 171 super.onLayoutCompleted(state); 172 for (OnLayoutCompletedListener listener : mOnLayoutCompletedListeners) { 173 listener.onLayoutCompleted(); 174 } 175 } 176 177 @Override incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos)178 protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) { 179 AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position); 180 // only account for the first icon in the row since they are the same size within a row 181 return (isIconViewType(item.viewType) && item.rowAppIndex != 0) 182 ? heightUntilLastPos 183 : (heightUntilLastPos + mCachedSizes.get(item.viewType)); 184 } 185 } 186 187 @Override setAppsPerRow(int appsPerRow)188 public void setAppsPerRow(int appsPerRow) { 189 mAppsPerRow = appsPerRow; 190 int totalSpans = mAppsPerRow; 191 for (int itemPerRow : mAdapterProvider.getSupportedItemsPerRowArray()) { 192 if (totalSpans % itemPerRow != 0) { 193 totalSpans *= itemPerRow; 194 } 195 } 196 mGridLayoutMgr.setSpanCount(totalSpans); 197 } 198 199 /** 200 * Helper class to size the grid items. 201 */ 202 public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup { 203 GridSpanSizer()204 public GridSpanSizer() { 205 super(); 206 setSpanIndexCacheEnabled(true); 207 } 208 209 @Override getSpanSize(int position)210 public int getSpanSize(int position) { 211 int totalSpans = mGridLayoutMgr.getSpanCount(); 212 List<AdapterItem> items = mApps.getAdapterItems(); 213 if (position >= items.size()) { 214 return totalSpans; 215 } 216 int viewType = items.get(position).viewType; 217 if (isIconViewType(viewType)) { 218 return totalSpans / mAppsPerRow; 219 } else { 220 if (mAdapterProvider.isViewSupported(viewType)) { 221 return totalSpans / mAdapterProvider.getItemsPerRow(viewType, mAppsPerRow); 222 } 223 224 // Section breaks span the full width 225 return totalSpans; 226 } 227 } 228 } 229 } 230