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