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