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 "nsIdleService.h"
10 #include "nsIObserverService.h"
11 #include "nsIServiceManager.h"
13 #include "nsCOMArray.h"
14 #include "nsXULAppAPI.h"
18 #include "mozilla/dom/ContentChild.h"
19 #include "mozilla/Services.h"
20 #include "mozilla/Preferences.h"
21 #include "mozilla/Telemetry.h"
24 #ifdef MOZ_WIDGET_ANDROID
25 #include <android/log.h>
28 using namespace mozilla
;
30 // interval in milliseconds between internal idle time requests.
31 #define MIN_IDLE_POLL_INTERVAL_MSEC (5 * PR_MSEC_PER_SEC) /* 5 sec */
33 // After the twenty four hour period expires for an idle daily, this is the
34 // amount of idle time we wait for before actually firing the idle-daily
36 #define DAILY_SIGNIFICANT_IDLE_SERVICE_SEC (3 * 60)
38 // In cases where it's been longer than twenty four hours since the last
39 // idle-daily, this is the shortend amount of idle time we wait for before
40 // firing the idle-daily event.
41 #define DAILY_SHORTENED_IDLE_SERVICE_SEC 60
43 // Pref for last time (seconds since epoch) daily notification was sent.
44 #define PREF_LAST_DAILY "idle.lastDailyNotification"
46 // Number of seconds in a day.
47 #define SECONDS_PER_DAY 86400
50 static PRLogModuleInfo
*sLog
= nullptr;
53 // Use this to find previously added observers in our array:
54 class IdleListenerComparator
57 bool Equals(IdleListener a
, IdleListener b
) const
59 return (a
.observer
== b
.observer
) &&
60 (a
.reqIdleTime
== b
.reqIdleTime
);
64 ////////////////////////////////////////////////////////////////////////////////
65 //// nsIdleServiceDaily
67 NS_IMPL_ISUPPORTS(nsIdleServiceDaily
, nsIObserver
, nsISupportsWeakReference
)
70 nsIdleServiceDaily::Observe(nsISupports
*,
74 PR_LOG(sLog
, PR_LOG_DEBUG
,
75 ("nsIdleServiceDaily: Observe '%s' (%d)",
76 aTopic
, mShutdownInProgress
));
78 if (strcmp(aTopic
, "profile-after-change") == 0) {
79 // We are back. Start sending notifications again.
80 mShutdownInProgress
= false;
84 if (strcmp(aTopic
, "xpcom-will-shutdown") == 0 ||
85 strcmp(aTopic
, "profile-change-teardown") == 0) {
86 mShutdownInProgress
= true;
89 if (mShutdownInProgress
|| strcmp(aTopic
, OBSERVER_TOPIC_ACTIVE
) == 0) {
92 MOZ_ASSERT(strcmp(aTopic
, OBSERVER_TOPIC_IDLE
) == 0);
94 PR_LOG(sLog
, PR_LOG_DEBUG
,
95 ("nsIdleServiceDaily: Notifying idle-daily observers"));
96 #ifdef MOZ_WIDGET_ANDROID
97 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
98 "Notifying idle-daily observers");
101 // Send the idle-daily observer event
102 nsCOMPtr
<nsIObserverService
> observerService
=
103 mozilla::services::GetObserverService();
104 NS_ENSURE_STATE(observerService
);
105 (void)observerService
->NotifyObservers(nullptr,
106 OBSERVER_TOPIC_IDLE_DAILY
,
109 // Notify the category observers.
110 nsCOMArray
<nsIObserver
> entries
;
111 mCategoryObservers
.GetEntries(entries
);
112 for (int32_t i
= 0; i
< entries
.Count(); ++i
) {
113 (void)entries
[i
]->Observe(nullptr, OBSERVER_TOPIC_IDLE_DAILY
, nullptr);
116 // Stop observing idle for today.
117 (void)mIdleService
->RemoveIdleObserver(this, mIdleDailyTriggerWait
);
119 // Set the last idle-daily time pref.
120 int32_t nowSec
= static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC
);
121 Preferences::SetInt(PREF_LAST_DAILY
, nowSec
);
123 // Force that to be stored so we don't retrigger twice a day under
124 // any circumstances.
125 nsIPrefService
* prefs
= Preferences::GetService();
127 prefs
->SavePrefFile(nullptr);
130 PR_LOG(sLog
, PR_LOG_DEBUG
,
131 ("nsIdleServiceDaily: Storing last idle time as %d sec.", nowSec
));
132 #ifdef MOZ_WIDGET_ANDROID
133 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
134 "Storing last idle time as %d", nowSec
);
137 // Note the moment we expect to get the next timer callback
138 mExpectedTriggerTime
= PR_Now() + ((PRTime
)SECONDS_PER_DAY
*
139 (PRTime
)PR_USEC_PER_SEC
);
141 PR_LOG(sLog
, PR_LOG_DEBUG
,
142 ("nsIdleServiceDaily: Restarting daily timer"));
144 // Start timer for the next check in one day.
145 (void)mTimer
->InitWithFuncCallback(DailyCallback
,
147 SECONDS_PER_DAY
* PR_MSEC_PER_SEC
,
148 nsITimer::TYPE_ONE_SHOT
);
153 nsIdleServiceDaily::nsIdleServiceDaily(nsIIdleService
* aIdleService
)
154 : mIdleService(aIdleService
)
155 , mTimer(do_CreateInstance(NS_TIMER_CONTRACTID
))
156 , mCategoryObservers(OBSERVER_TOPIC_IDLE_DAILY
)
157 , mShutdownInProgress(false)
158 , mExpectedTriggerTime(0)
159 , mIdleDailyTriggerWait(DAILY_SIGNIFICANT_IDLE_SERVICE_SEC
)
164 nsIdleServiceDaily::Init()
166 // First check the time of the last idle-daily event notification. If it
167 // has been 24 hours or higher, or if we have never sent an idle-daily,
168 // get ready to send an idle-daily event. Otherwise set a timer targeted
169 // at 24 hours past the last idle-daily we sent.
171 int32_t nowSec
= static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC
);
172 int32_t lastDaily
= Preferences::GetInt(PREF_LAST_DAILY
, 0);
173 if (lastDaily
< 0 || lastDaily
> nowSec
) {
174 // The time is bogus, use default.
177 int32_t secondsSinceLastDaily
= nowSec
- lastDaily
;
179 PR_LOG(sLog
, PR_LOG_DEBUG
,
180 ("nsIdleServiceDaily: Init: seconds since last daily: %d",
181 secondsSinceLastDaily
));
183 // If it has been twenty four hours or more or if we have never sent an
184 // idle-daily event get ready to send it during the next idle period.
185 if (secondsSinceLastDaily
> SECONDS_PER_DAY
) {
186 // Check for a "long wait", e.g. 48-hours or more.
187 bool hasBeenLongWait
= (lastDaily
&&
188 (secondsSinceLastDaily
> (SECONDS_PER_DAY
* 2)));
190 PR_LOG(sLog
, PR_LOG_DEBUG
,
191 ("nsIdleServiceDaily: has been long wait? %d",
194 // StageIdleDaily sets up a wait for the user to become idle and then
195 // sends the idle-daily event.
196 StageIdleDaily(hasBeenLongWait
);
198 PR_LOG(sLog
, PR_LOG_DEBUG
,
199 ("nsIdleServiceDaily: Setting timer a day from now"));
200 #ifdef MOZ_WIDGET_ANDROID
201 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
202 "Setting timer a day from now");
205 // According to our last idle-daily pref, the last idle-daily was fired
206 // less then 24 hours ago. Set a wait for the amount of time remaining.
207 int32_t milliSecLeftUntilDaily
= (SECONDS_PER_DAY
- secondsSinceLastDaily
)
210 PR_LOG(sLog
, PR_LOG_DEBUG
,
211 ("nsIdleServiceDaily: Seconds till next timeout: %d",
212 (SECONDS_PER_DAY
- secondsSinceLastDaily
)));
214 // Mark the time at which we expect this to fire. On systems with faulty
215 // timers, we need to be able to cross check that the timer fired at the
217 mExpectedTriggerTime
= PR_Now() +
218 (milliSecLeftUntilDaily
* PR_USEC_PER_MSEC
);
220 (void)mTimer
->InitWithFuncCallback(DailyCallback
,
222 milliSecLeftUntilDaily
,
223 nsITimer::TYPE_ONE_SHOT
);
226 // Register for when we should terminate/pause
227 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
229 PR_LOG(sLog
, PR_LOG_DEBUG
,
230 ("nsIdleServiceDaily: Registering for system event observers."));
231 obs
->AddObserver(this, "xpcom-will-shutdown", true);
232 obs
->AddObserver(this, "profile-change-teardown", true);
233 obs
->AddObserver(this, "profile-after-change", true);
237 nsIdleServiceDaily::~nsIdleServiceDaily()
247 nsIdleServiceDaily::StageIdleDaily(bool aHasBeenLongWait
)
249 NS_ASSERTION(mIdleService
, "No idle service available?");
250 PR_LOG(sLog
, PR_LOG_DEBUG
,
251 ("nsIdleServiceDaily: Registering Idle observer callback "
252 "(short wait requested? %d)", aHasBeenLongWait
));
253 #ifdef MOZ_WIDGET_ANDROID
254 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
255 "Registering Idle observer callback");
257 mIdleDailyTriggerWait
= (aHasBeenLongWait
?
258 DAILY_SHORTENED_IDLE_SERVICE_SEC
:
259 DAILY_SIGNIFICANT_IDLE_SERVICE_SEC
);
260 (void)mIdleService
->AddIdleObserver(this, mIdleDailyTriggerWait
);
265 nsIdleServiceDaily::DailyCallback(nsITimer
* aTimer
, void* aClosure
)
267 PR_LOG(sLog
, PR_LOG_DEBUG
,
268 ("nsIdleServiceDaily: DailyCallback running"));
269 #ifdef MOZ_WIDGET_ANDROID
270 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
271 "DailyCallback running");
274 nsIdleServiceDaily
* self
= static_cast<nsIdleServiceDaily
*>(aClosure
);
276 // Check to be sure the timer didn't fire early. This currently only
277 // happens on android.
278 PRTime now
= PR_Now();
279 if (self
->mExpectedTriggerTime
&& now
< self
->mExpectedTriggerTime
) {
280 // Timer returned early, reschedule to the appropriate time.
281 PRTime delayTime
= self
->mExpectedTriggerTime
- now
;
283 // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
284 delayTime
+= 10 * PR_USEC_PER_MSEC
;
286 PR_LOG(sLog
, PR_LOG_DEBUG
, ("nsIdleServiceDaily: DailyCallback resetting timer to %lld msec",
287 delayTime
/ PR_USEC_PER_MSEC
));
288 #ifdef MOZ_WIDGET_ANDROID
289 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
290 "DailyCallback resetting timer to %lld msec",
291 delayTime
/ PR_USEC_PER_MSEC
);
294 (void)self
->mTimer
->InitWithFuncCallback(DailyCallback
,
296 delayTime
/ PR_USEC_PER_MSEC
,
297 nsITimer::TYPE_ONE_SHOT
);
301 // Register for a short term wait for idle event. When this fires we fire
302 // our idle-daily event.
303 self
->StageIdleDaily(false);
308 * The idle services goal is to notify subscribers when a certain time has
309 * passed since the last user interaction with the system.
311 * On some platforms this is defined as the last time user events reached this
312 * application, on other platforms it is a system wide thing - the preferred
313 * implementation is to use the system idle time, rather than the application
314 * idle time, as the things depending on the idle service are likely to use
315 * significant resources (network, disk, memory, cpu, etc.).
317 * When the idle service needs to use the system wide idle timer, it typically
318 * needs to poll the idle time value by the means of a timer. It needs to
319 * poll fast when it is in active idle mode (when it has a listener in the idle
320 * mode) as it needs to detect if the user is active in other applications.
322 * When the service is waiting for the first listener to become idle, or when
323 * it is only monitoring application idle time, it only needs to have the timer
324 * expire at the time the next listener goes idle.
326 * The core state of the service is determined by:
328 * - A list of listeners.
330 * - A boolean that tells if any listeners are in idle mode.
332 * - A delta value that indicates when, measured from the last non-idle time,
333 * the next listener should switch to idle mode.
335 * - An absolute time of the last time idle mode was detected (this is used to
336 * judge if we have been out of idle mode since the last invocation of the
339 * There are four entry points into the system:
341 * - A new listener is registered.
343 * - An existing listener is deregistered.
345 * - User interaction is detected.
347 * - The timer expires.
349 * When a new listener is added its idle timeout, is compared with the next idle
350 * timeout, and if lower, that time is stored as the new timeout, and the timer
351 * is reconfigured to ensure a timeout around the time the new listener should
354 * If the next idle time is above the idle time requested by the new listener
355 * it won't be informed until the timer expires, this is to avoid recursive
356 * behavior and to simplify the code. In this case the timer will be set to
359 * When an existing listener is deregistered, it is just removed from the list
360 * of active listeners, we don't stop the timer, we just let it expire.
362 * When user interaction is detected, either because it was directly detected or
363 * because we polled the system timer and found it to be unexpected low, then we
364 * check the flag that tells us if any listeners are in idle mode, if there are
365 * they are removed from idle mode and told so, and we reset our state
366 * caculating the next timeout and restart the timer if needed.
368 * ---- Build in logic
370 * In order to avoid restarting the timer endlessly, the timer function has
371 * logic that will only restart the timer, if the requested timeout is before
372 * the current timeout.
377 ////////////////////////////////////////////////////////////////////////////////
381 nsIdleService
* gIdleService
;
384 already_AddRefed
<nsIdleService
>
385 nsIdleService::GetInstance()
387 nsRefPtr
<nsIdleService
> instance(gIdleService
);
388 return instance
.forget();
391 nsIdleService::nsIdleService() : mCurrentlySetToTimeoutAt(TimeStamp()),
392 mIdleObserverCount(0),
393 mDeltaToNextIdleSwitchInS(UINT32_MAX
),
394 mLastUserInteraction(TimeStamp::Now())
398 sLog
= PR_NewLogModule("idleService");
400 MOZ_ASSERT(!gIdleService
);
402 if (XRE_GetProcessType() == GeckoProcessType_Default
) {
403 mDailyIdle
= new nsIdleServiceDaily(this);
408 nsIdleService::~nsIdleService()
415 MOZ_ASSERT(gIdleService
== this);
416 gIdleService
= nullptr;
419 NS_IMPL_ISUPPORTS(nsIdleService
, nsIIdleService
, nsIIdleServiceInternal
)
422 nsIdleService::AddIdleObserver(nsIObserver
* aObserver
, uint32_t aIdleTimeInS
)
424 NS_ENSURE_ARG_POINTER(aObserver
);
425 // We don't accept idle time at 0, and we can't handle idle time that are too
426 // high either - no more than ~136 years.
427 NS_ENSURE_ARG_RANGE(aIdleTimeInS
, 1, (UINT32_MAX
/ 10) - 1);
429 if (XRE_GetProcessType() == GeckoProcessType_Content
) {
430 dom::ContentChild
* cpc
= dom::ContentChild::GetSingleton();
431 cpc
->AddIdleObserver(aObserver
, aIdleTimeInS
);
435 PR_LOG(sLog
, PR_LOG_DEBUG
,
436 ("idleService: Register idle observer %p for %d seconds",
437 aObserver
, aIdleTimeInS
));
438 #ifdef MOZ_WIDGET_ANDROID
439 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
440 "Register idle observer %p for %d seconds",
441 aObserver
, aIdleTimeInS
);
444 // Put the time + observer in a struct we can keep:
445 IdleListener
listener(aObserver
, aIdleTimeInS
);
447 if (!mArrayListeners
.AppendElement(listener
)) {
448 return NS_ERROR_OUT_OF_MEMORY
;
451 // Create our timer callback if it's not there already.
454 mTimer
= do_CreateInstance(NS_TIMER_CONTRACTID
, &rv
);
455 NS_ENSURE_SUCCESS(rv
, rv
);
458 // Check if the newly added observer has a smaller wait time than what we
459 // are waiting for now.
460 if (mDeltaToNextIdleSwitchInS
> aIdleTimeInS
) {
461 // If it is, then this is the next to move to idle (at this point we
462 // don't care if it should have switched already).
463 PR_LOG(sLog
, PR_LOG_DEBUG
,
464 ("idleService: Register: adjusting next switch from %d to %d seconds",
465 mDeltaToNextIdleSwitchInS
, aIdleTimeInS
));
466 #ifdef MOZ_WIDGET_ANDROID
467 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
468 "Register: adjusting next switch from %d to %d seconds",
469 mDeltaToNextIdleSwitchInS
, aIdleTimeInS
);
472 mDeltaToNextIdleSwitchInS
= aIdleTimeInS
;
475 // Ensure timer is running.
482 nsIdleService::RemoveIdleObserver(nsIObserver
* aObserver
, uint32_t aTimeInS
)
485 NS_ENSURE_ARG_POINTER(aObserver
);
486 NS_ENSURE_ARG(aTimeInS
);
488 if (XRE_GetProcessType() == GeckoProcessType_Content
) {
489 dom::ContentChild
* cpc
= dom::ContentChild::GetSingleton();
490 cpc
->RemoveIdleObserver(aObserver
, aTimeInS
);
494 IdleListener
listener(aObserver
, aTimeInS
);
496 // Find the entry and remove it, if it was the last entry, we just let the
497 // existing timer run to completion (there might be a new registration in a
499 IdleListenerComparator c
;
500 nsTArray
<IdleListener
>::index_type listenerIndex
= mArrayListeners
.IndexOf(listener
, 0, c
);
501 if (listenerIndex
!= mArrayListeners
.NoIndex
) {
502 if (mArrayListeners
.ElementAt(listenerIndex
).isIdle
)
503 mIdleObserverCount
--;
504 mArrayListeners
.RemoveElementAt(listenerIndex
);
505 PR_LOG(sLog
, PR_LOG_DEBUG
,
506 ("idleService: Remove observer %p (%d seconds), %d remain idle",
507 aObserver
, aTimeInS
, mIdleObserverCount
));
508 #ifdef MOZ_WIDGET_ANDROID
509 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
510 "Remove observer %p (%d seconds), %d remain idle",
511 aObserver
, aTimeInS
, mIdleObserverCount
);
516 // If we get here, we haven't removed anything:
517 PR_LOG(sLog
, PR_LOG_WARNING
,
518 ("idleService: Failed to remove idle observer %p (%d seconds)",
519 aObserver
, aTimeInS
));
520 #ifdef MOZ_WIDGET_ANDROID
521 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
522 "Failed to remove idle observer %p (%d seconds)",
523 aObserver
, aTimeInS
);
525 return NS_ERROR_FAILURE
;
529 nsIdleService::ResetIdleTimeOut(uint32_t idleDeltaInMS
)
531 PR_LOG(sLog
, PR_LOG_DEBUG
,
532 ("idleService: Reset idle timeout (last interaction %u msec)",
536 mLastUserInteraction
= TimeStamp::Now() -
537 TimeDuration::FromMilliseconds(idleDeltaInMS
);
539 // If no one is idle, then we are done, any existing timers can keep running.
540 if (mIdleObserverCount
== 0) {
541 PR_LOG(sLog
, PR_LOG_DEBUG
,
542 ("idleService: Reset idle timeout: no idle observers"));
546 // Mark all idle services as non-idle, and calculate the next idle timeout.
547 Telemetry::AutoTimer
<Telemetry::IDLE_NOTIFY_BACK_MS
> timer
;
548 nsCOMArray
<nsIObserver
> notifyList
;
549 mDeltaToNextIdleSwitchInS
= UINT32_MAX
;
551 // Loop through all listeners, and find any that have detected idle.
552 for (uint32_t i
= 0; i
< mArrayListeners
.Length(); i
++) {
553 IdleListener
& curListener
= mArrayListeners
.ElementAt(i
);
555 // If the listener was idle, then he shouldn't be any longer.
556 if (curListener
.isIdle
) {
557 notifyList
.AppendObject(curListener
.observer
);
558 curListener
.isIdle
= false;
561 // Check if the listener is the next one to timeout.
562 mDeltaToNextIdleSwitchInS
= std::min(mDeltaToNextIdleSwitchInS
,
563 curListener
.reqIdleTime
);
566 // When we are done, then we wont have anyone idle.
567 mIdleObserverCount
= 0;
569 // Restart the idle timer, and do so before anyone can delay us.
572 int32_t numberOfPendingNotifications
= notifyList
.Count();
573 Telemetry::Accumulate(Telemetry::IDLE_NOTIFY_BACK_LISTENERS
,
574 numberOfPendingNotifications
);
576 // Bail if nothing to do.
577 if (!numberOfPendingNotifications
) {
581 // Now send "active" events to all, if any should have timed out already,
582 // then they will be reawaken by the timer that is already running.
584 // We need a text string to send with any state change events.
585 nsAutoString timeStr
;
587 timeStr
.AppendInt((int32_t)(idleDeltaInMS
/ PR_MSEC_PER_SEC
));
589 // Send the "non-idle" events.
590 while (numberOfPendingNotifications
--) {
591 PR_LOG(sLog
, PR_LOG_DEBUG
,
592 ("idleService: Reset idle timeout: tell observer %p user is back",
593 notifyList
[numberOfPendingNotifications
]));
594 #ifdef MOZ_WIDGET_ANDROID
595 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
596 "Reset idle timeout: tell observer %p user is back",
597 notifyList
[numberOfPendingNotifications
]);
599 notifyList
[numberOfPendingNotifications
]->Observe(this,
600 OBSERVER_TOPIC_ACTIVE
,
607 nsIdleService::GetIdleTime(uint32_t* idleTime
)
609 // Check sanity of in parameter.
611 return NS_ERROR_NULL_POINTER
;
614 // Polled idle time in ms.
615 uint32_t polledIdleTimeMS
;
617 bool polledIdleTimeIsValid
= PollIdleTime(&polledIdleTimeMS
);
619 PR_LOG(sLog
, PR_LOG_DEBUG
,
620 ("idleService: Get idle time: polled %u msec, valid = %d",
621 polledIdleTimeMS
, polledIdleTimeIsValid
));
623 // timeSinceReset is in milliseconds.
624 TimeDuration timeSinceReset
= TimeStamp::Now() - mLastUserInteraction
;
625 uint32_t timeSinceResetInMS
= timeSinceReset
.ToMilliseconds();
627 PR_LOG(sLog
, PR_LOG_DEBUG
,
628 ("idleService: Get idle time: time since reset %u msec",
629 timeSinceResetInMS
));
630 #ifdef MOZ_WIDGET_ANDROID
631 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
632 "Get idle time: time since reset %u msec",
636 // If we did't get pulled data, return the time since last idle reset.
637 if (!polledIdleTimeIsValid
) {
638 // We need to convert to ms before returning the time.
639 *idleTime
= timeSinceResetInMS
;
643 // Otherwise return the shortest time detected (in ms).
644 *idleTime
= std::min(timeSinceResetInMS
, polledIdleTimeMS
);
651 nsIdleService::PollIdleTime(uint32_t* /*aIdleTime*/)
653 // Default behavior is not to have the ability to poll an idle time.
658 nsIdleService::UsePollMode()
661 return PollIdleTime(&dummy
);
665 nsIdleService::StaticIdleTimerCallback(nsITimer
* aTimer
, void* aClosure
)
667 static_cast<nsIdleService
*>(aClosure
)->IdleTimerCallback();
671 nsIdleService::IdleTimerCallback(void)
673 // Remember that we no longer have a timer running.
674 mCurrentlySetToTimeoutAt
= TimeStamp();
676 // Find the last detected idle time.
677 uint32_t lastIdleTimeInMS
= static_cast<uint32_t>((TimeStamp::Now() -
678 mLastUserInteraction
).ToMilliseconds());
679 // Get the current idle time.
680 uint32_t currentIdleTimeInMS
;
682 if (NS_FAILED(GetIdleTime(¤tIdleTimeInMS
))) {
683 PR_LOG(sLog
, PR_LOG_ALWAYS
,
684 ("idleService: Idle timer callback: failed to get idle time"));
685 #ifdef MOZ_WIDGET_ANDROID
686 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
687 "Idle timer callback: failed to get idle time");
692 PR_LOG(sLog
, PR_LOG_DEBUG
,
693 ("idleService: Idle timer callback: current idle time %u msec",
694 currentIdleTimeInMS
));
695 #ifdef MOZ_WIDGET_ANDROID
696 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
697 "Idle timer callback: current idle time %u msec",
698 currentIdleTimeInMS
);
701 // Check if we have had some user interaction we didn't handle previously
702 // we do the calculation in ms to lessen the chance for rounding errors to
703 // trigger wrong results.
704 if (lastIdleTimeInMS
> currentIdleTimeInMS
)
706 // We had user activity, so handle that part first (to ensure the listeners
707 // don't risk getting an non-idle after they get a new idle indication.
708 ResetIdleTimeOut(currentIdleTimeInMS
);
710 // NOTE: We can't bail here, as we might have something already timed out.
713 // Find the idle time in S.
714 uint32_t currentIdleTimeInS
= currentIdleTimeInMS
/ PR_MSEC_PER_SEC
;
716 // Restart timer and bail if no-one are expected to be in idle
717 if (mDeltaToNextIdleSwitchInS
> currentIdleTimeInS
) {
718 // If we didn't expect anyone to be idle, then just re-start the timer.
723 // Tell expired listeners they are expired,and find the next timeout
724 Telemetry::AutoTimer
<Telemetry::IDLE_NOTIFY_IDLE_MS
> timer
;
726 // We need to initialise the time to the next idle switch.
727 mDeltaToNextIdleSwitchInS
= UINT32_MAX
;
729 // Create list of observers that should be notified.
730 nsCOMArray
<nsIObserver
> notifyList
;
732 for (uint32_t i
= 0; i
< mArrayListeners
.Length(); i
++) {
733 IdleListener
& curListener
= mArrayListeners
.ElementAt(i
);
735 // We are only interested in items, that are not in the idle state.
736 if (!curListener
.isIdle
) {
737 // If they have an idle time smaller than the actual idle time.
738 if (curListener
.reqIdleTime
<= currentIdleTimeInS
) {
739 // Then add the listener to the list of listeners that should be
741 notifyList
.AppendObject(curListener
.observer
);
742 // This listener is now idle.
743 curListener
.isIdle
= true;
744 // Remember we have someone idle.
745 mIdleObserverCount
++;
747 // Listeners that are not timed out yet are candidates for timing out.
748 mDeltaToNextIdleSwitchInS
= std::min(mDeltaToNextIdleSwitchInS
,
749 curListener
.reqIdleTime
);
754 // Restart the timer before any notifications that could slow us down are
758 int32_t numberOfPendingNotifications
= notifyList
.Count();
759 Telemetry::Accumulate(Telemetry::IDLE_NOTIFY_IDLE_LISTENERS
,
760 numberOfPendingNotifications
);
762 // Bail if nothing to do.
763 if (!numberOfPendingNotifications
) {
764 PR_LOG(sLog
, PR_LOG_DEBUG
,
765 ("idleService: **** Idle timer callback: no observers to message."));
769 // We need a text string to send with any state change events.
770 nsAutoString timeStr
;
771 timeStr
.AppendInt(currentIdleTimeInS
);
773 // Notify all listeners that just timed out.
774 while (numberOfPendingNotifications
--) {
775 PR_LOG(sLog
, PR_LOG_DEBUG
,
776 ("idleService: **** Idle timer callback: tell observer %p user is idle",
777 notifyList
[numberOfPendingNotifications
]));
778 #ifdef MOZ_WIDGET_ANDROID
779 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
780 "Idle timer callback: tell observer %p user is idle",
781 notifyList
[numberOfPendingNotifications
]);
783 notifyList
[numberOfPendingNotifications
]->Observe(this,
790 nsIdleService::SetTimerExpiryIfBefore(TimeStamp aNextTimeout
)
792 #if defined(PR_LOGGING) || defined(MOZ_WIDGET_ANDROID)
793 TimeDuration nextTimeoutDuration
= aNextTimeout
- TimeStamp::Now();
796 PR_LOG(sLog
, PR_LOG_DEBUG
,
797 ("idleService: SetTimerExpiryIfBefore: next timeout %0.f msec from now",
798 nextTimeoutDuration
.ToMilliseconds()));
800 #ifdef MOZ_WIDGET_ANDROID
801 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
802 "SetTimerExpiryIfBefore: next timeout %0.f msec from now",
803 nextTimeoutDuration
.ToMilliseconds());
806 // Bail if we don't have a timer service.
811 // If the new timeout is before the old one or we don't have a timer running,
812 // then restart the timer.
813 if (mCurrentlySetToTimeoutAt
.IsNull() ||
814 mCurrentlySetToTimeoutAt
> aNextTimeout
) {
816 mCurrentlySetToTimeoutAt
= aNextTimeout
;
818 // Stop the current timer (it's ok to try'n stop it, even it isn't running).
821 // Check that the timeout is actually in the future, otherwise make it so.
822 TimeStamp currentTime
= TimeStamp::Now();
823 if (currentTime
> mCurrentlySetToTimeoutAt
) {
824 mCurrentlySetToTimeoutAt
= currentTime
;
827 // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
828 mCurrentlySetToTimeoutAt
+= TimeDuration::FromMilliseconds(10);
830 TimeDuration deltaTime
= mCurrentlySetToTimeoutAt
- currentTime
;
831 PR_LOG(sLog
, PR_LOG_DEBUG
,
832 ("idleService: IdleService reset timer expiry to %0.f msec from now",
833 deltaTime
.ToMilliseconds()));
834 #ifdef MOZ_WIDGET_ANDROID
835 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
836 "reset timer expiry to %0.f msec from now",
837 deltaTime
.ToMilliseconds());
841 mTimer
->InitWithFuncCallback(StaticIdleTimerCallback
,
843 deltaTime
.ToMilliseconds(),
844 nsITimer::TYPE_ONE_SHOT
);
850 nsIdleService::ReconfigureTimer(void)
852 // Check if either someone is idle, or someone will become idle.
853 if ((mIdleObserverCount
== 0) && UINT32_MAX
== mDeltaToNextIdleSwitchInS
) {
854 // If not, just let any existing timers run to completion
856 PR_LOG(sLog
, PR_LOG_DEBUG
,
857 ("idleService: ReconfigureTimer: no idle or waiting observers"));
858 #ifdef MOZ_WIDGET_ANDROID
859 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
860 "ReconfigureTimer: no idle or waiting observers");
865 // Find the next timeout value, assuming we are not polling.
867 // We need to store the current time, so we don't get artifacts from the time
868 // ticking while we are processing.
869 TimeStamp curTime
= TimeStamp::Now();
871 TimeStamp nextTimeoutAt
= mLastUserInteraction
+
872 TimeDuration::FromSeconds(mDeltaToNextIdleSwitchInS
);
874 #if defined(PR_LOGGING) || defined(MOZ_WIDGET_ANDROID)
875 TimeDuration nextTimeoutDuration
= nextTimeoutAt
- curTime
;
878 PR_LOG(sLog
, PR_LOG_DEBUG
,
879 ("idleService: next timeout %0.f msec from now",
880 nextTimeoutDuration
.ToMilliseconds()));
882 #ifdef MOZ_WIDGET_ANDROID
883 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
884 "next timeout %0.f msec from now",
885 nextTimeoutDuration
.ToMilliseconds());
888 // Check if we should correct the timeout time because we should poll before.
889 if ((mIdleObserverCount
> 0) && UsePollMode()) {
890 TimeStamp pollTimeout
=
891 curTime
+ TimeDuration::FromMilliseconds(MIN_IDLE_POLL_INTERVAL_MSEC
);
893 if (nextTimeoutAt
> pollTimeout
) {
894 PR_LOG(sLog
, PR_LOG_DEBUG
,
895 ("idleService: idle observers, reducing timeout to %lu msec from now",
896 MIN_IDLE_POLL_INTERVAL_MSEC
));
897 #ifdef MOZ_WIDGET_ANDROID
898 __android_log_print(ANDROID_LOG_INFO
, "IdleService",
899 "idle observers, reducing timeout to %lu msec from now",
900 MIN_IDLE_POLL_INTERVAL_MSEC
);
902 nextTimeoutAt
= pollTimeout
;
906 SetTimerExpiryIfBefore(nextTimeoutAt
);