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