1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
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"
10 #include "nsIObserverService.h"
12 #include "nsCOMArray.h"
13 #include "nsXULAppAPI.h"
15 #include "mozilla/Logging.h"
17 #include "mozilla/dom/ContentChild.h"
18 #include "mozilla/Services.h"
19 #include "mozilla/Preferences.h"
20 #include "mozilla/Telemetry.h"
23 #ifdef MOZ_WIDGET_ANDROID
24 # include <android/log.h>
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
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
{
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
)
67 nsUserIdleServiceDaily::Observe(nsISupports
*, const char* aTopic
,
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;
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) {
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");
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
,
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();
120 prefs
->SavePrefFile(nullptr);
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",
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");
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
164 if (lastDaily
== -1) {
165 MOZ_LOG(sLog
, LogLevel::Debug
,
166 ("nsUserIdleServiceDaily: Init: disabled idle-daily"));
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.
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)));
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
);
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");
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
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();
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() {
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)",
247 #ifdef MOZ_WIDGET_ANDROID
248 __android_log_print(LOG_LEVEL
, LOG_TAG
, "Registering Idle observer callback");
250 mIdleDailyTriggerWait
=
251 (aHasBeenLongWait
? DAILY_SHORTENED_IDLE_SERVICE_SEC
252 : DAILY_SIGNIFICANT_IDLE_SERVICE_SEC
);
253 (void)mIdleService
->AddIdleObserver(this, mIdleDailyTriggerWait
);
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");
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
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
);
286 (void)self
->mTimer
->InitWithNamedFuncCallback(
287 DailyCallback
, self
, delayTime
/ PR_USEC_PER_MSEC
,
288 nsITimer::TYPE_ONE_SHOT
, "nsUserIdleServiceDaily::DailyCallback");
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
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
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
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
370 nsUserIdleService
* gIdleService
;
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
);
385 if (XRE_IsParentProcess()) {
386 mDailyIdle
= new nsUserIdleServiceDaily(this);
391 nsUserIdleService::~nsUserIdleService() {
396 MOZ_ASSERT(gIdleService
== this);
397 gIdleService
= nullptr;
400 NS_IMPL_ISUPPORTS(nsUserIdleService
, nsIUserIdleService
,
401 nsIUserIdleServiceInternal
)
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
);
417 MOZ_LOG(sLog
, LogLevel::Debug
,
418 ("idleService: Register idle observer %p for %d seconds", aObserver
,
420 #ifdef MOZ_WIDGET_ANDROID
421 __android_log_print(LOG_LEVEL
, LOG_TAG
,
422 "Register idle observer %p for %d seconds", aObserver
,
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.
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).
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
);
454 mDeltaToNextIdleSwitchInS
= aIdleTimeInS
;
457 // Ensure timer is running.
464 nsUserIdleService::RemoveIdleObserver(nsIObserver
* aObserver
,
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
);
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
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
);
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
);
506 return NS_ERROR_FAILURE
;
510 nsUserIdleService::ResetIdleTimeOut(uint32_t idleDeltaInMS
) {
511 MOZ_LOG(sLog
, LogLevel::Debug
,
512 ("idleService: Reset idle timeout (last interaction %u msec)",
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"));
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.
551 int32_t numberOfPendingNotifications
= notifyList
.Count();
553 // Bail if nothing to do.
554 if (!numberOfPendingNotifications
) {
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
]);
576 notifyList
[numberOfPendingNotifications
]->Observe(
577 this, OBSERVER_TOPIC_ACTIVE
, timeStr
.get());
583 nsUserIdleService::GetIdleTime(uint32_t* idleTime
) {
584 // Check sanity of in parameter.
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",
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
;
618 // Otherwise return the shortest time detected (in ms).
619 *idleTime
= std::min(timeSinceResetInMS
, polledIdleTimeMS
);
624 bool nsUserIdleService::PollIdleTime(uint32_t* /*aIdleTime*/) {
625 // Default behavior is not to have the ability to poll an idle time.
629 bool nsUserIdleService::UsePollMode() {
631 return PollIdleTime(&dummy
);
634 nsresult
nsUserIdleService::GetDisabled(bool* aResult
) {
635 *aResult
= mDisabled
;
639 nsresult
nsUserIdleService::SetDisabled(bool aDisabled
) {
640 mDisabled
= aDisabled
;
644 void nsUserIdleService::StaticIdleTimerCallback(nsITimer
* aTimer
,
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(¤tIdleTimeInMS
))) {
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");
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
);
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.
700 MOZ_LOG(sLog
, LogLevel::Info
,
701 ("idleService: Skipping idle callback while disabled"));
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
725 notifyList
.AppendObject(curListener
.observer
);
726 // This listener is now idle.
727 curListener
.isIdle
= true;
728 // Remember we have someone idle.
729 mIdleObserverCount
++;
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
742 int32_t numberOfPendingNotifications
= notifyList
.Count();
744 // Bail if nothing to do.
745 if (!numberOfPendingNotifications
) {
747 sLog
, LogLevel::Debug
,
748 ("idleService: **** Idle timer callback: no observers to message."));
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
--) {
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
]);
767 notifyList
[numberOfPendingNotifications
]->Observe(this, OBSERVER_TOPIC_IDLE
,
772 void nsUserIdleService::SetTimerExpiryIfBefore(TimeStamp aNextTimeout
) {
773 TimeDuration nextTimeoutDuration
= aNextTimeout
- TimeStamp::Now();
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());
786 // Bail if we don't have a timer service.
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).
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
;
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());
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
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");
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());
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
) {
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
875 "idle observers, reducing timeout to %lu msec from now",
876 MIN_IDLE_POLL_INTERVAL_MSEC
);
878 nextTimeoutAt
= pollTimeout
;
882 SetTimerExpiryIfBefore(nextTimeoutAt
);