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 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 
     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