blob: d9015402732757977e8cbc59ef95e06bc521afc0 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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
17package android.widget;
18
19import android.os.Handler;
20import android.os.HandlerThread;
21import android.os.Looper;
22import android.os.Message;
23import android.util.Log;
24
25/**
26 * <p>A filter constrains data with a filtering pattern.</p>
27 *
28 * <p>Filters are usually created by {@link android.widget.Filterable}
29 * classes.</p>
30 *
31 * <p>Filtering operations performed by calling {@link #filter(CharSequence)} or
32 * {@link #filter(CharSequence, android.widget.Filter.FilterListener)} are
33 * performed asynchronously. When these methods are called, a filtering request
34 * is posted in a request queue and processed later. Any call to one of these
35 * methods will cancel any previous non-executed filtering request.</p>
36 *
37 * @see android.widget.Filterable
38 */
39public abstract class Filter {
40 private static final String LOG_TAG = "Filter";
41
42 private static final String THREAD_NAME = "Filter";
43 private static final int FILTER_TOKEN = 0xD0D0F00D;
44 private static final int FINISH_TOKEN = 0xDEADBEEF;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070045
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 private Handler mThreadHandler;
47 private Handler mResultHandler;
48
Karl Rosaen8bf92e02009-07-16 11:57:59 -070049 private Delayer mDelayer;
50
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070051 private final Object mLock = new Object();
52
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053 /**
54 * <p>Creates a new asynchronous filter.</p>
55 */
56 public Filter() {
57 mResultHandler = new ResultsHandler();
58 }
59
60 /**
Karl Rosaen8bf92e02009-07-16 11:57:59 -070061 * Provide an interface that decides how long to delay the message for a given query. Useful
62 * for heuristics such as posting a delay for the delete key to avoid doing any work while the
63 * user holds down the delete key.
64 *
65 * @param delayer The delayer.
66 * @hide
67 */
68 public void setDelayer(Delayer delayer) {
69 synchronized (mLock) {
70 mDelayer = delayer;
71 }
72 }
73
74 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 * <p>Starts an asynchronous filtering operation. Calling this method
76 * cancels all previous non-executed filtering requests and posts a new
77 * filtering request that will be executed later.</p>
78 *
79 * @param constraint the constraint used to filter the data
80 *
81 * @see #filter(CharSequence, android.widget.Filter.FilterListener)
82 */
83 public final void filter(CharSequence constraint) {
84 filter(constraint, null);
85 }
86
87 /**
88 * <p>Starts an asynchronous filtering operation. Calling this method
89 * cancels all previous non-executed filtering requests and posts a new
90 * filtering request that will be executed later.</p>
91 *
92 * <p>Upon completion, the listener is notified.</p>
93 *
94 * @param constraint the constraint used to filter the data
95 * @param listener a listener notified upon completion of the operation
96 *
97 * @see #filter(CharSequence)
98 * @see #performFiltering(CharSequence)
99 * @see #publishResults(CharSequence, android.widget.Filter.FilterResults)
100 */
101 public final void filter(CharSequence constraint, FilterListener listener) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700102 synchronized (mLock) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 if (mThreadHandler == null) {
Karl Rosaenab3ef102009-07-15 10:09:56 -0700104 HandlerThread thread = new HandlerThread(
105 THREAD_NAME, android.os.Process.THREAD_PRIORITY_BACKGROUND);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 thread.start();
107 mThreadHandler = new RequestHandler(thread.getLooper());
108 }
Karl Rosaen8bf92e02009-07-16 11:57:59 -0700109
110 final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
112 Message message = mThreadHandler.obtainMessage(FILTER_TOKEN);
113
114 RequestArguments args = new RequestArguments();
115 // make sure we use an immutable copy of the constraint, so that
116 // it doesn't change while the filter operation is in progress
117 args.constraint = constraint != null ? constraint.toString() : null;
118 args.listener = listener;
119 message.obj = args;
120
121 mThreadHandler.removeMessages(FILTER_TOKEN);
122 mThreadHandler.removeMessages(FINISH_TOKEN);
Karl Rosaen8bf92e02009-07-16 11:57:59 -0700123 mThreadHandler.sendMessageDelayed(message, delay);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 }
125 }
126
127 /**
128 * <p>Invoked in a worker thread to filter the data according to the
129 * constraint. Subclasses must implement this method to perform the
130 * filtering operation. Results computed by the filtering operation
131 * must be returned as a {@link android.widget.Filter.FilterResults} that
132 * will then be published in the UI thread through
133 * {@link #publishResults(CharSequence,
134 * android.widget.Filter.FilterResults)}.</p>
135 *
136 * <p><strong>Contract:</strong> When the constraint is null, the original
137 * data must be restored.</p>
138 *
139 * @param constraint the constraint used to filter the data
140 * @return the results of the filtering operation
141 *
142 * @see #filter(CharSequence, android.widget.Filter.FilterListener)
143 * @see #publishResults(CharSequence, android.widget.Filter.FilterResults)
144 * @see android.widget.Filter.FilterResults
145 */
146 protected abstract FilterResults performFiltering(CharSequence constraint);
147
148 /**
149 * <p>Invoked in the UI thread to publish the filtering results in the
150 * user interface. Subclasses must implement this method to display the
151 * results computed in {@link #performFiltering}.</p>
152 *
153 * @param constraint the constraint used to filter the data
154 * @param results the results of the filtering operation
155 *
156 * @see #filter(CharSequence, android.widget.Filter.FilterListener)
157 * @see #performFiltering(CharSequence)
158 * @see android.widget.Filter.FilterResults
159 */
160 protected abstract void publishResults(CharSequence constraint,
161 FilterResults results);
162
163 /**
164 * <p>Converts a value from the filtered set into a CharSequence. Subclasses
165 * should override this method to convert their results. The default
166 * implementation returns an empty String for null values or the default
167 * String representation of the value.</p>
168 *
169 * @param resultValue the value to convert to a CharSequence
170 * @return a CharSequence representing the value
171 */
172 public CharSequence convertResultToString(Object resultValue) {
173 return resultValue == null ? "" : resultValue.toString();
174 }
175
176 /**
177 * <p>Holds the results of a filtering operation. The results are the values
178 * computed by the filtering operation and the number of these values.</p>
179 */
180 protected static class FilterResults {
181 public FilterResults() {
182 // nothing to see here
183 }
184
185 /**
186 * <p>Contains all the values computed by the filtering operation.</p>
187 */
188 public Object values;
189
190 /**
191 * <p>Contains the number of values computed by the filtering
192 * operation.</p>
193 */
194 public int count;
195 }
196
197 /**
198 * <p>Listener used to receive a notification upon completion of a filtering
199 * operation.</p>
200 */
201 public static interface FilterListener {
202 /**
203 * <p>Notifies the end of a filtering operation.</p>
204 *
205 * @param count the number of values computed by the filter
206 */
207 public void onFilterComplete(int count);
208 }
209
210 /**
211 * <p>Worker thread handler. When a new filtering request is posted from
212 * {@link android.widget.Filter#filter(CharSequence, android.widget.Filter.FilterListener)},
213 * it is sent to this handler.</p>
214 */
215 private class RequestHandler extends Handler {
216 public RequestHandler(Looper looper) {
217 super(looper);
218 }
219
220 /**
221 * <p>Handles filtering requests by calling
222 * {@link Filter#performFiltering} and then sending a message
223 * with the results to the results handler.</p>
224 *
225 * @param msg the filtering request
226 */
227 public void handleMessage(Message msg) {
228 int what = msg.what;
229 Message message;
230 switch (what) {
231 case FILTER_TOKEN:
232 RequestArguments args = (RequestArguments) msg.obj;
233 try {
234 args.results = performFiltering(args.constraint);
235 } catch (Exception e) {
236 args.results = new FilterResults();
237 Log.w(LOG_TAG, "An exception occured during performFiltering()!", e);
238 } finally {
239 message = mResultHandler.obtainMessage(what);
240 message.obj = args;
241 message.sendToTarget();
242 }
243
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700244 synchronized (mLock) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 if (mThreadHandler != null) {
246 Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN);
247 mThreadHandler.sendMessageDelayed(finishMessage, 3000);
248 }
249 }
250 break;
251 case FINISH_TOKEN:
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700252 synchronized (mLock) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 if (mThreadHandler != null) {
254 mThreadHandler.getLooper().quit();
255 mThreadHandler = null;
256 }
257 }
258 break;
259 }
260 }
261 }
262
263 /**
264 * <p>Handles the results of a filtering operation. The results are
265 * handled in the UI thread.</p>
266 */
267 private class ResultsHandler extends Handler {
268 /**
269 * <p>Messages received from the request handler are processed in the
270 * UI thread. The processing involves calling
271 * {@link Filter#publishResults(CharSequence,
272 * android.widget.Filter.FilterResults)}
273 * to post the results back in the UI and then notifying the listener,
274 * if any.</p>
275 *
276 * @param msg the filtering results
277 */
278 @Override
279 public void handleMessage(Message msg) {
280 RequestArguments args = (RequestArguments) msg.obj;
281
282 publishResults(args.constraint, args.results);
283 if (args.listener != null) {
284 int count = args.results != null ? args.results.count : -1;
285 args.listener.onFilterComplete(count);
286 }
287 }
288 }
289
290 /**
291 * <p>Holds the arguments of a filtering request as well as the results
292 * of the request.</p>
293 */
294 private static class RequestArguments {
295 /**
296 * <p>The constraint used to filter the data.</p>
297 */
298 CharSequence constraint;
299
300 /**
301 * <p>The listener to notify upon completion. Can be null.</p>
302 */
303 FilterListener listener;
304
305 /**
306 * <p>The results of the filtering operation.</p>
307 */
308 FilterResults results;
309 }
Karl Rosaen8bf92e02009-07-16 11:57:59 -0700310
311 /**
312 * @hide
313 */
314 public interface Delayer {
315
316 /**
317 * @param constraint The constraint passed to {@link Filter#filter(CharSequence)}
318 * @return The delay that should be used for
319 * {@link Handler#sendMessageDelayed(android.os.Message, long)}
320 */
321 long getPostingDelay(CharSequence constraint);
322 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323}