Filter.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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 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; 45 46 private Handler mThreadHandler; 47 private Handler mResultHandler; 48 49 /** 50 * <p>Creates a new asynchronous filter.</p> 51 */ 52 public Filter() { 53 mResultHandler = new ResultsHandler(); 54 } 55 56 /** 57 * <p>Starts an asynchronous filtering operation. Calling this method 58 * cancels all previous non-executed filtering requests and posts a new 59 * filtering request that will be executed later.</p> 60 * 61 * @param constraint the constraint used to filter the data 62 * 63 * @see #filter(CharSequence, android.widget.Filter.FilterListener) 64 */ 65 public final void filter(CharSequence constraint) { 66 filter(constraint, null); 67 } 68 69 /** 70 * <p>Starts an asynchronous filtering operation. Calling this method 71 * cancels all previous non-executed filtering requests and posts a new 72 * filtering request that will be executed later.</p> 73 * 74 * <p>Upon completion, the listener is notified.</p> 75 * 76 * @param constraint the constraint used to filter the data 77 * @param listener a listener notified upon completion of the operation 78 * 79 * @see #filter(CharSequence) 80 * @see #performFiltering(CharSequence) 81 * @see #publishResults(CharSequence, android.widget.Filter.FilterResults) 82 */ 83 public final void filter(CharSequence constraint, FilterListener listener) { 84 synchronized (this) { 85 86 if (mThreadHandler == null) { 87 HandlerThread thread = new HandlerThread(THREAD_NAME); 88 thread.start(); 89 mThreadHandler = new RequestHandler(thread.getLooper()); 90 } 91 92 Message message = mThreadHandler.obtainMessage(FILTER_TOKEN); 93 94 RequestArguments args = new RequestArguments(); 95 // make sure we use an immutable copy of the constraint, so that 96 // it doesn't change while the filter operation is in progress 97 args.constraint = constraint != null ? constraint.toString() : null; 98 args.listener = listener; 99 message.obj = args; 100 101 mThreadHandler.removeMessages(FILTER_TOKEN); 102 mThreadHandler.removeMessages(FINISH_TOKEN); 103 mThreadHandler.sendMessage(message); 104 } 105 } 106 107 /** 108 * <p>Invoked in a worker thread to filter the data according to the 109 * constraint. Subclasses must implement this method to perform the 110 * filtering operation. Results computed by the filtering operation 111 * must be returned as a {@link android.widget.Filter.FilterResults} that 112 * will then be published in the UI thread through 113 * {@link #publishResults(CharSequence, 114 * android.widget.Filter.FilterResults)}.</p> 115 * 116 * <p><strong>Contract:</strong> When the constraint is null, the original 117 * data must be restored.</p> 118 * 119 * @param constraint the constraint used to filter the data 120 * @return the results of the filtering operation 121 * 122 * @see #filter(CharSequence, android.widget.Filter.FilterListener) 123 * @see #publishResults(CharSequence, android.widget.Filter.FilterResults) 124 * @see android.widget.Filter.FilterResults 125 */ 126 protected abstract FilterResults performFiltering(CharSequence constraint); 127 128 /** 129 * <p>Invoked in the UI thread to publish the filtering results in the 130 * user interface. Subclasses must implement this method to display the 131 * results computed in {@link #performFiltering}.</p> 132 * 133 * @param constraint the constraint used to filter the data 134 * @param results the results of the filtering operation 135 * 136 * @see #filter(CharSequence, android.widget.Filter.FilterListener) 137 * @see #performFiltering(CharSequence) 138 * @see android.widget.Filter.FilterResults 139 */ 140 protected abstract void publishResults(CharSequence constraint, 141 FilterResults results); 142 143 /** 144 * <p>Converts a value from the filtered set into a CharSequence. Subclasses 145 * should override this method to convert their results. The default 146 * implementation returns an empty String for null values or the default 147 * String representation of the value.</p> 148 * 149 * @param resultValue the value to convert to a CharSequence 150 * @return a CharSequence representing the value 151 */ 152 public CharSequence convertResultToString(Object resultValue) { 153 return resultValue == null ? "" : resultValue.toString(); 154 } 155 156 /** 157 * <p>Holds the results of a filtering operation. The results are the values 158 * computed by the filtering operation and the number of these values.</p> 159 */ 160 protected static class FilterResults { 161 public FilterResults() { 162 // nothing to see here 163 } 164 165 /** 166 * <p>Contains all the values computed by the filtering operation.</p> 167 */ 168 public Object values; 169 170 /** 171 * <p>Contains the number of values computed by the filtering 172 * operation.</p> 173 */ 174 public int count; 175 } 176 177 /** 178 * <p>Listener used to receive a notification upon completion of a filtering 179 * operation.</p> 180 */ 181 public static interface FilterListener { 182 /** 183 * <p>Notifies the end of a filtering operation.</p> 184 * 185 * @param count the number of values computed by the filter 186 */ 187 public void onFilterComplete(int count); 188 } 189 190 /** 191 * <p>Worker thread handler. When a new filtering request is posted from 192 * {@link android.widget.Filter#filter(CharSequence, android.widget.Filter.FilterListener)}, 193 * it is sent to this handler.</p> 194 */ 195 private class RequestHandler extends Handler { 196 public RequestHandler(Looper looper) { 197 super(looper); 198 } 199 200 /** 201 * <p>Handles filtering requests by calling 202 * {@link Filter#performFiltering} and then sending a message 203 * with the results to the results handler.</p> 204 * 205 * @param msg the filtering request 206 */ 207 public void handleMessage(Message msg) { 208 int what = msg.what; 209 Message message; 210 switch (what) { 211 case FILTER_TOKEN: 212 RequestArguments args = (RequestArguments) msg.obj; 213 try { 214 args.results = performFiltering(args.constraint); 215 } catch (Exception e) { 216 args.results = new FilterResults(); 217 Log.w(LOG_TAG, "An exception occured during performFiltering()!", e); 218 } finally { 219 message = mResultHandler.obtainMessage(what); 220 message.obj = args; 221 message.sendToTarget(); 222 } 223 224 synchronized (this) { 225 if (mThreadHandler != null) { 226 Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN); 227 mThreadHandler.sendMessageDelayed(finishMessage, 3000); 228 } 229 } 230 break; 231 case FINISH_TOKEN: 232 synchronized (this) { 233 if (mThreadHandler != null) { 234 mThreadHandler.getLooper().quit(); 235 mThreadHandler = null; 236 } 237 } 238 break; 239 } 240 } 241 } 242 243 /** 244 * <p>Handles the results of a filtering operation. The results are 245 * handled in the UI thread.</p> 246 */ 247 private class ResultsHandler extends Handler { 248 /** 249 * <p>Messages received from the request handler are processed in the 250 * UI thread. The processing involves calling 251 * {@link Filter#publishResults(CharSequence, 252 * android.widget.Filter.FilterResults)} 253 * to post the results back in the UI and then notifying the listener, 254 * if any.</p> 255 * 256 * @param msg the filtering results 257 */ 258 @Override 259 public void handleMessage(Message msg) { 260 RequestArguments args = (RequestArguments) msg.obj; 261 262 publishResults(args.constraint, args.results); 263 if (args.listener != null) { 264 int count = args.results != null ? args.results.count : -1; 265 args.listener.onFilterComplete(count); 266 } 267 } 268 } 269 270 /** 271 * <p>Holds the arguments of a filtering request as well as the results 272 * of the request.</p> 273 */ 274 private static class RequestArguments { 275 /** 276 * <p>The constraint used to filter the data.</p> 277 */ 278 CharSequence constraint; 279 280 /** 281 * <p>The listener to notify upon completion. Can be null.</p> 282 */ 283 FilterListener listener; 284 285 /** 286 * <p>The results of the filtering operation.</p> 287 */ 288 FilterResults results; 289 } 290} 291