• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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