• 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.content.Intent;
20 import android.content.res.Resources;
21 import android.support.v4.view.accessibility.AccessibilityEventCompat;
22 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
23 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
24 import android.support.v7.widget.GridLayoutManager;
25 import android.support.v7.widget.RecyclerView;
26 import android.view.Gravity;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.View.OnFocusChangeListener;
30 import android.view.ViewConfiguration;
31 import android.view.ViewGroup;
32 import android.view.accessibility.AccessibilityEvent;
33 import android.widget.TextView;
34 
35 import com.android.launcher3.AppInfo;
36 import com.android.launcher3.BubbleTextView;
37 import com.android.launcher3.Launcher;
38 import com.android.launcher3.R;
39 import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
40 import com.android.launcher3.compat.UserManagerCompat;
41 import com.android.launcher3.touch.ItemClickHandler;
42 import com.android.launcher3.touch.ItemLongClickListener;
43 import com.android.launcher3.util.PackageManagerHelper;
44 
45 import java.util.List;
46 
47 /**
48  * The grid view adapter of all the apps.
49  */
50 public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
51 
52     public static final String TAG = "AppsGridAdapter";
53 
54     // A normal icon
55     public static final int VIEW_TYPE_ICON = 1 << 1;
56     // The message shown when there are no filtered results
57     public static final int VIEW_TYPE_EMPTY_SEARCH = 1 << 2;
58     // The message to continue to a market search when there are no filtered results
59     public static final int VIEW_TYPE_SEARCH_MARKET = 1 << 3;
60 
61     // We use various dividers for various purposes.  They share enough attributes to reuse layouts,
62     // but differ in enough attributes to require different view types
63 
64     // A divider that separates the apps list and the search market button
65     public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
66     public static final int VIEW_TYPE_WORK_TAB_FOOTER = 1 << 5;
67 
68     // Common view type masks
69     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
70     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
71 
72 
73     public interface BindViewCallback {
onBindView(ViewHolder holder)74         void onBindView(ViewHolder holder);
75     }
76 
77     /**
78      * ViewHolder for each icon.
79      */
80     public static class ViewHolder extends RecyclerView.ViewHolder {
81 
ViewHolder(View v)82         public ViewHolder(View v) {
83             super(v);
84         }
85     }
86 
87     /**
88      * A subclass of GridLayoutManager that overrides accessibility values during app search.
89      */
90     public class AppsGridLayoutManager extends GridLayoutManager {
91 
AppsGridLayoutManager(Context context)92         public AppsGridLayoutManager(Context context) {
93             super(context, 1, GridLayoutManager.VERTICAL, false);
94         }
95 
96         @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)97         public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
98             super.onInitializeAccessibilityEvent(event);
99 
100             // Ensure that we only report the number apps for accessibility not including other
101             // adapter views
102             final AccessibilityRecordCompat record = AccessibilityEventCompat
103                     .asRecord(event);
104             record.setItemCount(mApps.getNumFilteredApps());
105             record.setFromIndex(Math.max(0,
106                     record.getFromIndex() - getRowsNotForAccessibility(record.getFromIndex())));
107             record.setToIndex(Math.max(0,
108                     record.getToIndex() - getRowsNotForAccessibility(record.getToIndex())));
109         }
110 
111         @Override
getRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)112         public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
113                 RecyclerView.State state) {
114             return super.getRowCountForAccessibility(recycler, state) -
115                     getRowsNotForAccessibility(mApps.getAdapterItems().size() - 1);
116         }
117 
118         @Override
onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info)119         public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
120                 RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
121             super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
122 
123             ViewGroup.LayoutParams lp = host.getLayoutParams();
124             AccessibilityNodeInfoCompat.CollectionItemInfoCompat cic = info.getCollectionItemInfo();
125             if (!(lp instanceof LayoutParams) || (cic == null)) {
126                 return;
127             }
128             LayoutParams glp = (LayoutParams) lp;
129             info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
130                     cic.getRowIndex() - getRowsNotForAccessibility(glp.getViewAdapterPosition()),
131                     cic.getRowSpan(),
132                     cic.getColumnIndex(),
133                     cic.getColumnSpan(),
134                     cic.isHeading(),
135                     cic.isSelected()));
136         }
137 
138         /**
139          * Returns the number of rows before {@param adapterPosition}, including this position
140          * which should not be counted towards the collection info.
141          */
getRowsNotForAccessibility(int adapterPosition)142         private int getRowsNotForAccessibility(int adapterPosition) {
143             List<AdapterItem> items = mApps.getAdapterItems();
144             adapterPosition = Math.max(adapterPosition, mApps.getAdapterItems().size() - 1);
145             int extraRows = 0;
146             for (int i = 0; i <= adapterPosition; i++) {
147                 if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_ICON)) {
148                     extraRows++;
149                 }
150             }
151             return extraRows;
152         }
153     }
154 
155     /**
156      * Helper class to size the grid items.
157      */
158     public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
159 
GridSpanSizer()160         public GridSpanSizer() {
161             super();
162             setSpanIndexCacheEnabled(true);
163         }
164 
165         @Override
getSpanSize(int position)166         public int getSpanSize(int position) {
167             if (isIconViewType(mApps.getAdapterItems().get(position).viewType)) {
168                 return 1;
169             } else {
170                 // Section breaks span the full width
171                 return mAppsPerRow;
172             }
173         }
174     }
175 
176     private final Launcher mLauncher;
177     private final LayoutInflater mLayoutInflater;
178     private final AlphabeticalAppsList mApps;
179     private final GridLayoutManager mGridLayoutMgr;
180     private final GridSpanSizer mGridSizer;
181 
182     private final int mAppsPerRow;
183 
184     private BindViewCallback mBindViewCallback;
185     private OnFocusChangeListener mIconFocusListener;
186 
187     // The text to show when there are no search results and no market search handler.
188     private String mEmptySearchMessage;
189     // The intent to send off to the market app, updated each time the search query changes.
190     private Intent mMarketSearchIntent;
191 
AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps)192     public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps) {
193         Resources res = launcher.getResources();
194         mLauncher = launcher;
195         mApps = apps;
196         mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
197         mGridSizer = new GridSpanSizer();
198         mGridLayoutMgr = new AppsGridLayoutManager(launcher);
199         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
200         mLayoutInflater = LayoutInflater.from(launcher);
201 
202         mAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
203         mGridLayoutMgr.setSpanCount(mAppsPerRow);
204     }
205 
isDividerViewType(int viewType)206     public static boolean isDividerViewType(int viewType) {
207         return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
208     }
209 
isIconViewType(int viewType)210     public static boolean isIconViewType(int viewType) {
211         return isViewType(viewType, VIEW_TYPE_MASK_ICON);
212     }
213 
isViewType(int viewType, int viewTypeMask)214     public static boolean isViewType(int viewType, int viewTypeMask) {
215         return (viewType & viewTypeMask) != 0;
216     }
217 
setIconFocusListener(OnFocusChangeListener focusListener)218     public void setIconFocusListener(OnFocusChangeListener focusListener) {
219         mIconFocusListener = focusListener;
220     }
221 
222     /**
223      * Sets the last search query that was made, used to show when there are no results and to also
224      * seed the intent for searching the market.
225      */
setLastSearchQuery(String query)226     public void setLastSearchQuery(String query) {
227         Resources res = mLauncher.getResources();
228         mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query);
229         mMarketSearchIntent = PackageManagerHelper.getMarketSearchIntent(mLauncher, query);
230     }
231 
232     /**
233      * Sets the callback for when views are bound.
234      */
setBindViewCallback(BindViewCallback cb)235     public void setBindViewCallback(BindViewCallback cb) {
236         mBindViewCallback = cb;
237     }
238 
239     /**
240      * Returns the grid layout manager.
241      */
getLayoutManager()242     public GridLayoutManager getLayoutManager() {
243         return mGridLayoutMgr;
244     }
245 
246     @Override
onCreateViewHolder(ViewGroup parent, int viewType)247     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
248         switch (viewType) {
249             case VIEW_TYPE_ICON:
250                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
251                         R.layout.all_apps_icon, parent, false);
252                 icon.setOnClickListener(ItemClickHandler.INSTANCE);
253                 icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
254                 icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout());
255                 icon.setOnFocusChangeListener(mIconFocusListener);
256 
257                 // Ensure the all apps icon height matches the workspace icons in portrait mode.
258                 icon.getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
259                 return new ViewHolder(icon);
260             case VIEW_TYPE_EMPTY_SEARCH:
261                 return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
262                         parent, false));
263             case VIEW_TYPE_SEARCH_MARKET:
264                 View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
265                         parent, false);
266                 searchMarketView.setOnClickListener(new View.OnClickListener() {
267                     @Override
268                     public void onClick(View v) {
269                         mLauncher.startActivitySafely(v, mMarketSearchIntent, null);
270                     }
271                 });
272                 return new ViewHolder(searchMarketView);
273             case VIEW_TYPE_ALL_APPS_DIVIDER:
274                 return new ViewHolder(mLayoutInflater.inflate(
275                         R.layout.all_apps_divider, parent, false));
276             case VIEW_TYPE_WORK_TAB_FOOTER:
277                 View footer = mLayoutInflater.inflate(R.layout.work_tab_footer, parent, false);
278                 return new ViewHolder(footer);
279             default:
280                 throw new RuntimeException("Unexpected view type");
281         }
282     }
283 
284     @Override
onBindViewHolder(ViewHolder holder, int position)285     public void onBindViewHolder(ViewHolder holder, int position) {
286         switch (holder.getItemViewType()) {
287             case VIEW_TYPE_ICON:
288                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
289                 BubbleTextView icon = (BubbleTextView) holder.itemView;
290                 icon.reset();
291                 icon.applyFromApplicationInfo(info);
292                 break;
293             case VIEW_TYPE_EMPTY_SEARCH:
294                 TextView emptyViewText = (TextView) holder.itemView;
295                 emptyViewText.setText(mEmptySearchMessage);
296                 emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
297                         Gravity.START | Gravity.CENTER_VERTICAL);
298                 break;
299             case VIEW_TYPE_SEARCH_MARKET:
300                 TextView searchView = (TextView) holder.itemView;
301                 if (mMarketSearchIntent != null) {
302                     searchView.setVisibility(View.VISIBLE);
303                 } else {
304                     searchView.setVisibility(View.GONE);
305                 }
306                 break;
307             case VIEW_TYPE_ALL_APPS_DIVIDER:
308                 // nothing to do
309                 break;
310             case VIEW_TYPE_WORK_TAB_FOOTER:
311                 WorkModeSwitch workModeToggle = holder.itemView.findViewById(R.id.work_mode_toggle);
312                 workModeToggle.refresh();
313                 TextView managedByLabel = holder.itemView.findViewById(R.id.managed_by_label);
314                 boolean anyProfileQuietModeEnabled = UserManagerCompat.getInstance(
315                         managedByLabel.getContext()).isAnyProfileQuietModeEnabled();
316                 managedByLabel.setText(anyProfileQuietModeEnabled
317                         ? R.string.work_mode_off_label : R.string.work_mode_on_label);
318                 break;
319         }
320         if (mBindViewCallback != null) {
321             mBindViewCallback.onBindView(holder);
322         }
323     }
324 
325     @Override
onFailedToRecycleView(ViewHolder holder)326     public boolean onFailedToRecycleView(ViewHolder holder) {
327         // Always recycle and we will reset the view when it is bound
328         return true;
329     }
330 
331     @Override
getItemCount()332     public int getItemCount() {
333         return mApps.getAdapterItems().size();
334     }
335 
336     @Override
getItemViewType(int position)337     public int getItemViewType(int position) {
338         AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
339         return item.viewType;
340     }
341 
342 }
343