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