1e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein/* 2e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * Copyright (C) 2010 The Android Open Source Project 3e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * 4e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License"); 5e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * you may not use this file except in compliance with the License. 6e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * You may obtain a copy of the License at 7e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * 8e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * http://www.apache.org/licenses/LICENSE-2.0 9e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * 10e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * Unless required by applicable law or agreed to in writing, software 11e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS, 12e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * See the License for the specific language governing permissions and 14e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * limitations under the License. 15e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein */ 16e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 17e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sappersteinpackage com.android.mail.utils; 18e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 19e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sappersteinimport android.os.Handler; 20e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sappersteinimport android.util.Log; 21e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 22e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sappersteinimport java.util.Timer; 23e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sappersteinimport java.util.TimerTask; 24e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 25e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein/** 26e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * This class used to "throttle" a flow of events. 27e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * 28e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * When {@link #onEvent()} is called, it calls the callback in a certain timeout later. 29e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * Initially {@link #mMinTimeout} is used as the timeout, but if it gets multiple {@link #onEvent} 30e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * calls in a certain amount of time, it extends the timeout, until it reaches {@link #mMaxTimeout}. 31e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * 32e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * This class is primarily used to throttle content changed events. 33e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein */ 34e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sappersteinpublic class Throttle { 35e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public static final boolean DEBUG = false; // Don't submit with true 36e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 37e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public static final int DEFAULT_MIN_TIMEOUT = 150; 38e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public static final int DEFAULT_MAX_TIMEOUT = 2500; 3984acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie // exposed for testing 4084acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie public static final int TIMEOUT_EXTEND_INTERVAL = 500; 41e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 42e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private static final String LOG_TAG = LogTag.getLogTag(); 43e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 44e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private static Timer TIMER = new Timer(); 45e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 46e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private final Clock mClock; 47e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private final Timer mTimer; 48e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 49e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Name of the instance. Only for logging. */ 50e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private final String mName; 51e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 52e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Handler for UI thread. */ 53e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private final Handler mHandler; 54e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 55e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Callback to be called */ 56e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private final Runnable mCallback; 57e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 58e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Minimum (default) timeout, in milliseconds. */ 59e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private final int mMinTimeout; 60e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 61e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Max timeout, in milliseconds. */ 62e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private final int mMaxTimeout; 63e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 64e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Current timeout, in milliseconds. */ 65e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private int mTimeout; 66e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 67e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** When {@link #onEvent()} was last called. */ 68e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private long mLastEventTime; 69e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 70e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private MyTimerTask mRunningTimerTask; 71e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 72e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Constructor with default timeout */ 73e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public Throttle(String name, Runnable callback, Handler handler) { 74e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein this(name, callback, handler, DEFAULT_MIN_TIMEOUT, DEFAULT_MAX_TIMEOUT); 75e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 76e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 77e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Constructor that takes custom timeout */ 78e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public Throttle(String name, Runnable callback, Handler handler,int minTimeout, 79e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein int maxTimeout) { 80e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein this(name, callback, handler, minTimeout, maxTimeout, Clock.INSTANCE, TIMER); 81e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 82e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 83e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** Constructor for tests */ 8484acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie // exposed for testing 8584acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie public Throttle(String name, Runnable callback, Handler handler,int minTimeout, 86e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein int maxTimeout, Clock clock, Timer timer) { 87e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (maxTimeout < minTimeout) { 88e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein throw new IllegalArgumentException(); 89e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 90e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mName = name; 91e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mCallback = callback; 92e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mClock = clock; 93e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mTimer = timer; 94e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mHandler = handler; 95e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mMinTimeout = minTimeout; 96e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mMaxTimeout = maxTimeout; 97e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mTimeout = mMinTimeout; 98e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 99e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 100e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private void debugLog(String message) { 101e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein Log.d(LOG_TAG, "Throttle: [" + mName + "] " + message); 102e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 103e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 104e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private boolean isCallbackScheduled() { 105e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein return mRunningTimerTask != null; 106e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 107e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 108e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public void cancelScheduledCallback() { 109e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (mRunningTimerTask != null) { 110e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (DEBUG) debugLog("Canceling scheduled callback"); 111e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mRunningTimerTask.cancel(); 112e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mRunningTimerTask = null; 113e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 114e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 115e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 11684acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie // exposed for testing 11784acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie public void updateTimeout() { 118e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein final long now = mClock.getTime(); 119e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if ((now - mLastEventTime) <= TIMEOUT_EXTEND_INTERVAL) { 120e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mTimeout *= 2; 121e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (mTimeout >= mMaxTimeout) { 122e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mTimeout = mMaxTimeout; 123e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 124e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (DEBUG) debugLog("Timeout extended " + mTimeout); 125e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } else { 126e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mTimeout = mMinTimeout; 127e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (DEBUG) debugLog("Timeout reset to " + mTimeout); 128e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 129e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 130e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mLastEventTime = now; 131e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 132e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 133e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public void onEvent() { 134e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (DEBUG) debugLog("onEvent"); 135e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 136e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein updateTimeout(); 137e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 138e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (isCallbackScheduled()) { 139e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (DEBUG) debugLog(" callback already scheduled"); 140e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } else { 141e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (DEBUG) debugLog(" scheduling callback"); 142e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mRunningTimerTask = new MyTimerTask(); 143e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mTimer.schedule(mRunningTimerTask, mTimeout); 144e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 145e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 146e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 147e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein /** 148e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein * Timer task called on timeout, 149e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein */ 150e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private class MyTimerTask extends TimerTask { 151e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private boolean mCanceled; 152e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 153e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein @Override 154e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public void run() { 155e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mHandler.post(new HandlerRunnable()); 156e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 157e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 158e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein @Override 159e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public boolean cancel() { 160e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mCanceled = true; 161e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein return super.cancel(); 162e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 163e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 164e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein private class HandlerRunnable implements Runnable { 165e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein @Override 166e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein public void run() { 167e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mRunningTimerTask = null; 168e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (!mCanceled) { // This check has to be done on the UI thread. 169e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein if (DEBUG) debugLog("Kicking callback"); 170e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein mCallback.run(); 171e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 172e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 173e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 174e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 175e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 17684acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie // exposed for testing 17784acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie public int getTimeoutForTest() { 178e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein return mTimeout; 179e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 180e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein 18184acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie // exposed for testing 18284acd3404a5aa9bbbf27fdc943119c4ed794ea5aJerry Xie public long getLastEventTimeForTest() { 183e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein return mLastEventTime; 184e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein } 185e45fd11ab9d362fbe3e47a7f62b77c498ee3c2caAndrew Sapperstein} 186