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