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 #include <chrono>
18 #include <thread>
19 
20 #include <vibratorservice/VibratorCallbackScheduler.h>
21 
22 namespace android {
23 
24 namespace vibrator {
25 
26 // -------------------------------------------------------------------------------------------------
27 
isExpired() const28 bool DelayedCallback::isExpired() const {
29     return mExpiration <= std::chrono::steady_clock::now();
30 }
31 
getWaitForExpirationDuration() const32 std::chrono::milliseconds DelayedCallback::getWaitForExpirationDuration() const {
33     std::chrono::milliseconds delta = std::chrono::duration_cast<std::chrono::milliseconds>(
34             mExpiration - std::chrono::steady_clock::now());
35     // Return zero if this is already expired.
36     return delta > delta.zero() ? delta : delta.zero();
37 }
38 
run() const39 void DelayedCallback::run() const {
40     mCallback();
41 }
42 
operator <(const DelayedCallback & other) const43 bool DelayedCallback::operator<(const DelayedCallback& other) const {
44     return mExpiration < other.mExpiration;
45 }
46 
operator >(const DelayedCallback & other) const47 bool DelayedCallback::operator>(const DelayedCallback& other) const {
48     return mExpiration > other.mExpiration;
49 }
50 
51 // -------------------------------------------------------------------------------------------------
52 
~CallbackScheduler()53 CallbackScheduler::~CallbackScheduler() {
54     {
55         std::lock_guard<std::mutex> lock(mMutex);
56         mFinished = true;
57     }
58     mCondition.notify_all();
59     if (mCallbackThread && mCallbackThread->joinable()) {
60         mCallbackThread->join();
61     }
62 }
63 
schedule(std::function<void ()> callback,std::chrono::milliseconds delay)64 void CallbackScheduler::schedule(std::function<void()> callback, std::chrono::milliseconds delay) {
65     {
66         std::lock_guard<std::mutex> lock(mMutex);
67         if (mCallbackThread == nullptr) {
68             mCallbackThread = std::make_unique<std::thread>(&CallbackScheduler::loop, this);
69         }
70         mQueue.emplace(DelayedCallback(callback, delay));
71     }
72     mCondition.notify_all();
73 }
74 
loop()75 void CallbackScheduler::loop() {
76     while (true) {
77         std::unique_lock<std::mutex> lock(mMutex);
78         if (mFinished) {
79             // Destructor was called, so let the callback thread die.
80             break;
81         }
82         while (!mQueue.empty() && mQueue.top().isExpired()) {
83             DelayedCallback callback = mQueue.top();
84             mQueue.pop();
85             lock.unlock();
86             callback.run();
87             lock.lock();
88         }
89         if (mQueue.empty()) {
90             // Wait until a new callback is scheduled or destructor was called.
91             mCondition.wait(lock, [this] { return mFinished || !mQueue.empty(); });
92         } else {
93             // Wait until next callback expires or a new one is scheduled.
94             // Use the monotonic steady clock to wait for the measured delay interval via wait_for
95             // instead of using a wall clock via wait_until.
96             mCondition.wait_for(lock, mQueue.top().getWaitForExpirationDuration());
97         }
98     }
99 }
100 
101 // -------------------------------------------------------------------------------------------------
102 
103 }; // namespace vibrator
104 
105 }; // namespace android
106