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 #include "SystemSuspend.h"
18
19 #include <aidl/android/system/suspend/ISystemSuspend.h>
20 #include <aidl/android/system/suspend/IWakeLock.h>
21 #include <android-base/file.h>
22 #include <android-base/logging.h>
23 #include <android-base/stringprintf.h>
24 #include <android-base/strings.h>
25 #include <android/binder_manager.h>
26 #include <fcntl.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29
30 #include <chrono>
31 #include <string>
32 #include <thread>
33 using namespace std::chrono_literals;
34
35 using ::aidl::android::system::suspend::ISystemSuspend;
36 using ::aidl::android::system::suspend::IWakeLock;
37 using ::aidl::android::system::suspend::WakeLockType;
38 using ::android::base::Error;
39 using ::android::base::ReadFdToString;
40 using ::android::base::WriteStringToFd;
41 using ::std::string;
42
43 namespace android {
44 namespace system {
45 namespace suspend {
46 namespace V1_0 {
47
48 struct SuspendTime {
49 std::chrono::nanoseconds suspendOverhead;
50 std::chrono::nanoseconds suspendTime;
51 };
52
53 static const char kSleepState[] = "mem";
54 // TODO(b/128923994): we only need /sys/power/wake_[un]lock to export debugging info via
55 // /sys/kernel/debug/wakeup_sources.
56 static constexpr char kSysPowerWakeLock[] = "/sys/power/wake_lock";
57 static constexpr char kSysPowerWakeUnlock[] = "/sys/power/wake_unlock";
58 static constexpr char kUnknownWakeup[] = "unknown";
59 // This is used to disable autosuspend when zygote is restarted
60 // it allows the system to make progress before autosuspend is kicked
61 // NOTE: If the name of this wakelock is changed then also update the name
62 // in rootdir/init.zygote32.rc, rootdir/init.zygote64.rc, and
63 // rootdir/init.zygote64_32.rc
64 static constexpr char kZygoteKernelWakelock[] = "zygote_kwl";
65
66 // This function assumes that data in fd is small enough that it can be read in one go.
67 // We use this function instead of the ones available in libbase because it doesn't block
68 // indefinitely when reading from socket streams which are used for testing.
readFd(int fd)69 string readFd(int fd) {
70 char buf[BUFSIZ];
71 ssize_t n = TEMP_FAILURE_RETRY(read(fd, &buf[0], sizeof(buf)));
72 if (n < 0) return "";
73 return string{buf, static_cast<size_t>(n)};
74 }
75
readWakeupReasons(int fd)76 static std::vector<std::string> readWakeupReasons(int fd) {
77 std::vector<std::string> wakeupReasons;
78 std::string reasonlines;
79
80 lseek(fd, 0, SEEK_SET);
81 if (!ReadFdToString(fd, &reasonlines) || reasonlines.empty()) {
82 PLOG(ERROR) << "failed to read wakeup reasons";
83 // Return unknown wakeup reason if we fail to read
84 return {kUnknownWakeup};
85 }
86
87 std::stringstream ss(reasonlines);
88 for (std::string reasonline; std::getline(ss, reasonline);) {
89 reasonline = ::android::base::Trim(reasonline);
90
91 // Only include non-empty reason lines
92 if (!reasonline.empty()) {
93 wakeupReasons.push_back(reasonline);
94 }
95 }
96
97 // Empty wakeup reason found. Record as unknown wakeup
98 if (wakeupReasons.empty()) {
99 wakeupReasons.push_back(kUnknownWakeup);
100 }
101
102 return wakeupReasons;
103 }
104
105 // reads the suspend overhead and suspend time
106 // Returns 0s if reading the sysfs node fails (unlikely)
readSuspendTime(int fd)107 static struct SuspendTime readSuspendTime(int fd) {
108 std::string content;
109
110 lseek(fd, 0, SEEK_SET);
111 if (!ReadFdToString(fd, &content)) {
112 LOG(ERROR) << "failed to read suspend time";
113 return {0ns, 0ns};
114 }
115
116 double suspendOverhead, suspendTime;
117 std::stringstream ss(content);
118 if (!(ss >> suspendOverhead) || !(ss >> suspendTime)) {
119 LOG(ERROR) << "failed to parse suspend time " << content;
120 return {0ns, 0ns};
121 }
122
123 return {std::chrono::duration_cast<std::chrono::nanoseconds>(
124 std::chrono::duration<double>(suspendOverhead)),
125 std::chrono::duration_cast<std::chrono::nanoseconds>(
126 std::chrono::duration<double>(suspendTime))};
127 }
128
SystemSuspend(unique_fd wakeupCountFd,unique_fd stateFd,unique_fd suspendStatsFd,size_t maxStatsEntries,unique_fd kernelWakelockStatsFd,unique_fd wakeupReasonsFd,unique_fd suspendTimeFd,const SleepTimeConfig & sleepTimeConfig,const sp<SuspendControlService> & controlService,const sp<SuspendControlServiceInternal> & controlServiceInternal,bool useSuspendCounter)129 SystemSuspend::SystemSuspend(unique_fd wakeupCountFd, unique_fd stateFd, unique_fd suspendStatsFd,
130 size_t maxStatsEntries, unique_fd kernelWakelockStatsFd,
131 unique_fd wakeupReasonsFd, unique_fd suspendTimeFd,
132 const SleepTimeConfig& sleepTimeConfig,
133 const sp<SuspendControlService>& controlService,
134 const sp<SuspendControlServiceInternal>& controlServiceInternal,
135 bool useSuspendCounter)
136 : mSuspendCounter(0),
137 mWakeupCountFd(std::move(wakeupCountFd)),
138 mStateFd(std::move(stateFd)),
139 mSuspendStatsFd(std::move(suspendStatsFd)),
140 mSuspendTimeFd(std::move(suspendTimeFd)),
141 kSleepTimeConfig(sleepTimeConfig),
142 mSleepTime(sleepTimeConfig.baseSleepTime),
143 mNumConsecutiveBadSuspends(0),
144 mControlService(controlService),
145 mControlServiceInternal(controlServiceInternal),
146 mStatsList(maxStatsEntries, std::move(kernelWakelockStatsFd)),
147 mWakeupList(maxStatsEntries),
148 mUseSuspendCounter(useSuspendCounter),
149 mWakeLockFd(-1),
150 mWakeUnlockFd(-1),
151 mWakeupReasonsFd(std::move(wakeupReasonsFd)) {
152 mControlServiceInternal->setSuspendService(this);
153
154 if (!mUseSuspendCounter) {
155 mWakeLockFd.reset(TEMP_FAILURE_RETRY(open(kSysPowerWakeLock, O_CLOEXEC | O_RDWR)));
156 if (mWakeLockFd < 0) {
157 PLOG(ERROR) << "error opening " << kSysPowerWakeLock;
158 }
159 }
160
161 mWakeUnlockFd.reset(TEMP_FAILURE_RETRY(open(kSysPowerWakeUnlock, O_CLOEXEC | O_RDWR)));
162 if (mWakeUnlockFd < 0) {
163 PLOG(ERROR) << "error opening " << kSysPowerWakeUnlock;
164 }
165 }
166
enableAutosuspend(const sp<IBinder> & token)167 bool SystemSuspend::enableAutosuspend(const sp<IBinder>& token) {
168 auto tokensLock = std::lock_guard(mAutosuspendClientTokensLock);
169 auto autosuspendLock = std::lock_guard(mAutosuspendLock);
170
171 // Disable zygote kernel wakelock, since explicitly attempting to
172 // enable autosuspend. This should be done even if autosuspend is
173 // already enabled, since it could be the case that the framework
174 // is restarting and connecting to the existing suspend service.
175 if (!WriteStringToFd(kZygoteKernelWakelock, mWakeUnlockFd)) {
176 PLOG(ERROR) << "error writing " << kZygoteKernelWakelock << " to " << kSysPowerWakeUnlock;
177 }
178
179 bool hasToken = std::find(mAutosuspendClientTokens.begin(), mAutosuspendClientTokens.end(),
180 token) != mAutosuspendClientTokens.end();
181
182 if (!hasToken) {
183 mAutosuspendClientTokens.push_back(token);
184 }
185
186 if (mAutosuspendEnabled) {
187 LOG(ERROR) << "Autosuspend already started.";
188 return false;
189 }
190
191 mAutosuspendEnabled = true;
192 initAutosuspendLocked();
193 return true;
194 }
195
disableAutosuspendLocked()196 void SystemSuspend::disableAutosuspendLocked() {
197 mAutosuspendClientTokens.clear();
198 if (mAutosuspendEnabled) {
199 mAutosuspendEnabled = false;
200 mAutosuspendCondVar.notify_all();
201 LOG(INFO) << "automatic system suspend disabled";
202 }
203 }
204
disableAutosuspend()205 void SystemSuspend::disableAutosuspend() {
206 auto tokensLock = std::lock_guard(mAutosuspendClientTokensLock);
207 auto autosuspendLock = std::lock_guard(mAutosuspendLock);
208 disableAutosuspendLocked();
209 }
210
checkAutosuspendClientsLivenessLocked()211 void SystemSuspend::checkAutosuspendClientsLivenessLocked() {
212 // Ping autosuspend client tokens, remove any dead tokens from the list.
213 // mAutosuspendLock must not be held when calling this, as that could lead to a deadlock
214 // if pingBinder() can't be processed by system_server because it's Binder thread pool is
215 // exhausted and blocked on acquire/release wakelock calls.
216 mAutosuspendClientTokens.erase(
217 std::remove_if(mAutosuspendClientTokens.begin(), mAutosuspendClientTokens.end(),
218 [](const sp<IBinder>& token) { return token->pingBinder() != OK; }),
219 mAutosuspendClientTokens.end());
220 }
221
hasAliveAutosuspendTokenLocked()222 bool SystemSuspend::hasAliveAutosuspendTokenLocked() {
223 return !mAutosuspendClientTokens.empty();
224 }
225
~SystemSuspend(void)226 SystemSuspend::~SystemSuspend(void) {
227 auto tokensLock = std::lock_guard(mAutosuspendClientTokensLock);
228 auto autosuspendLock = std::unique_lock(mAutosuspendLock);
229
230 // signal autosuspend thread to shut down
231 disableAutosuspendLocked();
232
233 // wait for autosuspend thread to exit
234 mAutosuspendCondVar.wait_for(autosuspendLock, 100ms, [this]() REQUIRES(mAutosuspendLock) {
235 return !mAutosuspendThreadCreated;
236 });
237 }
238
forceSuspend()239 bool SystemSuspend::forceSuspend() {
240 // We are forcing the system to suspend. This particular call ignores all
241 // existing wakelocks (full or partial). It does not cancel the wakelocks
242 // or reset mSuspendCounter, it just ignores them. When the system
243 // returns from suspend, the wakelocks and SuspendCounter will not have
244 // changed.
245 auto autosuspendLock = std::unique_lock(mAutosuspendLock);
246 bool success = WriteStringToFd(kSleepState, mStateFd);
247 autosuspendLock.unlock();
248
249 if (!success) {
250 PLOG(VERBOSE) << "error writing to /sys/power/state for forceSuspend";
251 }
252 return success;
253 }
254
incSuspendCounter(const string & name)255 void SystemSuspend::incSuspendCounter(const string& name) {
256 auto l = std::lock_guard(mAutosuspendLock);
257 if (mUseSuspendCounter) {
258 mSuspendCounter++;
259 } else {
260 if (!WriteStringToFd(name, mWakeLockFd)) {
261 PLOG(ERROR) << "error writing " << name << " to " << kSysPowerWakeLock;
262 }
263 }
264 }
265
decSuspendCounter(const string & name)266 void SystemSuspend::decSuspendCounter(const string& name) {
267 auto l = std::lock_guard(mAutosuspendLock);
268 if (mUseSuspendCounter) {
269 if (--mSuspendCounter == 0) {
270 mAutosuspendCondVar.notify_one();
271 }
272 } else {
273 if (!WriteStringToFd(name, mWakeUnlockFd)) {
274 PLOG(ERROR) << "error writing " << name << " to " << kSysPowerWakeUnlock;
275 }
276 }
277 }
278
reopenFileUsingFd(const int fd,const int permission)279 unique_fd SystemSuspend::reopenFileUsingFd(const int fd, const int permission) {
280 string filePath = android::base::StringPrintf("/proc/self/fd/%d", fd);
281
282 unique_fd tempFd{TEMP_FAILURE_RETRY(open(filePath.c_str(), permission))};
283 if (tempFd < 0) {
284 PLOG(ERROR) << "SystemSuspend: Error opening file, using path: " << filePath;
285 return unique_fd(-1);
286 }
287 return tempFd;
288 }
289
initAutosuspendLocked()290 void SystemSuspend::initAutosuspendLocked() {
291 if (mAutosuspendThreadCreated) {
292 LOG(INFO) << "Autosuspend thread already started.";
293 return;
294 }
295
296 std::thread autosuspendThread([this] {
297 auto autosuspendLock = std::unique_lock(mAutosuspendLock);
298 bool shouldSleep = true;
299
300 while (true) {
301 {
302 base::ScopedLockAssertion autosuspendLocked(mAutosuspendLock);
303
304 if (!mAutosuspendEnabled) {
305 mAutosuspendThreadCreated = false;
306 return;
307 }
308 // If we got here by a failed write to /sys/power/wakeup_count; don't sleep
309 // since we didn't attempt to suspend on the last cycle of this loop.
310 if (shouldSleep) {
311 mAutosuspendCondVar.wait_for(
312 autosuspendLock, mSleepTime,
313 [this]() REQUIRES(mAutosuspendLock) { return !mAutosuspendEnabled; });
314 }
315
316 if (!mAutosuspendEnabled) continue;
317 autosuspendLock.unlock();
318 }
319
320 lseek(mWakeupCountFd, 0, SEEK_SET);
321 string wakeupCount = readFd(mWakeupCountFd);
322
323 {
324 autosuspendLock.lock();
325 base::ScopedLockAssertion autosuspendLocked(mAutosuspendLock);
326
327 if (wakeupCount.empty()) {
328 PLOG(ERROR) << "error reading from /sys/power/wakeup_count";
329 continue;
330 }
331
332 shouldSleep = false;
333
334 mAutosuspendCondVar.wait(autosuspendLock, [this]() REQUIRES(mAutosuspendLock) {
335 return mSuspendCounter == 0 || !mAutosuspendEnabled;
336 });
337
338 if (!mAutosuspendEnabled) continue;
339 autosuspendLock.unlock();
340 }
341
342 bool success;
343 {
344 auto tokensLock = std::lock_guard(mAutosuspendClientTokensLock);
345 // TODO: Clean up client tokens after soaking the new approach
346 // checkAutosuspendClientsLivenessLocked();
347
348 autosuspendLock.lock();
349 base::ScopedLockAssertion autosuspendLocked(mAutosuspendLock);
350
351 if (!hasAliveAutosuspendTokenLocked()) {
352 disableAutosuspendLocked();
353 continue;
354 }
355
356 // Check suspend counter hasn't increased while checking client liveness
357 if (mSuspendCounter > 0) {
358 continue;
359 }
360
361 // The mutex is locked and *MUST* remain locked until we write to /sys/power/state.
362 // Otherwise, a WakeLock might be acquired after we check mSuspendCounter and before
363 // we write to /sys/power/state.
364
365 if (!WriteStringToFd(wakeupCount, mWakeupCountFd)) {
366 PLOG(VERBOSE) << "error writing to /sys/power/wakeup_count";
367 continue;
368 }
369 success = WriteStringToFd(kSleepState, mStateFd);
370 shouldSleep = true;
371
372 autosuspendLock.unlock();
373 }
374
375 if (!success) {
376 PLOG(VERBOSE) << "error writing to /sys/power/state";
377 }
378
379 struct SuspendTime suspendTime = readSuspendTime(mSuspendTimeFd);
380 updateSleepTime(success, suspendTime);
381
382 std::vector<std::string> wakeupReasons = readWakeupReasons(mWakeupReasonsFd);
383 if (wakeupReasons == std::vector<std::string>({kUnknownWakeup})) {
384 LOG(INFO) << "Unknown/empty wakeup reason. Re-opening wakeup_reason file.";
385
386 mWakeupReasonsFd =
387 std::move(reopenFileUsingFd(mWakeupReasonsFd.get(), O_CLOEXEC | O_RDONLY));
388 }
389 mWakeupList.update(wakeupReasons);
390
391 mControlService->notifyWakeup(success, wakeupReasons);
392
393 // Take the lock before returning to the start of the loop
394 autosuspendLock.lock();
395 }
396 });
397 autosuspendThread.detach();
398 mAutosuspendThreadCreated = true;
399 LOG(INFO) << "automatic system suspend enabled";
400 }
401
402 /**
403 * Updates sleep time depending on the result of suspend attempt.
404 * Time (in milliseconds) between suspend attempts is described the formula
405 * t[n] = { B, 0 < n <= N
406 * { min(B * (S**(n - N)), M), n > N
407 * where:
408 * n is the number of consecutive bad suspend attempts,
409 * B = kBaseSleepTime,
410 * N = kSuspendBackoffThreshold,
411 * S = kSleepTimeScaleFactor,
412 * M = kMaxSleepTime
413 *
414 * kFailedSuspendBackoffEnabled determines whether a failed suspend is counted as a bad suspend
415 *
416 * kShortSuspendBackoffEnabled determines whether a suspend whose duration
417 * t < kShortSuspendThreshold is counted as a bad suspend
418 */
updateSleepTime(bool success,const struct SuspendTime & suspendTime)419 void SystemSuspend::updateSleepTime(bool success, const struct SuspendTime& suspendTime) {
420 std::scoped_lock lock(mSuspendInfoLock);
421 mSuspendInfo.suspendAttemptCount++;
422 mSuspendInfo.sleepTimeMillis +=
423 std::chrono::round<std::chrono::milliseconds>(mSleepTime).count();
424
425 bool shortSuspend = success && (suspendTime.suspendTime > 0ns) &&
426 (suspendTime.suspendTime < kSleepTimeConfig.shortSuspendThreshold);
427
428 bool badSuspend = (kSleepTimeConfig.failedSuspendBackoffEnabled && !success) ||
429 (kSleepTimeConfig.shortSuspendBackoffEnabled && shortSuspend);
430
431 auto suspendTimeMillis =
432 std::chrono::round<std::chrono::milliseconds>(suspendTime.suspendTime).count();
433 auto suspendOverheadMillis =
434 std::chrono::round<std::chrono::milliseconds>(suspendTime.suspendOverhead).count();
435
436 if (success) {
437 mSuspendInfo.suspendOverheadTimeMillis += suspendOverheadMillis;
438 mSuspendInfo.suspendTimeMillis += suspendTimeMillis;
439 } else {
440 mSuspendInfo.failedSuspendCount++;
441 mSuspendInfo.failedSuspendOverheadTimeMillis += suspendOverheadMillis;
442 }
443
444 if (shortSuspend) {
445 mSuspendInfo.shortSuspendCount++;
446 mSuspendInfo.shortSuspendTimeMillis += suspendTimeMillis;
447 }
448
449 if (!badSuspend) {
450 mNumConsecutiveBadSuspends = 0;
451 mSleepTime = kSleepTimeConfig.baseSleepTime;
452 return;
453 }
454
455 // Suspend attempt was bad (failed or short suspend)
456 if (mNumConsecutiveBadSuspends >= kSleepTimeConfig.backoffThreshold) {
457 if (mNumConsecutiveBadSuspends == kSleepTimeConfig.backoffThreshold) {
458 mSuspendInfo.newBackoffCount++;
459 } else {
460 mSuspendInfo.backoffContinueCount++;
461 }
462
463 mSleepTime = std::min(std::chrono::round<std::chrono::milliseconds>(
464 mSleepTime * kSleepTimeConfig.sleepTimeScaleFactor),
465 kSleepTimeConfig.maxSleepTime);
466 }
467
468 mNumConsecutiveBadSuspends++;
469 }
470
updateWakeLockStatOnAcquire(const std::string & name,int pid)471 void SystemSuspend::updateWakeLockStatOnAcquire(const std::string& name, int pid) {
472 // Update the stats first so that the stat time is right after
473 // suspend counter being incremented.
474 mStatsList.updateOnAcquire(name, pid);
475 mControlService->notifyWakelock(name, true);
476 }
477
updateWakeLockStatOnRelease(const std::string & name,int pid)478 void SystemSuspend::updateWakeLockStatOnRelease(const std::string& name, int pid) {
479 // Update the stats first so that the stat time is right after
480 // suspend counter being decremented.
481 mStatsList.updateOnRelease(name, pid);
482 mControlService->notifyWakelock(name, false);
483 }
484
getStatsList() const485 const WakeLockEntryList& SystemSuspend::getStatsList() const {
486 return mStatsList;
487 }
488
updateStatsNow()489 void SystemSuspend::updateStatsNow() {
490 mStatsList.updateNow();
491 }
492
getSuspendInfo(SuspendInfo * info)493 void SystemSuspend::getSuspendInfo(SuspendInfo* info) {
494 std::scoped_lock lock(mSuspendInfoLock);
495
496 *info = mSuspendInfo;
497 }
498
getWakeupList() const499 const WakeupList& SystemSuspend::getWakeupList() const {
500 return mWakeupList;
501 }
502
503 /**
504 * Returns suspend stats.
505 */
getSuspendStats()506 Result<SuspendStats> SystemSuspend::getSuspendStats() {
507 SuspendStats stats;
508 std::unique_ptr<DIR, decltype(&closedir)> dp(fdopendir(dup(mSuspendStatsFd.get())), &closedir);
509 if (!dp) {
510 return stats;
511 }
512
513 // rewinddir, else subsequent calls will not get any suspend_stats
514 rewinddir(dp.get());
515
516 struct dirent* de;
517
518 // Grab a wakelock before reading suspend stats, to ensure a consistent snapshot.
519 const std::string suspendInstance = std::string() + ISystemSuspend::descriptor + "/default";
520 auto suspendService = ISystemSuspend::fromBinder(
521 ndk::SpAIBinder(AServiceManager_checkService(suspendInstance.c_str())));
522
523 std::shared_ptr<IWakeLock> wl = nullptr;
524 if (suspendService) {
525 auto status =
526 suspendService->acquireWakeLock(WakeLockType::PARTIAL, "suspend_stats_lock", &wl);
527 }
528
529 while ((de = readdir(dp.get()))) {
530 std::string statName(de->d_name);
531 if ((statName == ".") || (statName == "..")) {
532 continue;
533 }
534
535 unique_fd statFd{TEMP_FAILURE_RETRY(
536 openat(mSuspendStatsFd.get(), statName.c_str(), O_CLOEXEC | O_RDONLY))};
537 if (statFd < 0) {
538 return Error() << "Failed to open " << statName;
539 }
540
541 std::string valStr;
542 if (!ReadFdToString(statFd.get(), &valStr)) {
543 return Error() << "Failed to read " << statName;
544 }
545
546 // Trim newline
547 valStr.erase(std::remove(valStr.begin(), valStr.end(), '\n'), valStr.end());
548
549 if (statName == "last_failed_dev") {
550 stats.lastFailedDev = valStr;
551 } else if (statName == "last_failed_step") {
552 stats.lastFailedStep = valStr;
553 } else {
554 int statVal = std::stoi(valStr);
555 if (statName == "success") {
556 stats.success = statVal;
557 } else if (statName == "fail") {
558 stats.fail = statVal;
559 } else if (statName == "failed_freeze") {
560 stats.failedFreeze = statVal;
561 } else if (statName == "failed_prepare") {
562 stats.failedPrepare = statVal;
563 } else if (statName == "failed_suspend") {
564 stats.failedSuspend = statVal;
565 } else if (statName == "failed_suspend_late") {
566 stats.failedSuspendLate = statVal;
567 } else if (statName == "failed_suspend_noirq") {
568 stats.failedSuspendNoirq = statVal;
569 } else if (statName == "failed_resume") {
570 stats.failedResume = statVal;
571 } else if (statName == "failed_resume_early") {
572 stats.failedResumeEarly = statVal;
573 } else if (statName == "failed_resume_noirq") {
574 stats.failedResumeNoirq = statVal;
575 } else if (statName == "last_failed_errno") {
576 stats.lastFailedErrno = statVal;
577 }
578 }
579 }
580
581 return stats;
582 }
583
getSleepTime() const584 std::chrono::milliseconds SystemSuspend::getSleepTime() const {
585 return mSleepTime;
586 }
587
588 } // namespace V1_0
589 } // namespace suspend
590 } // namespace system
591 } // namespace android
592