1accafea75309628ec2456574bcaff8c97411187bJan Clarin/*
2accafea75309628ec2456574bcaff8c97411187bJan Clarin * Copyright 2018 The Android Open Source Project
3accafea75309628ec2456574bcaff8c97411187bJan Clarin *
4accafea75309628ec2456574bcaff8c97411187bJan Clarin * Licensed under the Apache License, Version 2.0 (the "License");
5accafea75309628ec2456574bcaff8c97411187bJan Clarin * you may not use this file except in compliance with the License.
6accafea75309628ec2456574bcaff8c97411187bJan Clarin * You may obtain a copy of the License at
7accafea75309628ec2456574bcaff8c97411187bJan Clarin *
8accafea75309628ec2456574bcaff8c97411187bJan Clarin *      http://www.apache.org/licenses/LICENSE-2.0
9accafea75309628ec2456574bcaff8c97411187bJan Clarin *
10accafea75309628ec2456574bcaff8c97411187bJan Clarin * Unless required by applicable law or agreed to in writing, software
11accafea75309628ec2456574bcaff8c97411187bJan Clarin * distributed under the License is distributed on an "AS IS" BASIS,
12accafea75309628ec2456574bcaff8c97411187bJan Clarin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13accafea75309628ec2456574bcaff8c97411187bJan Clarin * See the License for the specific language governing permissions and
14accafea75309628ec2456574bcaff8c97411187bJan Clarin * limitations under the License.
15accafea75309628ec2456574bcaff8c97411187bJan Clarin */
16accafea75309628ec2456574bcaff8c97411187bJan Clarin
17564e43098c323d1a90be53c190b8fdbdde973505Sumir Katariapackage androidx.work.impl.background.systemalarm;
18accafea75309628ec2456574bcaff8c97411187bJan Clarin
19accafea75309628ec2456574bcaff8c97411187bJan Clarinimport android.support.annotation.NonNull;
20accafea75309628ec2456574bcaff8c97411187bJan Clarinimport android.support.annotation.RestrictTo;
21dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumarimport android.support.annotation.VisibleForTesting;
22697d6a4a3797bc71d0dd8685937a318e9934066bRahul Ravikumarimport android.util.Log;
23accafea75309628ec2456574bcaff8c97411187bJan Clarin
247031a0fbe12b8159ab2dc6d9c50be5b3f38477faRahul Ravikumarimport androidx.work.WorkRequest;
25b5728f4e1a4b3f4f1fabf033b1363ca6b1cffdefSumir Kataria
26dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumarimport java.util.HashMap;
27accafea75309628ec2456574bcaff8c97411187bJan Clarinimport java.util.Map;
28accafea75309628ec2456574bcaff8c97411187bJan Clarinimport java.util.concurrent.Executors;
29accafea75309628ec2456574bcaff8c97411187bJan Clarinimport java.util.concurrent.ScheduledExecutorService;
30accafea75309628ec2456574bcaff8c97411187bJan Clarinimport java.util.concurrent.TimeUnit;
31accafea75309628ec2456574bcaff8c97411187bJan Clarin
32accafea75309628ec2456574bcaff8c97411187bJan Clarin/**
337031a0fbe12b8159ab2dc6d9c50be5b3f38477faRahul Ravikumar * Manages timers to enforce a time limit for processing {@link WorkRequest}.
34dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar * Notifies a {@link TimeLimitExceededListener} when the time limit
35dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar * is exceeded.
36accafea75309628ec2456574bcaff8c97411187bJan Clarin *
37accafea75309628ec2456574bcaff8c97411187bJan Clarin * @hide
38accafea75309628ec2456574bcaff8c97411187bJan Clarin */
39accafea75309628ec2456574bcaff8c97411187bJan Clarin@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
40dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumarclass WorkTimer {
41dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar
42accafea75309628ec2456574bcaff8c97411187bJan Clarin    private static final String TAG = "WorkTimer";
43accafea75309628ec2456574bcaff8c97411187bJan Clarin
44dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    private final ScheduledExecutorService mExecutorService;
45dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    private final Map<String, WorkTimerRunnable> mTimerMap;
46dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    private final Map<String, TimeLimitExceededListener> mListeners;
47dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    private final Object mLock;
48dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar
49dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    WorkTimer() {
50dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        mTimerMap = new HashMap<>();
51dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        mListeners = new HashMap<>();
52dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        mLock = new Object();
53dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        mExecutorService = Executors.newSingleThreadScheduledExecutor();
54dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    }
55accafea75309628ec2456574bcaff8c97411187bJan Clarin
56dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    @SuppressWarnings("FutureReturnValueIgnored")
57dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    void startTimer(@NonNull final String workSpecId,
58dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            long processingTimeMillis,
59dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            @NonNull TimeLimitExceededListener listener) {
60accafea75309628ec2456574bcaff8c97411187bJan Clarin
61dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        synchronized (mLock) {
62697d6a4a3797bc71d0dd8685937a318e9934066bRahul Ravikumar            Log.d(TAG, String.format("Starting timer for %s", workSpecId));
63dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            // clear existing timer's first
64dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            stopTimer(workSpecId);
65dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            WorkTimerRunnable runnable = new WorkTimerRunnable(this, workSpecId);
66dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            mTimerMap.put(workSpecId, runnable);
67dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            mListeners.put(workSpecId, listener);
68dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            mExecutorService.schedule(runnable, processingTimeMillis, TimeUnit.MILLISECONDS);
69dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        }
70accafea75309628ec2456574bcaff8c97411187bJan Clarin    }
71accafea75309628ec2456574bcaff8c97411187bJan Clarin
72dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    void stopTimer(@NonNull final String workSpecId) {
73dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        synchronized (mLock) {
74dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            if (mTimerMap.containsKey(workSpecId)) {
75697d6a4a3797bc71d0dd8685937a318e9934066bRahul Ravikumar                Log.d(TAG, String.format("Stopping timer for %s", workSpecId));
76dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                mTimerMap.remove(workSpecId);
77dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                mListeners.remove(workSpecId);
78accafea75309628ec2456574bcaff8c97411187bJan Clarin            }
79dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        }
80dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    }
81accafea75309628ec2456574bcaff8c97411187bJan Clarin
82dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    @VisibleForTesting
83dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    synchronized Map<String, WorkTimerRunnable> getTimerMap() {
84dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        return mTimerMap;
85accafea75309628ec2456574bcaff8c97411187bJan Clarin    }
86accafea75309628ec2456574bcaff8c97411187bJan Clarin
87dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    @VisibleForTesting
88dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    synchronized Map<String, TimeLimitExceededListener> getListeners() {
89dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        return mListeners;
90dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    }
91dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar
92dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    /**
93dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar     * The actual runnable scheduled on the scheduled executor.
94dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar     */
95dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar    static class WorkTimerRunnable implements Runnable {
96dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        static final String TAG = "WrkTimerRunnable";
97dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar
98dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        private final WorkTimer mWorkTimer;
99dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        private final String mWorkSpecId;
100dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar
101dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        WorkTimerRunnable(@NonNull WorkTimer workTimer, @NonNull String workSpecId) {
102dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            mWorkTimer = workTimer;
103dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            mWorkSpecId = workSpecId;
104dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        }
105dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar
106dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        @Override
107dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar        public void run() {
108dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            synchronized (mWorkTimer.mLock) {
109dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                if (mWorkTimer.mTimerMap.containsKey(mWorkSpecId)) {
110dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                    mWorkTimer.mTimerMap.remove(mWorkSpecId);
111dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                    // notify time limit exceeded.
112dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                    TimeLimitExceededListener listener = mWorkTimer.mListeners.remove(mWorkSpecId);
113dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                    if (listener != null) {
114dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                        listener.onTimeLimitExceeded(mWorkSpecId);
115dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                    }
116dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                } else {
117697d6a4a3797bc71d0dd8685937a318e9934066bRahul Ravikumar                    Log.d(TAG, String.format(
118697d6a4a3797bc71d0dd8685937a318e9934066bRahul Ravikumar                            "Timer with %s is already marked as complete.", mWorkSpecId));
119dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar                }
120dd3c04c8aa64eb9c8d3da1bbac6024e613d39143Rahul Ravikumar            }
121accafea75309628ec2456574bcaff8c97411187bJan Clarin        }
122accafea75309628ec2456574bcaff8c97411187bJan Clarin    }
123accafea75309628ec2456574bcaff8c97411187bJan Clarin
124accafea75309628ec2456574bcaff8c97411187bJan Clarin    interface TimeLimitExceededListener {
125accafea75309628ec2456574bcaff8c97411187bJan Clarin        void onTimeLimitExceeded(@NonNull String workSpecId);
126accafea75309628ec2456574bcaff8c97411187bJan Clarin    }
127accafea75309628ec2456574bcaff8c97411187bJan Clarin}
128