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 26 import com.android.tv.common.SharedPreferencesUtils; 27 import com.android.tv.common.SoftPreconditions; 28 29 import 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 */ 37 public 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 if (DEBUG) Log.i(TAG, "Delaying " + (intervalMs / 1000.0) + " seconds"); 56 mName = runnable.getClass().getCanonicalName(); 57 mHandler = new Handler(mContext.getMainLooper()); 58 } 59 60 public void start() { 61 SoftPreconditions.checkState(!mRunning, TAG, "start is called twice."); 62 if (mRunning) { 63 return; 64 } 65 mRunning = true; 66 new AsyncTask<Void, Void, Long>() { 67 @Override 68 protected Long doInBackground(Void... params) { 69 return getNextRunTime(); 70 } 71 72 @Override 73 protected void onPostExecute(Long nextRunTime) { 74 postAt(nextRunTime); 75 } 76 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 77 } 78 79 public void stop() { 80 mRunning = false; 81 mHandler.removeCallbacksAndMessages(null); 82 if (mOnStopRunnable != null) { 83 mOnStopRunnable.run(); 84 } 85 } 86 87 private void postAt(long next) { 88 if (!mRunning) { 89 return; 90 } 91 long now = System.currentTimeMillis(); 92 // Run it anyways even if it is in the past 93 if (DEBUG) Log.i(TAG, "Next run of " + mName + " at " + new Date(next)); 94 long delay = Math.max(next - now, 0); 95 boolean posted = mHandler.postDelayed(new Runnable() { 96 @Override 97 public void run() { 98 try { 99 if (DEBUG) Log.i(TAG, "Starting " + mName); 100 mRunnable.run(); 101 } catch (Exception e) { 102 Log.w(TAG, "Error running " + mName, e); 103 } 104 postAt(resetNextRunTime()); 105 } 106 }, delay); 107 if (!posted) { 108 Log.w(TAG, "Scheduling a future run of " + mName + " at " + new Date(next) + "failed"); 109 } 110 if (DEBUG) Log.i(TAG, "Actual delay is " + (delay / 1000.0) + " seconds."); 111 } 112 113 private SharedPreferences getSharedPreferences() { 114 return mContext.getSharedPreferences(SharedPreferencesUtils.SHARED_PREF_RECURRING_RUNNER, 115 Context.MODE_PRIVATE); 116 } 117 118 @WorkerThread 119 private long getNextRunTime() { 120 // The access to SharedPreferences is done by an AsyncTask thread because 121 // SharedPreferences reads to disk at first time. 122 long next = getSharedPreferences().getLong(mName, System.currentTimeMillis()); 123 if (next > System.currentTimeMillis() + mIntervalMs) { 124 next = resetNextRunTime(); 125 } 126 return next; 127 } 128 129 private long resetNextRunTime() { 130 long next = System.currentTimeMillis() + mIntervalMs; 131 getSharedPreferences().edit().putLong(mName, next).apply(); 132 return next; 133 } 134 } 135