Home | History | Annotate | Download | only in util
      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