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 17 package com.android.tv.util; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.os.AsyncTask; 22 import android.os.Handler; 23 import android.support.annotation.WorkerThread; 24 import android.util.Log; 25 import com.android.tv.common.SoftPreconditions; 26 import com.android.tv.common.util.SharedPreferencesUtils; 27 import 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 */ 35 public 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 RecurringRunner( Context context, long intervalMs, Runnable runnable, Runnable onStopRunnable)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 start(boolean resetNextRunTime)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 start()80 public void start() { 81 start(false); 82 } 83 stop()84 public void stop() { 85 mRunning = false; 86 mHandler.removeCallbacksAndMessages(null); 87 if (mOnStopRunnable != null) { 88 mOnStopRunnable.run(); 89 } 90 } 91 postAt(long next)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 () -> { 103 try { 104 if (DEBUG) Log.i(TAG, "Starting " + mName); 105 mRunnable.run(); 106 } catch (Exception e) { 107 Log.w(TAG, "Error running " + mName, e); 108 } 109 postAt(resetNextRunTime()); 110 }, 111 delay); 112 if (!posted) { 113 Log.w(TAG, "Scheduling a future run of " + mName + " at " + new Date(next) + "failed"); 114 } 115 if (DEBUG) Log.i(TAG, "Actual delay of " + mName + " is " + (delay / 1000.0) + " seconds."); 116 } 117 getSharedPreferences()118 private SharedPreferences getSharedPreferences() { 119 return mContext.getSharedPreferences( 120 SharedPreferencesUtils.SHARED_PREF_RECURRING_RUNNER, Context.MODE_PRIVATE); 121 } 122 123 @WorkerThread getNextRunTime()124 private long getNextRunTime() { 125 // The access to SharedPreferences is done by an AsyncTask thread because 126 // SharedPreferences reads to disk at first time. 127 long next = getSharedPreferences().getLong(mName, System.currentTimeMillis()); 128 if (next > System.currentTimeMillis() + mIntervalMs) { 129 next = resetNextRunTime(); 130 } 131 return next; 132 } 133 resetNextRunTime()134 private long resetNextRunTime() { 135 long next = System.currentTimeMillis() + mIntervalMs; 136 getSharedPreferences().edit().putLong(mName, next).apply(); 137 return next; 138 } 139 } 140