RecurringRunner.java revision 633eb826b8c97731dfc5ef12c7bf78a63734275d
1/* 2 * Copyright (C) 2015 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 17package com.android.tv.util; 18 19import android.content.Context; 20import android.content.SharedPreferences; 21import android.os.AsyncTask; 22import android.os.Handler; 23import android.support.annotation.WorkerThread; 24import android.util.Log; 25 26import com.android.tv.common.SharedPreferencesUtils; 27import com.android.tv.common.SoftPreconditions; 28 29import java.util.Date; 30 31/** 32 * Repeatedly executes a {@link Runnable}. 33 * 34 * <p>The next execution time is saved to a {@link SharedPreferences}, and used on the next start. 35 * The given {@link Runnable} will run in the main thread. 36 */ 37public final class RecurringRunner { 38 private static final String TAG = "RecurringRunner"; 39 private static final boolean DEBUG = false; 40 41 private final Handler mHandler; 42 private final long mIntervalMs; 43 private final Runnable mRunnable; 44 private final Runnable mOnStopRunnable; 45 private final Context mContext; 46 private final String mName; 47 private boolean mRunning; 48 49 public RecurringRunner(Context context, long intervalMs, Runnable runnable, 50 Runnable onStopRunnable) { 51 mContext = context.getApplicationContext(); 52 mRunnable = runnable; 53 mOnStopRunnable = onStopRunnable; 54 mIntervalMs = intervalMs; 55 mName = runnable.getClass().getCanonicalName(); 56 if (DEBUG) Log.i(TAG, " Delaying " + mName + " " + (intervalMs / 1000.0) + " seconds"); 57 mHandler = new Handler(mContext.getMainLooper()); 58 } 59 60 public void start(boolean resetNextRunTime) { 61 SoftPreconditions.checkState(!mRunning, TAG, mName + " start is called twice."); 62 if (mRunning) { 63 return; 64 } 65 mRunning = true; 66 if (resetNextRunTime) { 67 resetNextRunTime(); 68 } 69 new AsyncTask<Void, Void, Long>() { 70 @Override 71 protected Long doInBackground(Void... params) { 72 return getNextRunTime(); 73 } 74 75 @Override 76 protected void onPostExecute(Long nextRunTime) { 77 postAt(nextRunTime); 78 } 79 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 80 } 81 82 public void start() { 83 start(false); 84 } 85 86 public void stop() { 87 mRunning = false; 88 mHandler.removeCallbacksAndMessages(null); 89 if (mOnStopRunnable != null) { 90 mOnStopRunnable.run(); 91 } 92 } 93 94 private void postAt(long next) { 95 if (!mRunning) { 96 return; 97 } 98 long now = System.currentTimeMillis(); 99 // Run it anyways even if it is in the past 100 if (DEBUG) Log.i(TAG, "Next run of " + mName + " at " + new Date(next)); 101 long delay = Math.max(next - now, 0); 102 boolean posted = mHandler.postDelayed(new Runnable() { 103 @Override 104 public void run() { 105 try { 106 if (DEBUG) Log.i(TAG, "Starting " + mName); 107 mRunnable.run(); 108 } catch (Exception e) { 109 Log.w(TAG, "Error running " + mName, e); 110 } 111 postAt(resetNextRunTime()); 112 } 113 }, delay); 114 if (!posted) { 115 Log.w(TAG, "Scheduling a future run of " + mName + " at " + new Date(next) + "failed"); 116 } 117 if (DEBUG) Log.i(TAG, "Actual delay of " + mName + " is " + (delay / 1000.0) + " seconds."); 118 } 119 120 private SharedPreferences getSharedPreferences() { 121 return mContext.getSharedPreferences(SharedPreferencesUtils.SHARED_PREF_RECURRING_RUNNER, 122 Context.MODE_PRIVATE); 123 } 124 125 @WorkerThread 126 private long getNextRunTime() { 127 // The access to SharedPreferences is done by an AsyncTask thread because 128 // SharedPreferences reads to disk at first time. 129 long next = getSharedPreferences().getLong(mName, System.currentTimeMillis()); 130 if (next > System.currentTimeMillis() + mIntervalMs) { 131 next = resetNextRunTime(); 132 } 133 return next; 134 } 135 136 private long resetNextRunTime() { 137 long next = System.currentTimeMillis() + mIntervalMs; 138 getSharedPreferences().edit().putLong(mName, next).apply(); 139 return next; 140 } 141} 142