blob: 497b6a32b0ee771790d138d5c3203b4d931d4465 [file] [log] [blame]
/*
* Copyright (C) 2009 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 com.android.providers.contacts;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Log;
/**
* A scheduler for asynchronous aggregation of contacts. Aggregation will start after
* a short delay after it is scheduled, unless it is scheduled again, in which case the
* aggregation pass is delayed. There is an upper boundary on how long aggregation can
* be delayed.
*/
public class ContactAggregationScheduler {
private static final String TAG = "ContactAggregator";
public interface Aggregator {
/**
* Performs an aggregation run.
*/
void run();
/**
* Interrupts aggregation.
*/
void interrupt();
}
// Message ID used for communication with the aggregator
private static final int START_AGGREGATION_MESSAGE_ID = 1;
// Aggregation is delayed by this many milliseconds to allow changes to accumulate
public static final int AGGREGATION_DELAY = 1000;
// Maximum delay of aggregation from the initial aggregation request
public static final int MAX_AGGREGATION_DELAY = 10000;
// Minimum gap between requests that should cause a delay of aggregation
public static final int DELAYED_EXECUTION_TIMEOUT = 500;
public static final int STATUS_STAND_BY = 0;
public static final int STATUS_SCHEDULED = 1;
public static final int STATUS_RUNNING = 2;
public static final int STATUS_INTERRUPTED = 3;
private Aggregator mAggregator;
// Aggregation status
private int mStatus = STATUS_STAND_BY;
// If true, we need to automatically reschedule aggregation after the current pass is done
private boolean mRescheduleWhenComplete;
// The time when aggregation was requested for the first time.
// Reset when aggregation is completed
private long mInitialRequestTimestamp;
// Last time aggregation was requested
private long mLastAggregationEndedTimestamp;
private HandlerThread mHandlerThread;
private Handler mMessageHandler;
public void setAggregator(Aggregator aggregator) {
mAggregator = aggregator;
}
public void start() {
mHandlerThread = new HandlerThread("ContactAggregator", Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mMessageHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case START_AGGREGATION_MESSAGE_ID:
run();
break;
default:
throw new IllegalStateException("Unhandled message: " + msg.what);
}
}
};
}
public void stop() {
mAggregator.interrupt();
Looper looper = mHandlerThread.getLooper();
if (looper != null) {
looper.quit();
}
}
/**
* Schedules an aggregation pass after a short delay.
*/
public synchronized void schedule() {
switch (mStatus) {
case STATUS_STAND_BY: {
mInitialRequestTimestamp = currentTime();
mStatus = STATUS_SCHEDULED;
if (mInitialRequestTimestamp - mLastAggregationEndedTimestamp <
DELAYED_EXECUTION_TIMEOUT) {
runDelayed();
} else {
runNow();
}
break;
}
case STATUS_INTERRUPTED: {
// If the previous aggregation run was interrupted, do not reset
// the initial request timestamp - we don't want a continuous string
// of interrupted runs
mStatus = STATUS_SCHEDULED;
runDelayed();
break;
}
case STATUS_SCHEDULED: {
// If it has been less than MAX_AGGREGATION_DELAY millis since the initial request,
// reschedule the request.
if (currentTime() - mInitialRequestTimestamp < MAX_AGGREGATION_DELAY) {
runDelayed();
}
break;
}
case STATUS_RUNNING: {
// If it has been less than MAX_AGGREGATION_DELAY millis since the initial request,
// interrupt the current pass and reschedule the request.
if (currentTime() - mInitialRequestTimestamp < MAX_AGGREGATION_DELAY) {
mAggregator.interrupt();
mStatus = STATUS_INTERRUPTED;
}
mRescheduleWhenComplete = true;
break;
}
}
}
/**
* Called just before an aggregation pass begins.
*/
public void run() {
synchronized (this) {
mStatus = STATUS_RUNNING;
mRescheduleWhenComplete = false;
}
try {
mAggregator.run();
} finally {
mLastAggregationEndedTimestamp = currentTime();
synchronized (this) {
if (mStatus == STATUS_RUNNING) {
mStatus = STATUS_STAND_BY;
}
if (mRescheduleWhenComplete) {
mRescheduleWhenComplete = false;
schedule();
} else {
Log.w(TAG, "No more aggregation requests");
}
}
}
}
/* package */ void runNow() {
// If aggregation has already been requested, cancel the previous request
mMessageHandler.removeMessages(START_AGGREGATION_MESSAGE_ID);
// Schedule aggregation for right now
mMessageHandler.sendEmptyMessage(START_AGGREGATION_MESSAGE_ID);
}
/* package */ void runDelayed() {
// If aggregation has already been requested, cancel the previous request
mMessageHandler.removeMessages(START_AGGREGATION_MESSAGE_ID);
// Schedule aggregation for AGGREGATION_DELAY milliseconds from now
mMessageHandler.sendEmptyMessageDelayed(
START_AGGREGATION_MESSAGE_ID, AGGREGATION_DELAY);
}
/* package */ long currentTime() {
return System.currentTimeMillis();
}
}