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 
17 package androidx.work.impl.background.systemalarm;
18 
19 import android.support.annotation.NonNull;
20 import android.support.annotation.RestrictTo;
21 import android.support.annotation.VisibleForTesting;
22 import android.util.Log;
23 
24 import androidx.work.WorkRequest;
25 
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.ScheduledExecutorService;
30 import 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)
40 class 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 
WorkTimer()49     WorkTimer() {
50         mTimerMap = new HashMap<>();
51         mListeners = new HashMap<>();
52         mLock = new Object();
53         mExecutorService = Executors.newSingleThreadScheduledExecutor();
54     }
55 
56     @SuppressWarnings("FutureReturnValueIgnored")
startTimer(@onNull final String workSpecId, long processingTimeMillis, @NonNull TimeLimitExceededListener listener)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 
stopTimer(@onNull final String workSpecId)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
getTimerMap()83     synchronized Map<String, WorkTimerRunnable> getTimerMap() {
84         return mTimerMap;
85     }
86 
87     @VisibleForTesting
getListeners()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 
WorkTimerRunnable(@onNull WorkTimer workTimer, @NonNull String workSpecId)101         WorkTimerRunnable(@NonNull WorkTimer workTimer, @NonNull String workSpecId) {
102             mWorkTimer = workTimer;
103             mWorkSpecId = workSpecId;
104         }
105 
106         @Override
run()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 {
onTimeLimitExceeded(@onNull String workSpecId)125         void onTimeLimitExceeded(@NonNull String workSpecId);
126     }
127 }
128