1 /*
2  * Copyright (C) 2020 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 #pragma once
18 
19 #include <android-base/thread_annotations.h>
20 #include <chrono>
21 #include <map>
22 #include <mutex>
23 #include <thread>
24 
25 namespace android::mediametrics {
26 
27 class TimedAction {
28     // Use system_clock instead of steady_clock to include suspend time.
29     using TimerClock = class std::chrono::system_clock;
30 
31     // Define granularity of wakeup to prevent delayed events if
32     // device is suspended.
33     static constexpr auto kWakeupInterval = std::chrono::minutes(3);
34 public:
TimedAction()35     TimedAction() : mThread{[this](){threadLoop();}} {}
36 
~TimedAction()37     ~TimedAction() {
38         quit();
39     }
40 
41     // TODO: return a handle for cancelling the action?
42     template <typename T> // T is in units of std::chrono::duration.
postIn(const T & time,std::function<void ()> f)43     void postIn(const T& time, std::function<void()> f) {
44         postAt(TimerClock::now() + time, f);
45     }
46 
47     template <typename T> // T is in units of std::chrono::time_point
postAt(const T & targetTime,std::function<void ()> f)48     void postAt(const T& targetTime, std::function<void()> f) {
49         std::lock_guard l(mLock);
50         if (mQuit) return;
51         if (mMap.empty() || targetTime < mMap.begin()->first) {
52             mMap.emplace_hint(mMap.begin(), targetTime, std::move(f));
53             mCondition.notify_one();
54         } else {
55             mMap.emplace(targetTime, std::move(f));
56         }
57     }
58 
clear()59     void clear() {
60         std::lock_guard l(mLock);
61         mMap.clear();
62     }
63 
quit()64     void quit() {
65         {
66             std::lock_guard l(mLock);
67             if (mQuit) return;
68             mQuit = true;
69             mMap.clear();
70             mCondition.notify_all();
71         }
72         mThread.join();
73     }
74 
size()75     size_t size() const {
76         std::lock_guard l(mLock);
77         return mMap.size();
78     }
79 
80 private:
threadLoop()81     void threadLoop() NO_THREAD_SAFETY_ANALYSIS { // thread safety doesn't cover unique_lock
82         std::unique_lock l(mLock);
83         while (!mQuit) {
84             auto sleepUntilTime = std::chrono::time_point<TimerClock>::max();
85             if (!mMap.empty()) {
86                 sleepUntilTime = mMap.begin()->first;
87                 const auto now = TimerClock::now();
88                 if (sleepUntilTime <= now) {
89                     auto node = mMap.extract(mMap.begin()); // removes from mMap.
90                     l.unlock();
91                     node.mapped()();
92                     l.lock();
93                     continue;
94                 }
95                 // Bionic uses CLOCK_MONOTONIC for its pthread_mutex regardless
96                 // of REALTIME specification, use kWakeupInterval to ensure minimum
97                 // granularity if suspended.
98                 sleepUntilTime = std::min(sleepUntilTime, now + kWakeupInterval);
99             }
100             mCondition.wait_until(l, sleepUntilTime);
101         }
102     }
103 
104     mutable std::mutex mLock;
105     std::condition_variable mCondition GUARDED_BY(mLock);
106     bool mQuit GUARDED_BY(mLock) = false;
107     std::multimap<std::chrono::time_point<TimerClock>, std::function<void()>>
108             mMap GUARDED_BY(mLock); // multiple functions could execute at the same time.
109 
110     // needs to be initialized after the variables above, done in constructor initializer list.
111     std::thread mThread;
112 };
113 
114 } // namespace android::mediametrics
115