• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.car.carlauncher;
17 
18 import android.app.Activity;
19 import android.view.View;
20 import android.view.ViewGroup;
21 import android.widget.Filter;
22 import android.widget.Filterable;
23 import android.widget.ImageView;
24 import android.widget.TextView;
25 
26 import androidx.annotation.NonNull;
27 import androidx.recyclerview.widget.RecyclerView;
28 
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.regex.Pattern;
33 
34 /**
35  * The search result adapter that binds the filtered result to a list view.
36  */
37 
38 final class SearchResultAdapter
39         extends RecyclerView.Adapter<SearchResultAdapter.ViewHolder> implements Filterable {
40     private final Activity mContext;
41     private List<AppMetaData> mAllApps;
42     private List<AppMetaData> mSearchResults;
43     private AppSearchFilter filter;
44     private boolean mIsDistractionOptimizationRequired;
45 
SearchResultAdapter(Activity context)46     public SearchResultAdapter(Activity context) {
47         mContext = context;
48         mSearchResults = null;
49     }
50 
setIsDistractionOptimizationRequired(boolean isDistractionOptimizationRequired)51     public void setIsDistractionOptimizationRequired(boolean isDistractionOptimizationRequired) {
52         mIsDistractionOptimizationRequired = isDistractionOptimizationRequired;
53         notifyDataSetChanged();
54     }
55 
setAllApps(List<AppMetaData> apps)56     public void setAllApps(List<AppMetaData> apps) {
57         mAllApps = apps;
58         notifyDataSetChanged();
59     }
60 
61     public static class ViewHolder extends RecyclerView.ViewHolder {
62         private View mAppItem;
63         private ImageView mAppIconView;
64         private TextView mAppNameView;
65 
ViewHolder(View view)66         public ViewHolder(View view) {
67             super(view);
68             mAppItem = view.findViewById(R.id.app_item);
69             mAppIconView = mAppItem.findViewById(R.id.app_icon);
70             mAppNameView = mAppItem.findViewById(R.id.app_name);
71         }
72 
bind(AppMetaData app, View.OnClickListener listener)73         public void bind(AppMetaData app, View.OnClickListener listener) {
74             if (app == null) {
75                 // Empty out the view
76                 mAppIconView.setImageDrawable(null);
77                 mAppNameView.setText(null);
78                 mAppItem.setClickable(false);
79                 mAppItem.setOnClickListener(null);
80             } else {
81                 mAppIconView.setImageDrawable(app.getIcon());
82                 mAppNameView.setText(app.getDisplayName());
83                 mAppItem.setClickable(true);
84                 mAppItem.setOnClickListener(listener);
85             }
86         }
87     }
88 
89     /**
90      * This filter does a simple case insensitive text match based on the application
91      * display name. e.g. "gm" -> "gmail".
92      * It only matches apps starting with the search query. It does not do sub-string matching.
93      */
94     private class AppSearchFilter extends Filter {
95 
96         @Override
performFiltering(CharSequence constraint)97         protected FilterResults performFiltering(CharSequence constraint) {
98             FilterResults results = new FilterResults();
99             if (constraint != null && constraint.length() > 0) {
100                 List filterList = new ArrayList();
101                 for (int i = 0; i < mAllApps.size(); i++) {
102                     AppMetaData app = mAllApps.get(i);
103                     if (shouldShowApp(
104                             app.getDisplayName(),
105                             constraint.toString(),
106                             app.getIsDistractionOptimized())) {
107                         filterList.add(app);
108                     }
109                 }
110                 Collections.sort(filterList, AppLauncherUtils.ALPHABETICAL_COMPARATOR);
111                 results.count = filterList.size();
112                 results.values = filterList;
113             } else {
114                 results.count = 0;
115                 results.values = null;
116             }
117 
118             return results;
119         }
120 
shouldShowApp( String displayName, String constraint, boolean isDistractionOptimized)121         private boolean shouldShowApp(
122                 String displayName, String constraint, boolean isDistractionOptimized) {
123             if (!mIsDistractionOptimizationRequired
124                     || (mIsDistractionOptimizationRequired && isDistractionOptimized)) {
125                 Pattern pattern = Pattern.compile(
126                         "^" + constraint + ".*$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
127                 return pattern.matcher(displayName).matches();
128             }
129             return false;
130         }
131 
132         @Override
publishResults(CharSequence constraint, FilterResults results)133         protected void publishResults(CharSequence constraint, FilterResults results) {
134             mSearchResults = (ArrayList) results.values;
135             notifyDataSetChanged();
136         }
137     }
138 
139     @Override
getFilter()140     public Filter getFilter() {
141         if (filter == null) {
142             filter = new AppSearchFilter();
143         }
144         return filter;
145     }
146 
147     @NonNull
148     @Override
onCreateViewHolder( @onNull ViewGroup parent, int viewType)149     public SearchResultAdapter.ViewHolder onCreateViewHolder(
150             @NonNull ViewGroup parent, int viewType) {
151         View view = mContext.getLayoutInflater().inflate(R.layout.app_search_result_item, null);
152         return new ViewHolder(view);
153     }
154 
155     @Override
onBindViewHolder(@onNull SearchResultAdapter.ViewHolder holder, int position)156     public void onBindViewHolder(@NonNull SearchResultAdapter.ViewHolder holder, int position) {
157         // Get the data item for this position
158         AppMetaData app = mSearchResults.get(position);
159         holder.bind(app, v -> AppLauncherUtils.launchApp(mContext, app));
160     }
161 
162     @Override
getItemId(int position)163     public long getItemId(int position) {
164         return position;
165     }
166 
167     @Override
getItemCount()168     public int getItemCount() {
169         if (mSearchResults != null) {
170             return mSearchResults.size();
171         } else {
172             return 0;
173         }
174     }
175 
clearResults()176     void clearResults() {
177         mSearchResults.clear();
178         notifyDataSetChanged();
179     }
180 }
181