1 /*
2  * Copyright (C) 2014 Google Inc.
3  * Licensed to The Android Open Source Project.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.mail.ui;
19 
20 import android.app.SearchManager;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.net.Uri;
24 import android.os.AsyncTask;
25 import android.util.AttributeSet;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.AdapterView;
30 import android.widget.BaseAdapter;
31 import android.widget.ImageView;
32 import android.widget.LinearLayout;
33 import android.widget.ListView;
34 import android.widget.TextView;
35 
36 import com.android.mail.R;
37 import com.android.mail.providers.SearchRecentSuggestionsProvider;
38 import com.google.common.collect.Lists;
39 
40 import java.util.List;
41 
42 /**
43  * Custom quantum-styled search view that overlays the main activity.
44  */
45 public class MaterialSearchSuggestionsList extends LinearLayout
46         implements AdapterView.OnItemClickListener, View.OnClickListener {
47     private MaterialSearchViewController mController;
48     private SearchRecentSuggestionsProvider mSuggestionsProvider;
49     private List<SuggestionItem> mSuggestions = Lists.newArrayList();
50     private String mQuery;
51 
52     private MaterialSearchViewListAdapter mAdapter;
53     private QuerySuggestionsTask mQueryTask;
54 
MaterialSearchSuggestionsList(Context context)55     public MaterialSearchSuggestionsList(Context context) {
56         super(context);
57     }
58 
MaterialSearchSuggestionsList(Context context, AttributeSet attrs)59     public MaterialSearchSuggestionsList(Context context, AttributeSet attrs) {
60         super(context, attrs);
61     }
62 
63     // PUBLIC API
setController(MaterialSearchViewController controller, SearchRecentSuggestionsProvider suggestionsProvider)64     public void setController(MaterialSearchViewController controller,
65             SearchRecentSuggestionsProvider suggestionsProvider) {
66         mController = controller;
67         mSuggestionsProvider = suggestionsProvider;
68     }
69 
setQuery(String query)70     public void setQuery(String query) {
71         mQuery = query;
72         if (mQueryTask != null) {
73             mQueryTask.cancel(true);
74         }
75         mQueryTask = new QuerySuggestionsTask();
76         mQueryTask.execute(query);
77     }
78 
79     // PRIVATE API
80     @Override
onFinishInflate()81     protected void onFinishInflate() {
82         super.onFinishInflate();
83 
84         final ListView listView = (ListView) findViewById(R.id.search_overlay_suggestion_list);
85         listView.setOnItemClickListener(this);
86         final View dummyHolder = findViewById(R.id.search_overlay_scrim);
87         dummyHolder.setOnClickListener(this);
88 
89         // set up the adapter
90         mAdapter = new MaterialSearchViewListAdapter(getContext(), R.layout.search_suggestion_item);
91         listView.setAdapter(mAdapter);
92     }
93 
94     @Override
setVisibility(int visibility)95     public void setVisibility(int visibility) {
96         if (!isShown() && visibility == VISIBLE) {
97             // When we go from gone to visible, re-query for suggestions in case they changed.
98             setQuery(mQuery);
99         }
100         super.setVisibility(visibility);
101     }
102 
103     @Override
onItemClick(AdapterView<?> adapterView, View view, int position, long id)104     public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
105         mController.onSearchPerformed(mSuggestions.get(position).suggestion);
106     }
107 
108     @Override
onClick(View view)109     public void onClick(View view) {
110         mController.showSearchActionBar(
111                 MaterialSearchViewController.SEARCH_VIEW_STATE_ONLY_ACTIONBAR);
112     }
113 
114     // Background task for querying the suggestions list
115     private class QuerySuggestionsTask extends AsyncTask<String, Void, List<SuggestionItem>> {
116         @Override
doInBackground(String... strings)117         protected List<SuggestionItem> doInBackground(String... strings) {
118             String query = strings[0];
119             if (query == null) {
120                 query = "";
121             }
122 
123             Cursor c = null;
124             final List<SuggestionItem> result = Lists.newArrayList();
125             try {
126                 c = mSuggestionsProvider.query(query);
127 
128                 if (c != null && c.moveToFirst()) {
129                     final int textIndex = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
130                     final int iconIndex = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
131                     do {
132                         final String suggestion = c.getString(textIndex);
133                         final Uri iconUri = Uri.parse(c.getString(iconIndex));
134                         result.add(new SuggestionItem(suggestion, iconUri));
135                     } while (c.moveToNext());
136                 }
137             } finally {
138                 if (c != null) {
139                     c.close();
140                 }
141             }
142 
143             return result;
144         }
145 
146         @Override
onPostExecute(List<SuggestionItem> strings)147         protected void onPostExecute(List<SuggestionItem> strings) {
148             if (!isCancelled()) {
149                 // Should not have any race conditions here since we cancel the previous asynctask
150                 // before starting the new one. It's unlikely that the new task finishes fast enough
151                 // to get to onPostExecute when this one is in addAll.
152                 mSuggestions.clear();
153                 mSuggestions.addAll(strings);
154                 mAdapter.notifyDataSetChanged();
155             }
156         }
157     }
158 
159     private static class SuggestionItem {
160         final String suggestion;
161         final Uri icon;
162 
SuggestionItem(String s, Uri i)163         public SuggestionItem(String s, Uri i) {
164             suggestion = s;
165             icon = i;
166         }
167     }
168 
169     // Custom adapter to populate our list
170     private class MaterialSearchViewListAdapter extends BaseAdapter {
171         private final Context mContext;
172         private final int mResId;
173         private LayoutInflater mInflater;
174 
MaterialSearchViewListAdapter(Context context, int resource)175         public MaterialSearchViewListAdapter(Context context, int resource) {
176             super();
177             mContext = context;
178             mResId = resource;
179         }
180 
getInflater()181         private LayoutInflater getInflater() {
182             if (mInflater == null) {
183                 mInflater = LayoutInflater.from(mContext);
184             }
185             return mInflater;
186         }
187 
188         @Override
getCount()189         public int getCount() {
190             return mSuggestions.size();
191         }
192 
193         @Override
getItem(int i)194         public Object getItem(int i) {
195             return mSuggestions.get(i);
196         }
197 
198         @Override
getItemId(int i)199         public long getItemId(int i) {
200             return 0;
201         }
202 
203         @Override
getView(int position, View convertView, ViewGroup parent)204         public View getView(int position, View convertView, ViewGroup parent) {
205             if (convertView == null) {
206                 convertView = getInflater().inflate(mResId, parent, false);
207             }
208 
209             final SuggestionItem item = mSuggestions.get(position);
210             final TextView text =
211                     (TextView) convertView.findViewById(R.id.search_overlay_item_text);
212             text.setText(item.suggestion);
213             text.setContentDescription(getResources().getString(R.string.search_suggestion_desc,
214                     item.suggestion));
215             ((ImageView) convertView.findViewById(R.id.search_overlay_item_icon))
216                     .setImageURI(item.icon);
217 
218             return convertView;
219         }
220     }
221 }
222