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 onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info)128 public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, 129 RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) { 130 super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info); 131 132 ViewGroup.LayoutParams lp = host.getLayoutParams(); 133 AccessibilityNodeInfoCompat.CollectionItemInfoCompat cic = info.getCollectionItemInfo(); 134 if (!(lp instanceof LayoutParams) || (cic == null)) { 135 return; 136 } 137 LayoutParams glp = (LayoutParams) lp; 138 info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain( 139 cic.getRowIndex() - getRowsNotForAccessibility(glp.getViewAdapterPosition()), 140 cic.getRowSpan(), 141 cic.getColumnIndex(), 142 cic.getColumnSpan(), 143 cic.isHeading(), 144 cic.isSelected())); 145 } 146 147 /** 148 * Returns the number of rows before {@param adapterPosition}, including this position 149 * which should not be counted towards the collection info. 150 */ getRowsNotForAccessibility(int adapterPosition)151 private int getRowsNotForAccessibility(int adapterPosition) { 152 List<AdapterItem> items = mApps.getAdapterItems(); 153 adapterPosition = Math.max(adapterPosition, items.size() - 1); 154 int extraRows = 0; 155 for (int i = 0; i <= adapterPosition && i < items.size(); i++) { 156 if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_ICON)) { 157 extraRows++; 158 } 159 } 160 return extraRows; 161 } 162 163 @Override onLayoutCompleted(RecyclerView.State state)164 public void onLayoutCompleted(RecyclerView.State state) { 165 super.onLayoutCompleted(state); 166 for (OnLayoutCompletedListener listener : mOnLayoutCompletedListeners) { 167 listener.onLayoutCompleted(); 168 } 169 } 170 171 @Override incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos)172 protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) { 173 AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position); 174 // only account for the first icon in the row since they are the same size within a row 175 return (isIconViewType(item.viewType) && item.rowAppIndex != 0) 176 ? heightUntilLastPos 177 : (heightUntilLastPos + mCachedSizes.get(item.viewType)); 178 } 179 } 180 181 @Override setAppsPerRow(int appsPerRow)182 public void setAppsPerRow(int appsPerRow) { 183 mAppsPerRow = appsPerRow; 184 int totalSpans = mAppsPerRow; 185 for (int itemPerRow : mAdapterProvider.getSupportedItemsPerRowArray()) { 186 if (totalSpans % itemPerRow != 0) { 187 totalSpans *= itemPerRow; 188 } 189 } 190 mGridLayoutMgr.setSpanCount(totalSpans); 191 } 192 193 /** 194 * Helper class to size the grid items. 195 */ 196 public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup { 197 GridSpanSizer()198 public GridSpanSizer() { 199 super(); 200 setSpanIndexCacheEnabled(true); 201 } 202 203 @Override getSpanSize(int position)204 public int getSpanSize(int position) { 205 int totalSpans = mGridLayoutMgr.getSpanCount(); 206 List<AdapterItem> items = mApps.getAdapterItems(); 207 if (position >= items.size()) { 208 return totalSpans; 209 } 210 int viewType = items.get(position).viewType; 211 if (isIconViewType(viewType)) { 212 return totalSpans / mAppsPerRow; 213 } else { 214 if (mAdapterProvider.isViewSupported(viewType)) { 215 return totalSpans / mAdapterProvider.getItemsPerRow(viewType, mAppsPerRow); 216 } 217 218 // Section breaks span the full width 219 return totalSpans; 220 } 221 } 222 } 223 } 224