/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log; /** *

A filter constrains data with a filtering pattern.

* *

Filters are usually created by {@link android.widget.Filterable} * classes.

* *

Filtering operations performed by calling {@link #filter(CharSequence)} or * {@link #filter(CharSequence, android.widget.Filter.FilterListener)} are * performed asynchronously. When these methods are called, a filtering request * is posted in a request queue and processed later. Any call to one of these * methods will cancel any previous non-executed filtering request.

* * @see android.widget.Filterable */ public abstract class Filter { private static final String LOG_TAG = "Filter"; private static final String THREAD_NAME = "Filter"; private static final int FILTER_TOKEN = 0xD0D0F00D; private static final int FINISH_TOKEN = 0xDEADBEEF; private Handler mThreadHandler; private Handler mResultHandler; private Delayer mDelayer; private final Object mLock = new Object(); /** *

Creates a new asynchronous filter.

*/ public Filter() { mResultHandler = new ResultsHandler(); } /** * Provide an interface that decides how long to delay the message for a given query. Useful * for heuristics such as posting a delay for the delete key to avoid doing any work while the * user holds down the delete key. * * @param delayer The delayer. * @hide */ public void setDelayer(Delayer delayer) { synchronized (mLock) { mDelayer = delayer; } } /** *

Starts an asynchronous filtering operation. Calling this method * cancels all previous non-executed filtering requests and posts a new * filtering request that will be executed later.

* * @param constraint the constraint used to filter the data * * @see #filter(CharSequence, android.widget.Filter.FilterListener) */ public final void filter(CharSequence constraint) { filter(constraint, null); } /** *

Starts an asynchronous filtering operation. Calling this method * cancels all previous non-executed filtering requests and posts a new * filtering request that will be executed later.

* *

Upon completion, the listener is notified.

* * @param constraint the constraint used to filter the data * @param listener a listener notified upon completion of the operation * * @see #filter(CharSequence) * @see #performFiltering(CharSequence) * @see #publishResults(CharSequence, android.widget.Filter.FilterResults) */ public final void filter(CharSequence constraint, FilterListener listener) { synchronized (mLock) { if (mThreadHandler == null) { HandlerThread thread = new HandlerThread( THREAD_NAME, android.os.Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mThreadHandler = new RequestHandler(thread.getLooper()); } final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint); Message message = mThreadHandler.obtainMessage(FILTER_TOKEN); RequestArguments args = new RequestArguments(); // make sure we use an immutable copy of the constraint, so that // it doesn't change while the filter operation is in progress args.constraint = constraint != null ? constraint.toString() : null; args.listener = listener; message.obj = args; mThreadHandler.removeMessages(FILTER_TOKEN); mThreadHandler.removeMessages(FINISH_TOKEN); mThreadHandler.sendMessageDelayed(message, delay); } } /** *

Invoked in a worker thread to filter the data according to the * constraint. Subclasses must implement this method to perform the * filtering operation. Results computed by the filtering operation * must be returned as a {@link android.widget.Filter.FilterResults} that * will then be published in the UI thread through * {@link #publishResults(CharSequence, * android.widget.Filter.FilterResults)}.

* *

Contract: When the constraint is null, the original * data must be restored.

* * @param constraint the constraint used to filter the data * @return the results of the filtering operation * * @see #filter(CharSequence, android.widget.Filter.FilterListener) * @see #publishResults(CharSequence, android.widget.Filter.FilterResults) * @see android.widget.Filter.FilterResults */ protected abstract FilterResults performFiltering(CharSequence constraint); /** *

Invoked in the UI thread to publish the filtering results in the * user interface. Subclasses must implement this method to display the * results computed in {@link #performFiltering}.

* * @param constraint the constraint used to filter the data * @param results the results of the filtering operation * * @see #filter(CharSequence, android.widget.Filter.FilterListener) * @see #performFiltering(CharSequence) * @see android.widget.Filter.FilterResults */ protected abstract void publishResults(CharSequence constraint, FilterResults results); /** *

Converts a value from the filtered set into a CharSequence. Subclasses * should override this method to convert their results. The default * implementation returns an empty String for null values or the default * String representation of the value.

* * @param resultValue the value to convert to a CharSequence * @return a CharSequence representing the value */ public CharSequence convertResultToString(Object resultValue) { return resultValue == null ? "" : resultValue.toString(); } /** *

Holds the results of a filtering operation. The results are the values * computed by the filtering operation and the number of these values.

*/ protected static class FilterResults { public FilterResults() { // nothing to see here } /** *

Contains all the values computed by the filtering operation.

*/ public Object values; /** *

Contains the number of values computed by the filtering * operation.

*/ public int count; } /** *

Listener used to receive a notification upon completion of a filtering * operation.

*/ public static interface FilterListener { /** *

Notifies the end of a filtering operation.

* * @param count the number of values computed by the filter */ public void onFilterComplete(int count); } /** *

Worker thread handler. When a new filtering request is posted from * {@link android.widget.Filter#filter(CharSequence, android.widget.Filter.FilterListener)}, * it is sent to this handler.

*/ private class RequestHandler extends Handler { public RequestHandler(Looper looper) { super(looper); } /** *

Handles filtering requests by calling * {@link Filter#performFiltering} and then sending a message * with the results to the results handler.

* * @param msg the filtering request */ public void handleMessage(Message msg) { int what = msg.what; Message message; switch (what) { case FILTER_TOKEN: RequestArguments args = (RequestArguments) msg.obj; try { args.results = performFiltering(args.constraint); } catch (Exception e) { args.results = new FilterResults(); Log.w(LOG_TAG, "An exception occured during performFiltering()!", e); } finally { message = mResultHandler.obtainMessage(what); message.obj = args; message.sendToTarget(); } synchronized (mLock) { if (mThreadHandler != null) { Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN); mThreadHandler.sendMessageDelayed(finishMessage, 3000); } } break; case FINISH_TOKEN: synchronized (mLock) { if (mThreadHandler != null) { mThreadHandler.getLooper().quit(); mThreadHandler = null; } } break; } } } /** *

Handles the results of a filtering operation. The results are * handled in the UI thread.

*/ private class ResultsHandler extends Handler { /** *

Messages received from the request handler are processed in the * UI thread. The processing involves calling * {@link Filter#publishResults(CharSequence, * android.widget.Filter.FilterResults)} * to post the results back in the UI and then notifying the listener, * if any.

* * @param msg the filtering results */ @Override public void handleMessage(Message msg) { RequestArguments args = (RequestArguments) msg.obj; publishResults(args.constraint, args.results); if (args.listener != null) { int count = args.results != null ? args.results.count : -1; args.listener.onFilterComplete(count); } } } /** *

Holds the arguments of a filtering request as well as the results * of the request.

*/ private static class RequestArguments { /** *

The constraint used to filter the data.

*/ CharSequence constraint; /** *

The listener to notify upon completion. Can be null.

*/ FilterListener listener; /** *

The results of the filtering operation.

*/ FilterResults results; } /** * @hide */ public interface Delayer { /** * @param constraint The constraint passed to {@link Filter#filter(CharSequence)} * @return The delay that should be used for * {@link Handler#sendMessageDelayed(android.os.Message, long)} */ long getPostingDelay(CharSequence constraint); } }