Bug 1646817 - Support DocumentChannel process switching in sidebars and popups r...
[gecko.git] / widget / nsUserIdleService.cpp
blob3dbb5450edf30afdbf7f1cb55016d720125359c3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsUserIdleService.h"
9 #include "nsString.h"
10 #include "nsIObserverService.h"
11 #include "nsDebug.h"
12 #include "nsCOMArray.h"
13 #include "nsXULAppAPI.h"
14 #include "prinrval.h"
15 #include "mozilla/Logging.h"
16 #include "prtime.h"
17 #include "mozilla/dom/ContentChild.h"
18 #include "mozilla/Services.h"
19 #include "mozilla/Preferences.h"
20 #include "mozilla/Telemetry.h"
21 #include <algorithm>
23 #ifdef MOZ_WIDGET_ANDROID
24 # include <android/log.h>
25 #endif
27 using namespace mozilla;
29 // interval in milliseconds between internal idle time requests.
30 #define MIN_IDLE_POLL_INTERVAL_MSEC (5 * PR_MSEC_PER_SEC) /* 5 sec */
32 // After the twenty four hour period expires for an idle daily, this is the
33 // amount of idle time we wait for before actually firing the idle-daily
34 // event.
35 #define DAILY_SIGNIFICANT_IDLE_SERVICE_SEC (3 * 60)
37 // In cases where it's been longer than twenty four hours since the last
38 // idle-daily, this is the shortend amount of idle time we wait for before
39 // firing the idle-daily event.
40 #define DAILY_SHORTENED_IDLE_SERVICE_SEC 60
42 // Pref for last time (seconds since epoch) daily notification was sent.
43 #define PREF_LAST_DAILY "idle.lastDailyNotification"
45 // Number of seconds in a day.
46 #define SECONDS_PER_DAY 86400
48 static LazyLogModule sLog("idleService");
50 #define LOG_TAG "GeckoIdleService"
51 #define LOG_LEVEL ANDROID_LOG_DEBUG
53 // Use this to find previously added observers in our array:
54 class IdleListenerComparator {
55 public:
56 bool Equals(IdleListener a, IdleListener b) const {
57 return (a.observer == b.observer) && (a.reqIdleTime == b.reqIdleTime);
61 ////////////////////////////////////////////////////////////////////////////////
62 //// nsUserIdleServiceDaily
64 NS_IMPL_ISUPPORTS(nsUserIdleServiceDaily, nsIObserver, nsISupportsWeakReference)
66 NS_IMETHODIMP
67 nsUserIdleServiceDaily::Observe(nsISupports*, const char* aTopic,
68 const char16_t*) {
69 MOZ_LOG(sLog, LogLevel::Debug,
70 ("nsUserIdleServiceDaily: Observe '%s' (%d)", aTopic,
71 mShutdownInProgress));
73 if (strcmp(aTopic, "profile-after-change") == 0) {
74 // We are back. Start sending notifications again.
75 mShutdownInProgress = false;
76 return NS_OK;
79 if (strcmp(aTopic, "xpcom-will-shutdown") == 0 ||
80 strcmp(aTopic, "profile-change-teardown") == 0) {
81 mShutdownInProgress = true;
84 if (mShutdownInProgress || strcmp(aTopic, OBSERVER_TOPIC_ACTIVE) == 0) {
85 return NS_OK;
87 MOZ_ASSERT(strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0);
89 MOZ_LOG(sLog, LogLevel::Debug,
90 ("nsUserIdleServiceDaily: Notifying idle-daily observers"));
91 #ifdef MOZ_WIDGET_ANDROID
92 __android_log_print(LOG_LEVEL, LOG_TAG, "Notifying idle-daily observers");
93 #endif
95 // Send the idle-daily observer event
96 nsCOMPtr<nsIObserverService> observerService =
97 mozilla::services::GetObserverService();
98 NS_ENSURE_STATE(observerService);
99 (void)observerService->NotifyObservers(nullptr, OBSERVER_TOPIC_IDLE_DAILY,
100 nullptr);
102 // Notify the category observers.
103 nsCOMArray<nsIObserver> entries;
104 mCategoryObservers.GetEntries(entries);
105 for (int32_t i = 0; i < entries.Count(); ++i) {
106 (void)entries[i]->Observe(nullptr, OBSERVER_TOPIC_IDLE_DAILY, nullptr);
109 // Stop observing idle for today.
110 (void)mIdleService->RemoveIdleObserver(this, mIdleDailyTriggerWait);
112 // Set the last idle-daily time pref.
113 int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
114 Preferences::SetInt(PREF_LAST_DAILY, nowSec);
116 // Force that to be stored so we don't retrigger twice a day under
117 // any circumstances.
118 nsIPrefService* prefs = Preferences::GetService();
119 if (prefs) {
120 prefs->SavePrefFile(nullptr);
123 MOZ_LOG(
124 sLog, LogLevel::Debug,
125 ("nsUserIdleServiceDaily: Storing last idle time as %d sec.", nowSec));
126 #ifdef MOZ_WIDGET_ANDROID
127 __android_log_print(LOG_LEVEL, LOG_TAG, "Storing last idle time as %d",
128 nowSec);
129 #endif
131 // Note the moment we expect to get the next timer callback
132 mExpectedTriggerTime =
133 PR_Now() + ((PRTime)SECONDS_PER_DAY * (PRTime)PR_USEC_PER_SEC);
135 MOZ_LOG(sLog, LogLevel::Debug,
136 ("nsUserIdleServiceDaily: Restarting daily timer"));
138 // Start timer for the next check in one day.
139 (void)mTimer->InitWithNamedFuncCallback(
140 DailyCallback, this, SECONDS_PER_DAY * PR_MSEC_PER_SEC,
141 nsITimer::TYPE_ONE_SHOT, "nsUserIdleServiceDaily::Observe");
143 return NS_OK;
146 nsUserIdleServiceDaily::nsUserIdleServiceDaily(nsIUserIdleService* aIdleService)
147 : mIdleService(aIdleService),
148 mTimer(NS_NewTimer()),
149 mCategoryObservers(OBSERVER_TOPIC_IDLE_DAILY),
150 mShutdownInProgress(false),
151 mExpectedTriggerTime(0),
152 mIdleDailyTriggerWait(DAILY_SIGNIFICANT_IDLE_SERVICE_SEC) {}
154 void nsUserIdleServiceDaily::Init() {
155 // First check the time of the last idle-daily event notification. If it
156 // has been 24 hours or higher, or if we have never sent an idle-daily,
157 // get ready to send an idle-daily event. Otherwise set a timer targeted
158 // at 24 hours past the last idle-daily we sent.
160 int32_t lastDaily = Preferences::GetInt(PREF_LAST_DAILY, 0);
161 // Setting the pref to -1 allows to disable idle-daily, and it's particularly
162 // useful in tests. Normally there should be no need for the user to set
163 // this value.
164 if (lastDaily == -1) {
165 MOZ_LOG(sLog, LogLevel::Debug,
166 ("nsUserIdleServiceDaily: Init: disabled idle-daily"));
167 return;
170 int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
171 if (lastDaily < 0 || lastDaily > nowSec) {
172 // The time is bogus, use default.
173 lastDaily = 0;
175 int32_t secondsSinceLastDaily = nowSec - lastDaily;
177 MOZ_LOG(sLog, LogLevel::Debug,
178 ("nsUserIdleServiceDaily: Init: seconds since last daily: %d",
179 secondsSinceLastDaily));
181 // If it has been twenty four hours or more or if we have never sent an
182 // idle-daily event get ready to send it during the next idle period.
183 if (secondsSinceLastDaily > SECONDS_PER_DAY) {
184 // Check for a "long wait", e.g. 48-hours or more.
185 bool hasBeenLongWait =
186 (lastDaily && (secondsSinceLastDaily > (SECONDS_PER_DAY * 2)));
188 MOZ_LOG(
189 sLog, LogLevel::Debug,
190 ("nsUserIdleServiceDaily: has been long wait? %d", hasBeenLongWait));
192 // StageIdleDaily sets up a wait for the user to become idle and then
193 // sends the idle-daily event.
194 StageIdleDaily(hasBeenLongWait);
195 } else {
196 MOZ_LOG(sLog, LogLevel::Debug,
197 ("nsUserIdleServiceDaily: Setting timer a day from now"));
198 #ifdef MOZ_WIDGET_ANDROID
199 __android_log_print(LOG_LEVEL, LOG_TAG, "Setting timer a day from now");
200 #endif
202 // According to our last idle-daily pref, the last idle-daily was fired
203 // less then 24 hours ago. Set a wait for the amount of time remaining.
204 int32_t milliSecLeftUntilDaily =
205 (SECONDS_PER_DAY - secondsSinceLastDaily) * PR_MSEC_PER_SEC;
207 MOZ_LOG(sLog, LogLevel::Debug,
208 ("nsUserIdleServiceDaily: Seconds till next timeout: %d",
209 (SECONDS_PER_DAY - secondsSinceLastDaily)));
211 // Mark the time at which we expect this to fire. On systems with faulty
212 // timers, we need to be able to cross check that the timer fired at the
213 // expected time.
214 mExpectedTriggerTime =
215 PR_Now() + (milliSecLeftUntilDaily * PR_USEC_PER_MSEC);
217 (void)mTimer->InitWithNamedFuncCallback(
218 DailyCallback, this, milliSecLeftUntilDaily, nsITimer::TYPE_ONE_SHOT,
219 "nsUserIdleServiceDaily::Init");
222 // Register for when we should terminate/pause
223 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
224 if (obs) {
225 MOZ_LOG(
226 sLog, LogLevel::Debug,
227 ("nsUserIdleServiceDaily: Registering for system event observers."));
228 obs->AddObserver(this, "xpcom-will-shutdown", true);
229 obs->AddObserver(this, "profile-change-teardown", true);
230 obs->AddObserver(this, "profile-after-change", true);
234 nsUserIdleServiceDaily::~nsUserIdleServiceDaily() {
235 if (mTimer) {
236 mTimer->Cancel();
237 mTimer = nullptr;
241 void nsUserIdleServiceDaily::StageIdleDaily(bool aHasBeenLongWait) {
242 NS_ASSERTION(mIdleService, "No idle service available?");
243 MOZ_LOG(sLog, LogLevel::Debug,
244 ("nsUserIdleServiceDaily: Registering Idle observer callback "
245 "(short wait requested? %d)",
246 aHasBeenLongWait));
247 #ifdef MOZ_WIDGET_ANDROID
248 __android_log_print(LOG_LEVEL, LOG_TAG, "Registering Idle observer callback");
249 #endif
250 mIdleDailyTriggerWait =
251 (aHasBeenLongWait ? DAILY_SHORTENED_IDLE_SERVICE_SEC
252 : DAILY_SIGNIFICANT_IDLE_SERVICE_SEC);
253 (void)mIdleService->AddIdleObserver(this, mIdleDailyTriggerWait);
256 // static
257 void nsUserIdleServiceDaily::DailyCallback(nsITimer* aTimer, void* aClosure) {
258 MOZ_LOG(sLog, LogLevel::Debug,
259 ("nsUserIdleServiceDaily: DailyCallback running"));
260 #ifdef MOZ_WIDGET_ANDROID
261 __android_log_print(LOG_LEVEL, LOG_TAG, "DailyCallback running");
262 #endif
264 nsUserIdleServiceDaily* self = static_cast<nsUserIdleServiceDaily*>(aClosure);
266 // Check to be sure the timer didn't fire early. This currently only
267 // happens on android.
268 PRTime now = PR_Now();
269 if (self->mExpectedTriggerTime && now < self->mExpectedTriggerTime) {
270 // Timer returned early, reschedule to the appropriate time.
271 PRTime delayTime = self->mExpectedTriggerTime - now;
273 // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
274 delayTime += 10 * PR_USEC_PER_MSEC;
276 MOZ_LOG(sLog, LogLevel::Debug,
277 ("nsUserIdleServiceDaily: DailyCallback resetting timer to %" PRId64
278 " msec",
279 delayTime / PR_USEC_PER_MSEC));
280 #ifdef MOZ_WIDGET_ANDROID
281 __android_log_print(LOG_LEVEL, LOG_TAG,
282 "DailyCallback resetting timer to %" PRId64 " msec",
283 delayTime / PR_USEC_PER_MSEC);
284 #endif
286 (void)self->mTimer->InitWithNamedFuncCallback(
287 DailyCallback, self, delayTime / PR_USEC_PER_MSEC,
288 nsITimer::TYPE_ONE_SHOT, "nsUserIdleServiceDaily::DailyCallback");
289 return;
292 // Register for a short term wait for idle event. When this fires we fire
293 // our idle-daily event.
294 self->StageIdleDaily(false);
298 * The idle services goal is to notify subscribers when a certain time has
299 * passed since the last user interaction with the system.
301 * On some platforms this is defined as the last time user events reached this
302 * application, on other platforms it is a system wide thing - the preferred
303 * implementation is to use the system idle time, rather than the application
304 * idle time, as the things depending on the idle service are likely to use
305 * significant resources (network, disk, memory, cpu, etc.).
307 * When the idle service needs to use the system wide idle timer, it typically
308 * needs to poll the idle time value by the means of a timer. It needs to
309 * poll fast when it is in active idle mode (when it has a listener in the idle
310 * mode) as it needs to detect if the user is active in other applications.
312 * When the service is waiting for the first listener to become idle, or when
313 * it is only monitoring application idle time, it only needs to have the timer
314 * expire at the time the next listener goes idle.
316 * The core state of the service is determined by:
318 * - A list of listeners.
320 * - A boolean that tells if any listeners are in idle mode.
322 * - A delta value that indicates when, measured from the last non-idle time,
323 * the next listener should switch to idle mode.
325 * - An absolute time of the last time idle mode was detected (this is used to
326 * judge if we have been out of idle mode since the last invocation of the
327 * service.
329 * There are four entry points into the system:
331 * - A new listener is registered.
333 * - An existing listener is deregistered.
335 * - User interaction is detected.
337 * - The timer expires.
339 * When a new listener is added its idle timeout, is compared with the next idle
340 * timeout, and if lower, that time is stored as the new timeout, and the timer
341 * is reconfigured to ensure a timeout around the time the new listener should
342 * timeout.
344 * If the next idle time is above the idle time requested by the new listener
345 * it won't be informed until the timer expires, this is to avoid recursive
346 * behavior and to simplify the code. In this case the timer will be set to
347 * about 10 ms.
349 * When an existing listener is deregistered, it is just removed from the list
350 * of active listeners, we don't stop the timer, we just let it expire.
352 * When user interaction is detected, either because it was directly detected or
353 * because we polled the system timer and found it to be unexpected low, then we
354 * check the flag that tells us if any listeners are in idle mode, if there are
355 * they are removed from idle mode and told so, and we reset our state
356 * caculating the next timeout and restart the timer if needed.
358 * ---- Build in logic
360 * In order to avoid restarting the timer endlessly, the timer function has
361 * logic that will only restart the timer, if the requested timeout is before
362 * the current timeout.
366 ////////////////////////////////////////////////////////////////////////////////
367 //// nsUserIdleService
369 namespace {
370 nsUserIdleService* gIdleService;
371 } // namespace
373 already_AddRefed<nsUserIdleService> nsUserIdleService::GetInstance() {
374 RefPtr<nsUserIdleService> instance(gIdleService);
375 return instance.forget();
378 nsUserIdleService::nsUserIdleService()
379 : mCurrentlySetToTimeoutAt(TimeStamp()),
380 mIdleObserverCount(0),
381 mDeltaToNextIdleSwitchInS(UINT32_MAX),
382 mLastUserInteraction(TimeStamp::Now()) {
383 MOZ_ASSERT(!gIdleService);
384 gIdleService = this;
385 if (XRE_IsParentProcess()) {
386 mDailyIdle = new nsUserIdleServiceDaily(this);
387 mDailyIdle->Init();
391 nsUserIdleService::~nsUserIdleService() {
392 if (mTimer) {
393 mTimer->Cancel();
396 MOZ_ASSERT(gIdleService == this);
397 gIdleService = nullptr;
400 NS_IMPL_ISUPPORTS(nsUserIdleService, nsIUserIdleService,
401 nsIUserIdleServiceInternal)
403 NS_IMETHODIMP
404 nsUserIdleService::AddIdleObserver(nsIObserver* aObserver,
405 uint32_t aIdleTimeInS) {
406 NS_ENSURE_ARG_POINTER(aObserver);
407 // We don't accept idle time at 0, and we can't handle idle time that are too
408 // high either - no more than ~136 years.
409 NS_ENSURE_ARG_RANGE(aIdleTimeInS, 1, (UINT32_MAX / 10) - 1);
411 if (XRE_IsContentProcess()) {
412 dom::ContentChild* cpc = dom::ContentChild::GetSingleton();
413 cpc->AddIdleObserver(aObserver, aIdleTimeInS);
414 return NS_OK;
417 MOZ_LOG(sLog, LogLevel::Debug,
418 ("idleService: Register idle observer %p for %d seconds", aObserver,
419 aIdleTimeInS));
420 #ifdef MOZ_WIDGET_ANDROID
421 __android_log_print(LOG_LEVEL, LOG_TAG,
422 "Register idle observer %p for %d seconds", aObserver,
423 aIdleTimeInS);
424 #endif
426 // Put the time + observer in a struct we can keep:
427 IdleListener listener(aObserver, aIdleTimeInS);
429 // XXX(Bug 1631371) Check if this should use a fallible operation as it
430 // pretended earlier.
431 mArrayListeners.AppendElement(listener);
433 // Create our timer callback if it's not there already.
434 if (!mTimer) {
435 mTimer = NS_NewTimer();
436 NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY);
439 // Check if the newly added observer has a smaller wait time than what we
440 // are waiting for now.
441 if (mDeltaToNextIdleSwitchInS > aIdleTimeInS) {
442 // If it is, then this is the next to move to idle (at this point we
443 // don't care if it should have switched already).
444 MOZ_LOG(
445 sLog, LogLevel::Debug,
446 ("idleService: Register: adjusting next switch from %d to %d seconds",
447 mDeltaToNextIdleSwitchInS, aIdleTimeInS));
448 #ifdef MOZ_WIDGET_ANDROID
449 __android_log_print(LOG_LEVEL, LOG_TAG,
450 "Register: adjusting next switch from %d to %d seconds",
451 mDeltaToNextIdleSwitchInS, aIdleTimeInS);
452 #endif
454 mDeltaToNextIdleSwitchInS = aIdleTimeInS;
457 // Ensure timer is running.
458 ReconfigureTimer();
460 return NS_OK;
463 NS_IMETHODIMP
464 nsUserIdleService::RemoveIdleObserver(nsIObserver* aObserver,
465 uint32_t aTimeInS) {
466 NS_ENSURE_ARG_POINTER(aObserver);
467 NS_ENSURE_ARG(aTimeInS);
469 if (XRE_IsContentProcess()) {
470 dom::ContentChild* cpc = dom::ContentChild::GetSingleton();
471 cpc->RemoveIdleObserver(aObserver, aTimeInS);
472 return NS_OK;
475 IdleListener listener(aObserver, aTimeInS);
477 // Find the entry and remove it, if it was the last entry, we just let the
478 // existing timer run to completion (there might be a new registration in a
479 // little while.
480 IdleListenerComparator c;
481 nsTArray<IdleListener>::index_type listenerIndex =
482 mArrayListeners.IndexOf(listener, 0, c);
483 if (listenerIndex != mArrayListeners.NoIndex) {
484 if (mArrayListeners.ElementAt(listenerIndex).isIdle) mIdleObserverCount--;
485 mArrayListeners.RemoveElementAt(listenerIndex);
486 MOZ_LOG(sLog, LogLevel::Debug,
487 ("idleService: Remove observer %p (%d seconds), %d remain idle",
488 aObserver, aTimeInS, mIdleObserverCount));
489 #ifdef MOZ_WIDGET_ANDROID
490 __android_log_print(LOG_LEVEL, LOG_TAG,
491 "Remove observer %p (%d seconds), %d remain idle",
492 aObserver, aTimeInS, mIdleObserverCount);
493 #endif
494 return NS_OK;
497 // If we get here, we haven't removed anything:
498 MOZ_LOG(sLog, LogLevel::Warning,
499 ("idleService: Failed to remove idle observer %p (%d seconds)",
500 aObserver, aTimeInS));
501 #ifdef MOZ_WIDGET_ANDROID
502 __android_log_print(LOG_LEVEL, LOG_TAG,
503 "Failed to remove idle observer %p (%d seconds)",
504 aObserver, aTimeInS);
505 #endif
506 return NS_ERROR_FAILURE;
509 NS_IMETHODIMP
510 nsUserIdleService::ResetIdleTimeOut(uint32_t idleDeltaInMS) {
511 MOZ_LOG(sLog, LogLevel::Debug,
512 ("idleService: Reset idle timeout (last interaction %u msec)",
513 idleDeltaInMS));
515 // Store the time
516 mLastUserInteraction =
517 TimeStamp::Now() - TimeDuration::FromMilliseconds(idleDeltaInMS);
519 // If no one is idle, then we are done, any existing timers can keep running.
520 if (mIdleObserverCount == 0) {
521 MOZ_LOG(sLog, LogLevel::Debug,
522 ("idleService: Reset idle timeout: no idle observers"));
523 return NS_OK;
526 // Mark all idle services as non-idle, and calculate the next idle timeout.
527 nsCOMArray<nsIObserver> notifyList;
528 mDeltaToNextIdleSwitchInS = UINT32_MAX;
530 // Loop through all listeners, and find any that have detected idle.
531 for (uint32_t i = 0; i < mArrayListeners.Length(); i++) {
532 IdleListener& curListener = mArrayListeners.ElementAt(i);
534 // If the listener was idle, then he shouldn't be any longer.
535 if (curListener.isIdle) {
536 notifyList.AppendObject(curListener.observer);
537 curListener.isIdle = false;
540 // Check if the listener is the next one to timeout.
541 mDeltaToNextIdleSwitchInS =
542 std::min(mDeltaToNextIdleSwitchInS, curListener.reqIdleTime);
545 // When we are done, then we wont have anyone idle.
546 mIdleObserverCount = 0;
548 // Restart the idle timer, and do so before anyone can delay us.
549 ReconfigureTimer();
551 int32_t numberOfPendingNotifications = notifyList.Count();
553 // Bail if nothing to do.
554 if (!numberOfPendingNotifications) {
555 return NS_OK;
558 // Now send "active" events to all, if any should have timed out already,
559 // then they will be reawaken by the timer that is already running.
561 // We need a text string to send with any state change events.
562 nsAutoString timeStr;
564 timeStr.AppendInt((int32_t)(idleDeltaInMS / PR_MSEC_PER_SEC));
566 // Send the "non-idle" events.
567 while (numberOfPendingNotifications--) {
568 MOZ_LOG(sLog, LogLevel::Debug,
569 ("idleService: Reset idle timeout: tell observer %p user is back",
570 notifyList[numberOfPendingNotifications]));
571 #ifdef MOZ_WIDGET_ANDROID
572 __android_log_print(LOG_LEVEL, LOG_TAG,
573 "Reset idle timeout: tell observer %p user is back",
574 notifyList[numberOfPendingNotifications]);
575 #endif
576 notifyList[numberOfPendingNotifications]->Observe(
577 this, OBSERVER_TOPIC_ACTIVE, timeStr.get());
579 return NS_OK;
582 NS_IMETHODIMP
583 nsUserIdleService::GetIdleTime(uint32_t* idleTime) {
584 // Check sanity of in parameter.
585 if (!idleTime) {
586 return NS_ERROR_NULL_POINTER;
589 // Polled idle time in ms.
590 uint32_t polledIdleTimeMS;
592 bool polledIdleTimeIsValid = PollIdleTime(&polledIdleTimeMS);
594 MOZ_LOG(sLog, LogLevel::Debug,
595 ("idleService: Get idle time: polled %u msec, valid = %d",
596 polledIdleTimeMS, polledIdleTimeIsValid));
598 // timeSinceReset is in milliseconds.
599 TimeDuration timeSinceReset = TimeStamp::Now() - mLastUserInteraction;
600 uint32_t timeSinceResetInMS = timeSinceReset.ToMilliseconds();
602 MOZ_LOG(sLog, LogLevel::Debug,
603 ("idleService: Get idle time: time since reset %u msec",
604 timeSinceResetInMS));
605 #ifdef MOZ_WIDGET_ANDROID
606 __android_log_print(LOG_LEVEL, LOG_TAG,
607 "Get idle time: time since reset %u msec",
608 timeSinceResetInMS);
609 #endif
611 // If we did't get pulled data, return the time since last idle reset.
612 if (!polledIdleTimeIsValid) {
613 // We need to convert to ms before returning the time.
614 *idleTime = timeSinceResetInMS;
615 return NS_OK;
618 // Otherwise return the shortest time detected (in ms).
619 *idleTime = std::min(timeSinceResetInMS, polledIdleTimeMS);
621 return NS_OK;
624 bool nsUserIdleService::PollIdleTime(uint32_t* /*aIdleTime*/) {
625 // Default behavior is not to have the ability to poll an idle time.
626 return false;
629 bool nsUserIdleService::UsePollMode() {
630 uint32_t dummy;
631 return PollIdleTime(&dummy);
634 nsresult nsUserIdleService::GetDisabled(bool* aResult) {
635 *aResult = mDisabled;
636 return NS_OK;
639 nsresult nsUserIdleService::SetDisabled(bool aDisabled) {
640 mDisabled = aDisabled;
641 return NS_OK;
644 void nsUserIdleService::StaticIdleTimerCallback(nsITimer* aTimer,
645 void* aClosure) {
646 static_cast<nsUserIdleService*>(aClosure)->IdleTimerCallback();
649 void nsUserIdleService::IdleTimerCallback(void) {
650 // Remember that we no longer have a timer running.
651 mCurrentlySetToTimeoutAt = TimeStamp();
653 // Find the last detected idle time.
654 uint32_t lastIdleTimeInMS = static_cast<uint32_t>(
655 (TimeStamp::Now() - mLastUserInteraction).ToMilliseconds());
656 // Get the current idle time.
657 uint32_t currentIdleTimeInMS;
659 if (NS_FAILED(GetIdleTime(&currentIdleTimeInMS))) {
660 MOZ_LOG(sLog, LogLevel::Info,
661 ("idleService: Idle timer callback: failed to get idle time"));
662 #ifdef MOZ_WIDGET_ANDROID
663 __android_log_print(LOG_LEVEL, LOG_TAG,
664 "Idle timer callback: failed to get idle time");
665 #endif
666 return;
669 MOZ_LOG(sLog, LogLevel::Debug,
670 ("idleService: Idle timer callback: current idle time %u msec",
671 currentIdleTimeInMS));
672 #ifdef MOZ_WIDGET_ANDROID
673 __android_log_print(LOG_LEVEL, LOG_TAG,
674 "Idle timer callback: current idle time %u msec",
675 currentIdleTimeInMS);
676 #endif
678 // Check if we have had some user interaction we didn't handle previously
679 // we do the calculation in ms to lessen the chance for rounding errors to
680 // trigger wrong results.
681 if (lastIdleTimeInMS > currentIdleTimeInMS) {
682 // We had user activity, so handle that part first (to ensure the listeners
683 // don't risk getting an non-idle after they get a new idle indication.
684 ResetIdleTimeOut(currentIdleTimeInMS);
686 // NOTE: We can't bail here, as we might have something already timed out.
689 // Find the idle time in S.
690 uint32_t currentIdleTimeInS = currentIdleTimeInMS / PR_MSEC_PER_SEC;
692 // Restart timer and bail if no-one are expected to be in idle
693 if (mDeltaToNextIdleSwitchInS > currentIdleTimeInS) {
694 // If we didn't expect anyone to be idle, then just re-start the timer.
695 ReconfigureTimer();
696 return;
699 if (mDisabled) {
700 MOZ_LOG(sLog, LogLevel::Info,
701 ("idleService: Skipping idle callback while disabled"));
703 ReconfigureTimer();
704 return;
707 // Tell expired listeners they are expired,and find the next timeout
708 Telemetry::AutoTimer<Telemetry::IDLE_NOTIFY_IDLE_MS> timer;
710 // We need to initialise the time to the next idle switch.
711 mDeltaToNextIdleSwitchInS = UINT32_MAX;
713 // Create list of observers that should be notified.
714 nsCOMArray<nsIObserver> notifyList;
716 for (uint32_t i = 0; i < mArrayListeners.Length(); i++) {
717 IdleListener& curListener = mArrayListeners.ElementAt(i);
719 // We are only interested in items, that are not in the idle state.
720 if (!curListener.isIdle) {
721 // If they have an idle time smaller than the actual idle time.
722 if (curListener.reqIdleTime <= currentIdleTimeInS) {
723 // Then add the listener to the list of listeners that should be
724 // notified.
725 notifyList.AppendObject(curListener.observer);
726 // This listener is now idle.
727 curListener.isIdle = true;
728 // Remember we have someone idle.
729 mIdleObserverCount++;
730 } else {
731 // Listeners that are not timed out yet are candidates for timing out.
732 mDeltaToNextIdleSwitchInS =
733 std::min(mDeltaToNextIdleSwitchInS, curListener.reqIdleTime);
738 // Restart the timer before any notifications that could slow us down are
739 // done.
740 ReconfigureTimer();
742 int32_t numberOfPendingNotifications = notifyList.Count();
744 // Bail if nothing to do.
745 if (!numberOfPendingNotifications) {
746 MOZ_LOG(
747 sLog, LogLevel::Debug,
748 ("idleService: **** Idle timer callback: no observers to message."));
749 return;
752 // We need a text string to send with any state change events.
753 nsAutoString timeStr;
754 timeStr.AppendInt(currentIdleTimeInS);
756 // Notify all listeners that just timed out.
757 while (numberOfPendingNotifications--) {
758 MOZ_LOG(
759 sLog, LogLevel::Debug,
760 ("idleService: **** Idle timer callback: tell observer %p user is idle",
761 notifyList[numberOfPendingNotifications]));
762 #ifdef MOZ_WIDGET_ANDROID
763 __android_log_print(LOG_LEVEL, LOG_TAG,
764 "Idle timer callback: tell observer %p user is idle",
765 notifyList[numberOfPendingNotifications]);
766 #endif
767 notifyList[numberOfPendingNotifications]->Observe(this, OBSERVER_TOPIC_IDLE,
768 timeStr.get());
772 void nsUserIdleService::SetTimerExpiryIfBefore(TimeStamp aNextTimeout) {
773 TimeDuration nextTimeoutDuration = aNextTimeout - TimeStamp::Now();
775 MOZ_LOG(
776 sLog, LogLevel::Debug,
777 ("idleService: SetTimerExpiryIfBefore: next timeout %0.f msec from now",
778 nextTimeoutDuration.ToMilliseconds()));
780 #ifdef MOZ_WIDGET_ANDROID
781 __android_log_print(LOG_LEVEL, LOG_TAG,
782 "SetTimerExpiryIfBefore: next timeout %0.f msec from now",
783 nextTimeoutDuration.ToMilliseconds());
784 #endif
786 // Bail if we don't have a timer service.
787 if (!mTimer) {
788 return;
791 // If the new timeout is before the old one or we don't have a timer running,
792 // then restart the timer.
793 if (mCurrentlySetToTimeoutAt.IsNull() ||
794 mCurrentlySetToTimeoutAt > aNextTimeout) {
795 mCurrentlySetToTimeoutAt = aNextTimeout;
797 // Stop the current timer (it's ok to try'n stop it, even it isn't running).
798 mTimer->Cancel();
800 // Check that the timeout is actually in the future, otherwise make it so.
801 TimeStamp currentTime = TimeStamp::Now();
802 if (currentTime > mCurrentlySetToTimeoutAt) {
803 mCurrentlySetToTimeoutAt = currentTime;
806 // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
807 mCurrentlySetToTimeoutAt += TimeDuration::FromMilliseconds(10);
809 TimeDuration deltaTime = mCurrentlySetToTimeoutAt - currentTime;
810 MOZ_LOG(
811 sLog, LogLevel::Debug,
812 ("idleService: IdleService reset timer expiry to %0.f msec from now",
813 deltaTime.ToMilliseconds()));
814 #ifdef MOZ_WIDGET_ANDROID
815 __android_log_print(LOG_LEVEL, LOG_TAG,
816 "reset timer expiry to %0.f msec from now",
817 deltaTime.ToMilliseconds());
818 #endif
820 // Start the timer
821 mTimer->InitWithNamedFuncCallback(
822 StaticIdleTimerCallback, this, deltaTime.ToMilliseconds(),
823 nsITimer::TYPE_ONE_SHOT, "nsUserIdleService::SetTimerExpiryIfBefore");
827 void nsUserIdleService::ReconfigureTimer(void) {
828 // Check if either someone is idle, or someone will become idle.
829 if ((mIdleObserverCount == 0) && UINT32_MAX == mDeltaToNextIdleSwitchInS) {
830 // If not, just let any existing timers run to completion
831 // And bail out.
832 MOZ_LOG(sLog, LogLevel::Debug,
833 ("idleService: ReconfigureTimer: no idle or waiting observers"));
834 #ifdef MOZ_WIDGET_ANDROID
835 __android_log_print(LOG_LEVEL, LOG_TAG,
836 "ReconfigureTimer: no idle or waiting observers");
837 #endif
838 return;
841 // Find the next timeout value, assuming we are not polling.
843 // We need to store the current time, so we don't get artifacts from the time
844 // ticking while we are processing.
845 TimeStamp curTime = TimeStamp::Now();
847 TimeStamp nextTimeoutAt =
848 mLastUserInteraction +
849 TimeDuration::FromSeconds(mDeltaToNextIdleSwitchInS);
851 TimeDuration nextTimeoutDuration = nextTimeoutAt - curTime;
853 MOZ_LOG(sLog, LogLevel::Debug,
854 ("idleService: next timeout %0.f msec from now",
855 nextTimeoutDuration.ToMilliseconds()));
857 #ifdef MOZ_WIDGET_ANDROID
858 __android_log_print(LOG_LEVEL, LOG_TAG, "next timeout %0.f msec from now",
859 nextTimeoutDuration.ToMilliseconds());
860 #endif
862 // Check if we should correct the timeout time because we should poll before.
863 if ((mIdleObserverCount > 0) && UsePollMode()) {
864 TimeStamp pollTimeout =
865 curTime + TimeDuration::FromMilliseconds(MIN_IDLE_POLL_INTERVAL_MSEC);
867 if (nextTimeoutAt > pollTimeout) {
868 MOZ_LOG(
869 sLog, LogLevel::Debug,
870 ("idleService: idle observers, reducing timeout to %lu msec from now",
871 MIN_IDLE_POLL_INTERVAL_MSEC));
872 #ifdef MOZ_WIDGET_ANDROID
873 __android_log_print(
874 LOG_LEVEL, LOG_TAG,
875 "idle observers, reducing timeout to %lu msec from now",
876 MIN_IDLE_POLL_INTERVAL_MSEC);
877 #endif
878 nextTimeoutAt = pollTimeout;
882 SetTimerExpiryIfBefore(nextTimeoutAt);