1/*
2 * Copyright 2018 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 androidx.work.impl.background.systemalarm;
18
19import android.support.annotation.NonNull;
20import android.support.annotation.RestrictTo;
21import android.support.annotation.VisibleForTesting;
22import android.util.Log;
23
24import androidx.work.WorkRequest;
25
26import java.util.HashMap;
27import java.util.Map;
28import java.util.concurrent.Executors;
29import java.util.concurrent.ScheduledExecutorService;
30import java.util.concurrent.TimeUnit;
31
32/**
33 * Manages timers to enforce a time limit for processing {@link WorkRequest}.
34 * Notifies a {@link TimeLimitExceededListener} when the time limit
35 * is exceeded.
36 *
37 * @hide
38 */
39@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
40class WorkTimer {
41
42    private static final String TAG = "WorkTimer";
43
44    private final ScheduledExecutorService mExecutorService;
45    private final Map<String, WorkTimerRunnable> mTimerMap;
46    private final Map<String, TimeLimitExceededListener> mListeners;
47    private final Object mLock;
48
49    WorkTimer() {
50        mTimerMap = new HashMap<>();
51        mListeners = new HashMap<>();
52        mLock = new Object();
53        mExecutorService = Executors.newSingleThreadScheduledExecutor();
54    }
55
56    @SuppressWarnings("FutureReturnValueIgnored")
57    void startTimer(@NonNull final String workSpecId,
58            long processingTimeMillis,
59            @NonNull TimeLimitExceededListener listener) {
60
61        synchronized (mLock) {
62            Log.d(TAG, String.format("Starting timer for %s", workSpecId));
63            // clear existing timer's first
64            stopTimer(workSpecId);
65            WorkTimerRunnable runnable = new WorkTimerRunnable(this, workSpecId);
66            mTimerMap.put(workSpecId, runnable);
67            mListeners.put(workSpecId, listener);
68            mExecutorService.schedule(runnable, processingTimeMillis, TimeUnit.MILLISECONDS);
69        }
70    }
71
72    void stopTimer(@NonNull final String workSpecId) {
73        synchronized (mLock) {
74            if (mTimerMap.containsKey(workSpecId)) {
75                Log.d(TAG, String.format("Stopping timer for %s", workSpecId));
76                mTimerMap.remove(workSpecId);
77                mListeners.remove(workSpecId);
78            }
79        }
80    }
81
82    @VisibleForTesting
83    synchronized Map<String, WorkTimerRunnable> getTimerMap() {
84        return mTimerMap;
85    }
86
87    @VisibleForTesting
88    synchronized Map<String, TimeLimitExceededListener> getListeners() {
89        return mListeners;
90    }
91
92    /**
93     * The actual runnable scheduled on the scheduled executor.
94     */
95    static class WorkTimerRunnable implements Runnable {
96        static final String TAG = "WrkTimerRunnable";
97
98        private final WorkTimer mWorkTimer;
99        private final String mWorkSpecId;
100
101        WorkTimerRunnable(@NonNull WorkTimer workTimer, @NonNull String workSpecId) {
102            mWorkTimer = workTimer;
103            mWorkSpecId = workSpecId;
104        }
105
106        @Override
107        public void run() {
108            synchronized (mWorkTimer.mLock) {
109                if (mWorkTimer.mTimerMap.containsKey(mWorkSpecId)) {
110                    mWorkTimer.mTimerMap.remove(mWorkSpecId);
111                    // notify time limit exceeded.
112                    TimeLimitExceededListener listener = mWorkTimer.mListeners.remove(mWorkSpecId);
113                    if (listener != null) {
114                        listener.onTimeLimitExceeded(mWorkSpecId);
115                    }
116                } else {
117                    Log.d(TAG, String.format(
118                            "Timer with %s is already marked as complete.", mWorkSpecId));
119                }
120            }
121        }
122    }
123
124    interface TimeLimitExceededListener {
125        void onTimeLimitExceeded(@NonNull String workSpecId);
126    }
127}
128