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