• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.widget;
18 
19 import android.os.Handler;
20 import android.os.HandlerThread;
21 import android.os.Looper;
22 import android.os.Message;
23 import 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  */
39 public 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;
45 
46     private Handler mThreadHandler;
47     private Handler mResultHandler;
48 
49     private Delayer mDelayer;
50 
51     private final Object mLock = new Object();
52 
53     /**
54      * <p>Creates a new asynchronous filter.</p>
55      */
Filter()56     public Filter() {
57         mResultHandler = new ResultsHandler();
58     }
59 
60     /**
61      * 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      */
setDelayer(Delayer delayer)68     public void setDelayer(Delayer delayer) {
69         synchronized (mLock) {
70             mDelayer = delayer;
71         }
72     }
73 
74     /**
75      * <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      */
filter(CharSequence constraint)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      */
filter(CharSequence constraint, FilterListener listener)101     public final void filter(CharSequence constraint, FilterListener listener) {
102         synchronized (mLock) {
103             if (mThreadHandler == null) {
104                 HandlerThread thread = new HandlerThread(
105                         THREAD_NAME, android.os.Process.THREAD_PRIORITY_BACKGROUND);
106                 thread.start();
107                 mThreadHandler = new RequestHandler(thread.getLooper());
108             }
109 
110             final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint);
111 
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);
123             mThreadHandler.sendMessageDelayed(message, delay);
124         }
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      */
performFiltering(CharSequence constraint)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      */
publishResults(CharSequence constraint, FilterResults results)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      */
convertResultToString(Object resultValue)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 {
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          */
onFilterComplete(int count)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 {
RequestHandler(Looper looper)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          */
handleMessage(Message msg)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 
244                     synchronized (mLock) {
245                         if (mThreadHandler != null) {
246                             Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN);
247                             mThreadHandler.sendMessageDelayed(finishMessage, 3000);
248                         }
249                     }
250                     break;
251                 case FINISH_TOKEN:
252                     synchronized (mLock) {
253                         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
handleMessage(Message msg)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     }
310 
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          */
getPostingDelay(CharSequence constraint)321         long getPostingDelay(CharSequence constraint);
322     }
323 }
324