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