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