/*
* 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 edu.gatech.oad.rocket.findmythings.list;
import android.os.*;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
/**
* In Android parlance, a filter constrains data by a filtering pattern.
*
* This class extends the Android filter paradigm with custom, parametrized constraints.
* <p>Filtering operations 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.</p>
*
* @see android.widget.Filter
* @see android.widget.Filterable
*/
public abstract class CustomFilter<T, U extends CustomFilter.Constraint<T>> {
private static final String LOG_TAG = "CustomFilter";
private static final String THREAD_NAME = "CustomFilter";
private static final int FILTER_TOKEN = 0xD0D0F00D;
private static final int FINISH_TOKEN = 0xDEADBEEF;
private RequestHandler<T, U> mThreadHandler;
private ResultsHandler<T, U> mResultsHandler;
private final Object mLock = new Object();
/**
* An object describing how to filter the given parametrized object.
*/
public static abstract class Constraint<T> implements Parcelable {
public abstract boolean isEmpty();
}
/**
* <p>Creates a new asynchronous filter.</p>
*/
public CustomFilter() {
mResultsHandler = new ResultsHandler<T, U>(this);
}
/**
* <p>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.</p>
*
* @param constraint the constraint used to filter the data
*/
public final void filter(U constraint) {
filter(constraint, null);
}
/**
* <p>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.</p>
*
* <p>Upon completion, the listener is notified.</p>
*
* @param constraint the constraint used to filter the data
* @param listener a listener notified upon completion of the operation
*/
public final void filter(U 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<T, U>(this, thread.getLooper());
}
final long delay = 0;
Message message = mThreadHandler.obtainMessage(FILTER_TOKEN);
message.obj = new RequestArguments<T, U>(constraint, listener);
mThreadHandler.removeMessages(FILTER_TOKEN);
mThreadHandler.removeMessages(FINISH_TOKEN);
mThreadHandler.sendMessageDelayed(message, delay);
}
}
/**
* <p>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 List} that
* will then be published in the UI thread through publishResults()</p>
*
* <p><strong>Contract:</strong> When the constraint is null, the original
* data must be restored.</p>
*
* @param constraint the constraint used to filter the data
* @return the results of the filtering operation
*/
protected abstract List<T> performFiltering(U constraint);
/**
* <p>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}.</p>
*
* @param results the results of the filtering operation
* @param constraint the constraint used to filter the data
*/
protected abstract void publishResults(List<T> results, U constraint);
/**
* <p>Listener used to receive a notification upon completion of a filtering
* operation.</p>
*/
public static interface FilterListener {
/**
* <p>Notifies the end of a filtering operation.</p>
*
* @param count the number of values computed by the filter
*/
public void onFilterComplete(int count);
}
/**
* <p>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.</p>
*/
private static class RequestHandler<T, U extends CustomFilter.Constraint<T>> extends Handler {
WeakReference<CustomFilter<T, U>> mFilter;
RequestHandler(CustomFilter<T, U> filter, Looper looper) {
super(looper);
mFilter = new WeakReference<CustomFilter<T, U>>(filter);
}
/**
* <p>Handles filtering requests by calling
* {@link CustomFilter#performFiltering} and then sending a message
* with the results to the results handler.</p>
*
* @param msg the filtering request
*/
@SuppressWarnings("unchecked")
public void handleMessage(Message msg) {
CustomFilter<T, U> filt = mFilter.get();
if (filt == null) return;
int what = msg.what;
Message message;
switch (what) {
case FILTER_TOKEN:
RequestArguments<T, U> args = (RequestArguments<T, U>) msg.obj;
try {
args.results = filt.performFiltering(args.constraint);
} catch (Exception e) {
args.results = Collections.emptyList();
Log.w(LOG_TAG, "An exception occurred during performFiltering()!", e);
} finally {
message = filt.mResultsHandler.obtainMessage(what);
message.obj = args;
message.sendToTarget();
}
synchronized (filt.mLock) {
if (filt.mThreadHandler != null) {
Message finishMessage = filt.mThreadHandler.obtainMessage(FINISH_TOKEN);
filt.mThreadHandler.sendMessageDelayed(finishMessage, 3000);
}
}
break;
case FINISH_TOKEN:
synchronized (filt.mLock) {
if (filt.mThreadHandler != null) {
filt.mThreadHandler.getLooper().quit();
filt.mThreadHandler = null;
}
}
break;
}
}
}
/**
* <p>Handles the results of a filtering operation. The results are
* handled in the UI thread.</p>
*/
private static class ResultsHandler<T, U extends CustomFilter.Constraint<T>> extends Handler {
WeakReference<CustomFilter<T, U>> mFilter;
ResultsHandler(CustomFilter<T, U> filter) {
mFilter = new WeakReference<CustomFilter<T, U>>(filter);
}
/**
* <p>Messages received from the request handler are processed in the
* UI thread. The processing involves calling
* publishResults(T, FilterResults)
* to post the results back in the UI and then notifying the listener,
* if any.</p>
*
* @param msg the filtering results
*/
@Override @SuppressWarnings("unchecked")
public void handleMessage(Message msg) {
RequestArguments<T, U> args = (RequestArguments<T, U>) msg.obj;
CustomFilter<T, U> filt = mFilter.get();
if (filt != null) {
filt.publishResults(args.results, args.constraint);
if (args.listener != null) {
int count = args.results != null ? args.results.size() : -1;
args.listener.onFilterComplete(count);
}
}
}
}
/**
* <p>Holds the arguments of a filtering request as well as the results
* of the request.</p>
*/
private static class RequestArguments<V, W> {
public RequestArguments(W constraint, FilterListener listener) {
this.constraint = constraint;
this.listener = listener;
}
/**
* <p>The constraint used to filter the data.</p>
*/
W constraint;
/**
* <p>The listener to notify upon completion. Can be null.</p>
*/
FilterListener listener;
/**
* <p>The results of the filtering operation.</p>
*/
List<V> results;
}
}