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