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