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