Home | History | Annotate | Download | only in allapps
      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 {
     74         void onBindView(ViewHolder holder);
     75     }
     76 
     77     /**
     78      * ViewHolder for each icon.
     79      */
     80     public static class ViewHolder extends RecyclerView.ViewHolder {
     81 
     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 
     92         public AppsGridLayoutManager(Context context) {
     93             super(context, 1, GridLayoutManager.VERTICAL, false);
     94         }
     95 
     96         @Override
     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
    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
    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          */
    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 
    160         public GridSpanSizer() {
    161             super();
    162             setSpanIndexCacheEnabled(true);
    163         }
    164 
    165         @Override
    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 
    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 
    206     public static boolean isDividerViewType(int viewType) {
    207         return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
    208     }
    209 
    210     public static boolean isIconViewType(int viewType) {
    211         return isViewType(viewType, VIEW_TYPE_MASK_ICON);
    212     }
    213 
    214     public static boolean isViewType(int viewType, int viewTypeMask) {
    215         return (viewType & viewTypeMask) != 0;
    216     }
    217 
    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      */
    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      */
    235     public void setBindViewCallback(BindViewCallback cb) {
    236         mBindViewCallback = cb;
    237     }
    238 
    239     /**
    240      * Returns the grid layout manager.
    241      */
    242     public GridLayoutManager getLayoutManager() {
    243         return mGridLayoutMgr;
    244     }
    245 
    246     @Override
    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
    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
    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
    332     public int getItemCount() {
    333         return mApps.getAdapterItems().size();
    334     }
    335 
    336     @Override
    337     public int getItemViewType(int position) {
    338         AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
    339         return item.viewType;
    340     }
    341 
    342 }
    343