1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/xpwidgets/nsIdleService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,907 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:expandtab:shiftwidth=2:tabstop=2: 1.6 + */ 1.7 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.8 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.10 + 1.11 +#include "nsIdleService.h" 1.12 +#include "nsString.h" 1.13 +#include "nsIObserverService.h" 1.14 +#include "nsIServiceManager.h" 1.15 +#include "nsDebug.h" 1.16 +#include "nsCOMArray.h" 1.17 +#include "nsXULAppAPI.h" 1.18 +#include "prinrval.h" 1.19 +#include "prlog.h" 1.20 +#include "prtime.h" 1.21 +#include "mozilla/dom/ContentChild.h" 1.22 +#include "mozilla/Services.h" 1.23 +#include "mozilla/Preferences.h" 1.24 +#include "mozilla/Telemetry.h" 1.25 +#include <algorithm> 1.26 + 1.27 +#ifdef MOZ_WIDGET_ANDROID 1.28 +#include <android/log.h> 1.29 +#endif 1.30 + 1.31 +using namespace mozilla; 1.32 + 1.33 +// interval in milliseconds between internal idle time requests. 1.34 +#define MIN_IDLE_POLL_INTERVAL_MSEC (5 * PR_MSEC_PER_SEC) /* 5 sec */ 1.35 + 1.36 +// After the twenty four hour period expires for an idle daily, this is the 1.37 +// amount of idle time we wait for before actually firing the idle-daily 1.38 +// event. 1.39 +#define DAILY_SIGNIFICANT_IDLE_SERVICE_SEC (3 * 60) 1.40 + 1.41 +// In cases where it's been longer than twenty four hours since the last 1.42 +// idle-daily, this is the shortend amount of idle time we wait for before 1.43 +// firing the idle-daily event. 1.44 +#define DAILY_SHORTENED_IDLE_SERVICE_SEC 60 1.45 + 1.46 +// Pref for last time (seconds since epoch) daily notification was sent. 1.47 +#define PREF_LAST_DAILY "idle.lastDailyNotification" 1.48 + 1.49 +// Number of seconds in a day. 1.50 +#define SECONDS_PER_DAY 86400 1.51 + 1.52 +#ifdef PR_LOGGING 1.53 +static PRLogModuleInfo *sLog = nullptr; 1.54 +#endif 1.55 + 1.56 +// Use this to find previously added observers in our array: 1.57 +class IdleListenerComparator 1.58 +{ 1.59 +public: 1.60 + bool Equals(IdleListener a, IdleListener b) const 1.61 + { 1.62 + return (a.observer == b.observer) && 1.63 + (a.reqIdleTime == b.reqIdleTime); 1.64 + } 1.65 +}; 1.66 + 1.67 +//////////////////////////////////////////////////////////////////////////////// 1.68 +//// nsIdleServiceDaily 1.69 + 1.70 +NS_IMPL_ISUPPORTS(nsIdleServiceDaily, nsIObserver, nsISupportsWeakReference) 1.71 + 1.72 +NS_IMETHODIMP 1.73 +nsIdleServiceDaily::Observe(nsISupports *, 1.74 + const char *aTopic, 1.75 + const char16_t *) 1.76 +{ 1.77 + PR_LOG(sLog, PR_LOG_DEBUG, 1.78 + ("nsIdleServiceDaily: Observe '%s' (%d)", 1.79 + aTopic, mShutdownInProgress)); 1.80 + 1.81 + if (strcmp(aTopic, "profile-after-change") == 0) { 1.82 + // We are back. Start sending notifications again. 1.83 + mShutdownInProgress = false; 1.84 + return NS_OK; 1.85 + } 1.86 + 1.87 + if (strcmp(aTopic, "xpcom-will-shutdown") == 0 || 1.88 + strcmp(aTopic, "profile-change-teardown") == 0) { 1.89 + mShutdownInProgress = true; 1.90 + } 1.91 + 1.92 + if (mShutdownInProgress || strcmp(aTopic, OBSERVER_TOPIC_ACTIVE) == 0) { 1.93 + return NS_OK; 1.94 + } 1.95 + MOZ_ASSERT(strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0); 1.96 + 1.97 + PR_LOG(sLog, PR_LOG_DEBUG, 1.98 + ("nsIdleServiceDaily: Notifying idle-daily observers")); 1.99 +#ifdef MOZ_WIDGET_ANDROID 1.100 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.101 + "Notifying idle-daily observers"); 1.102 +#endif 1.103 + 1.104 + // Send the idle-daily observer event 1.105 + nsCOMPtr<nsIObserverService> observerService = 1.106 + mozilla::services::GetObserverService(); 1.107 + NS_ENSURE_STATE(observerService); 1.108 + (void)observerService->NotifyObservers(nullptr, 1.109 + OBSERVER_TOPIC_IDLE_DAILY, 1.110 + nullptr); 1.111 + 1.112 + // Notify the category observers. 1.113 + nsCOMArray<nsIObserver> entries; 1.114 + mCategoryObservers.GetEntries(entries); 1.115 + for (int32_t i = 0; i < entries.Count(); ++i) { 1.116 + (void)entries[i]->Observe(nullptr, OBSERVER_TOPIC_IDLE_DAILY, nullptr); 1.117 + } 1.118 + 1.119 + // Stop observing idle for today. 1.120 + (void)mIdleService->RemoveIdleObserver(this, mIdleDailyTriggerWait); 1.121 + 1.122 + // Set the last idle-daily time pref. 1.123 + int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC); 1.124 + Preferences::SetInt(PREF_LAST_DAILY, nowSec); 1.125 + 1.126 + // Force that to be stored so we don't retrigger twice a day under 1.127 + // any circumstances. 1.128 + nsIPrefService* prefs = Preferences::GetService(); 1.129 + if (prefs) { 1.130 + prefs->SavePrefFile(nullptr); 1.131 + } 1.132 + 1.133 + PR_LOG(sLog, PR_LOG_DEBUG, 1.134 + ("nsIdleServiceDaily: Storing last idle time as %d sec.", nowSec)); 1.135 +#ifdef MOZ_WIDGET_ANDROID 1.136 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.137 + "Storing last idle time as %d", nowSec); 1.138 +#endif 1.139 + 1.140 + // Note the moment we expect to get the next timer callback 1.141 + mExpectedTriggerTime = PR_Now() + ((PRTime)SECONDS_PER_DAY * 1.142 + (PRTime)PR_USEC_PER_SEC); 1.143 + 1.144 + PR_LOG(sLog, PR_LOG_DEBUG, 1.145 + ("nsIdleServiceDaily: Restarting daily timer")); 1.146 + 1.147 + // Start timer for the next check in one day. 1.148 + (void)mTimer->InitWithFuncCallback(DailyCallback, 1.149 + this, 1.150 + SECONDS_PER_DAY * PR_MSEC_PER_SEC, 1.151 + nsITimer::TYPE_ONE_SHOT); 1.152 + 1.153 + return NS_OK; 1.154 +} 1.155 + 1.156 +nsIdleServiceDaily::nsIdleServiceDaily(nsIIdleService* aIdleService) 1.157 + : mIdleService(aIdleService) 1.158 + , mTimer(do_CreateInstance(NS_TIMER_CONTRACTID)) 1.159 + , mCategoryObservers(OBSERVER_TOPIC_IDLE_DAILY) 1.160 + , mShutdownInProgress(false) 1.161 + , mExpectedTriggerTime(0) 1.162 + , mIdleDailyTriggerWait(DAILY_SIGNIFICANT_IDLE_SERVICE_SEC) 1.163 +{ 1.164 +} 1.165 + 1.166 +void 1.167 +nsIdleServiceDaily::Init() 1.168 +{ 1.169 + // First check the time of the last idle-daily event notification. If it 1.170 + // has been 24 hours or higher, or if we have never sent an idle-daily, 1.171 + // get ready to send an idle-daily event. Otherwise set a timer targeted 1.172 + // at 24 hours past the last idle-daily we sent. 1.173 + 1.174 + int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC); 1.175 + int32_t lastDaily = Preferences::GetInt(PREF_LAST_DAILY, 0); 1.176 + if (lastDaily < 0 || lastDaily > nowSec) { 1.177 + // The time is bogus, use default. 1.178 + lastDaily = 0; 1.179 + } 1.180 + int32_t secondsSinceLastDaily = nowSec - lastDaily; 1.181 + 1.182 + PR_LOG(sLog, PR_LOG_DEBUG, 1.183 + ("nsIdleServiceDaily: Init: seconds since last daily: %d", 1.184 + secondsSinceLastDaily)); 1.185 + 1.186 + // If it has been twenty four hours or more or if we have never sent an 1.187 + // idle-daily event get ready to send it during the next idle period. 1.188 + if (secondsSinceLastDaily > SECONDS_PER_DAY) { 1.189 + // Check for a "long wait", e.g. 48-hours or more. 1.190 + bool hasBeenLongWait = (lastDaily && 1.191 + (secondsSinceLastDaily > (SECONDS_PER_DAY * 2))); 1.192 + 1.193 + PR_LOG(sLog, PR_LOG_DEBUG, 1.194 + ("nsIdleServiceDaily: has been long wait? %d", 1.195 + hasBeenLongWait)); 1.196 + 1.197 + // StageIdleDaily sets up a wait for the user to become idle and then 1.198 + // sends the idle-daily event. 1.199 + StageIdleDaily(hasBeenLongWait); 1.200 + } else { 1.201 + PR_LOG(sLog, PR_LOG_DEBUG, 1.202 + ("nsIdleServiceDaily: Setting timer a day from now")); 1.203 +#ifdef MOZ_WIDGET_ANDROID 1.204 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.205 + "Setting timer a day from now"); 1.206 +#endif 1.207 + 1.208 + // According to our last idle-daily pref, the last idle-daily was fired 1.209 + // less then 24 hours ago. Set a wait for the amount of time remaining. 1.210 + int32_t milliSecLeftUntilDaily = (SECONDS_PER_DAY - secondsSinceLastDaily) 1.211 + * PR_MSEC_PER_SEC; 1.212 + 1.213 + PR_LOG(sLog, PR_LOG_DEBUG, 1.214 + ("nsIdleServiceDaily: Seconds till next timeout: %d", 1.215 + (SECONDS_PER_DAY - secondsSinceLastDaily))); 1.216 + 1.217 + // Mark the time at which we expect this to fire. On systems with faulty 1.218 + // timers, we need to be able to cross check that the timer fired at the 1.219 + // expected time. 1.220 + mExpectedTriggerTime = PR_Now() + 1.221 + (milliSecLeftUntilDaily * PR_USEC_PER_MSEC); 1.222 + 1.223 + (void)mTimer->InitWithFuncCallback(DailyCallback, 1.224 + this, 1.225 + milliSecLeftUntilDaily, 1.226 + nsITimer::TYPE_ONE_SHOT); 1.227 + } 1.228 + 1.229 + // Register for when we should terminate/pause 1.230 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.231 + if (obs) { 1.232 + PR_LOG(sLog, PR_LOG_DEBUG, 1.233 + ("nsIdleServiceDaily: Registering for system event observers.")); 1.234 + obs->AddObserver(this, "xpcom-will-shutdown", true); 1.235 + obs->AddObserver(this, "profile-change-teardown", true); 1.236 + obs->AddObserver(this, "profile-after-change", true); 1.237 + } 1.238 +} 1.239 + 1.240 +nsIdleServiceDaily::~nsIdleServiceDaily() 1.241 +{ 1.242 + if (mTimer) { 1.243 + mTimer->Cancel(); 1.244 + mTimer = nullptr; 1.245 + } 1.246 +} 1.247 + 1.248 + 1.249 +void 1.250 +nsIdleServiceDaily::StageIdleDaily(bool aHasBeenLongWait) 1.251 +{ 1.252 + NS_ASSERTION(mIdleService, "No idle service available?"); 1.253 + PR_LOG(sLog, PR_LOG_DEBUG, 1.254 + ("nsIdleServiceDaily: Registering Idle observer callback " 1.255 + "(short wait requested? %d)", aHasBeenLongWait)); 1.256 +#ifdef MOZ_WIDGET_ANDROID 1.257 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.258 + "Registering Idle observer callback"); 1.259 +#endif 1.260 + mIdleDailyTriggerWait = (aHasBeenLongWait ? 1.261 + DAILY_SHORTENED_IDLE_SERVICE_SEC : 1.262 + DAILY_SIGNIFICANT_IDLE_SERVICE_SEC); 1.263 + (void)mIdleService->AddIdleObserver(this, mIdleDailyTriggerWait); 1.264 +} 1.265 + 1.266 +// static 1.267 +void 1.268 +nsIdleServiceDaily::DailyCallback(nsITimer* aTimer, void* aClosure) 1.269 +{ 1.270 + PR_LOG(sLog, PR_LOG_DEBUG, 1.271 + ("nsIdleServiceDaily: DailyCallback running")); 1.272 +#ifdef MOZ_WIDGET_ANDROID 1.273 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.274 + "DailyCallback running"); 1.275 +#endif 1.276 + 1.277 + nsIdleServiceDaily* self = static_cast<nsIdleServiceDaily*>(aClosure); 1.278 + 1.279 + // Check to be sure the timer didn't fire early. This currently only 1.280 + // happens on android. 1.281 + PRTime now = PR_Now(); 1.282 + if (self->mExpectedTriggerTime && now < self->mExpectedTriggerTime) { 1.283 + // Timer returned early, reschedule to the appropriate time. 1.284 + PRTime delayTime = self->mExpectedTriggerTime - now; 1.285 + 1.286 + // Add 10 ms to ensure we don't undershoot, and never get a "0" timer. 1.287 + delayTime += 10 * PR_USEC_PER_MSEC; 1.288 + 1.289 + PR_LOG(sLog, PR_LOG_DEBUG, ("nsIdleServiceDaily: DailyCallback resetting timer to %lld msec", 1.290 + delayTime / PR_USEC_PER_MSEC)); 1.291 +#ifdef MOZ_WIDGET_ANDROID 1.292 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.293 + "DailyCallback resetting timer to %lld msec", 1.294 + delayTime / PR_USEC_PER_MSEC); 1.295 +#endif 1.296 + 1.297 + (void)self->mTimer->InitWithFuncCallback(DailyCallback, 1.298 + self, 1.299 + delayTime / PR_USEC_PER_MSEC, 1.300 + nsITimer::TYPE_ONE_SHOT); 1.301 + return; 1.302 + } 1.303 + 1.304 + // Register for a short term wait for idle event. When this fires we fire 1.305 + // our idle-daily event. 1.306 + self->StageIdleDaily(false); 1.307 +} 1.308 + 1.309 + 1.310 +/** 1.311 + * The idle services goal is to notify subscribers when a certain time has 1.312 + * passed since the last user interaction with the system. 1.313 + * 1.314 + * On some platforms this is defined as the last time user events reached this 1.315 + * application, on other platforms it is a system wide thing - the preferred 1.316 + * implementation is to use the system idle time, rather than the application 1.317 + * idle time, as the things depending on the idle service are likely to use 1.318 + * significant resources (network, disk, memory, cpu, etc.). 1.319 + * 1.320 + * When the idle service needs to use the system wide idle timer, it typically 1.321 + * needs to poll the idle time value by the means of a timer. It needs to 1.322 + * poll fast when it is in active idle mode (when it has a listener in the idle 1.323 + * mode) as it needs to detect if the user is active in other applications. 1.324 + * 1.325 + * When the service is waiting for the first listener to become idle, or when 1.326 + * it is only monitoring application idle time, it only needs to have the timer 1.327 + * expire at the time the next listener goes idle. 1.328 + * 1.329 + * The core state of the service is determined by: 1.330 + * 1.331 + * - A list of listeners. 1.332 + * 1.333 + * - A boolean that tells if any listeners are in idle mode. 1.334 + * 1.335 + * - A delta value that indicates when, measured from the last non-idle time, 1.336 + * the next listener should switch to idle mode. 1.337 + * 1.338 + * - An absolute time of the last time idle mode was detected (this is used to 1.339 + * judge if we have been out of idle mode since the last invocation of the 1.340 + * service. 1.341 + * 1.342 + * There are four entry points into the system: 1.343 + * 1.344 + * - A new listener is registered. 1.345 + * 1.346 + * - An existing listener is deregistered. 1.347 + * 1.348 + * - User interaction is detected. 1.349 + * 1.350 + * - The timer expires. 1.351 + * 1.352 + * When a new listener is added its idle timeout, is compared with the next idle 1.353 + * timeout, and if lower, that time is stored as the new timeout, and the timer 1.354 + * is reconfigured to ensure a timeout around the time the new listener should 1.355 + * timeout. 1.356 + * 1.357 + * If the next idle time is above the idle time requested by the new listener 1.358 + * it won't be informed until the timer expires, this is to avoid recursive 1.359 + * behavior and to simplify the code. In this case the timer will be set to 1.360 + * about 10 ms. 1.361 + * 1.362 + * When an existing listener is deregistered, it is just removed from the list 1.363 + * of active listeners, we don't stop the timer, we just let it expire. 1.364 + * 1.365 + * When user interaction is detected, either because it was directly detected or 1.366 + * because we polled the system timer and found it to be unexpected low, then we 1.367 + * check the flag that tells us if any listeners are in idle mode, if there are 1.368 + * they are removed from idle mode and told so, and we reset our state 1.369 + * caculating the next timeout and restart the timer if needed. 1.370 + * 1.371 + * ---- Build in logic 1.372 + * 1.373 + * In order to avoid restarting the timer endlessly, the timer function has 1.374 + * logic that will only restart the timer, if the requested timeout is before 1.375 + * the current timeout. 1.376 + * 1.377 + */ 1.378 + 1.379 + 1.380 +//////////////////////////////////////////////////////////////////////////////// 1.381 +//// nsIdleService 1.382 + 1.383 +namespace { 1.384 +nsIdleService* gIdleService; 1.385 +} 1.386 + 1.387 +already_AddRefed<nsIdleService> 1.388 +nsIdleService::GetInstance() 1.389 +{ 1.390 + nsRefPtr<nsIdleService> instance(gIdleService); 1.391 + return instance.forget(); 1.392 +} 1.393 + 1.394 +nsIdleService::nsIdleService() : mCurrentlySetToTimeoutAt(TimeStamp()), 1.395 + mIdleObserverCount(0), 1.396 + mDeltaToNextIdleSwitchInS(UINT32_MAX), 1.397 + mLastUserInteraction(TimeStamp::Now()) 1.398 +{ 1.399 +#ifdef PR_LOGGING 1.400 + if (sLog == nullptr) 1.401 + sLog = PR_NewLogModule("idleService"); 1.402 +#endif 1.403 + MOZ_ASSERT(!gIdleService); 1.404 + gIdleService = this; 1.405 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.406 + mDailyIdle = new nsIdleServiceDaily(this); 1.407 + mDailyIdle->Init(); 1.408 + } 1.409 +} 1.410 + 1.411 +nsIdleService::~nsIdleService() 1.412 +{ 1.413 + if(mTimer) { 1.414 + mTimer->Cancel(); 1.415 + } 1.416 + 1.417 + 1.418 + MOZ_ASSERT(gIdleService == this); 1.419 + gIdleService = nullptr; 1.420 +} 1.421 + 1.422 +NS_IMPL_ISUPPORTS(nsIdleService, nsIIdleService, nsIIdleServiceInternal) 1.423 + 1.424 +NS_IMETHODIMP 1.425 +nsIdleService::AddIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS) 1.426 +{ 1.427 + NS_ENSURE_ARG_POINTER(aObserver); 1.428 + // We don't accept idle time at 0, and we can't handle idle time that are too 1.429 + // high either - no more than ~136 years. 1.430 + NS_ENSURE_ARG_RANGE(aIdleTimeInS, 1, (UINT32_MAX / 10) - 1); 1.431 + 1.432 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.433 + dom::ContentChild* cpc = dom::ContentChild::GetSingleton(); 1.434 + cpc->AddIdleObserver(aObserver, aIdleTimeInS); 1.435 + return NS_OK; 1.436 + } 1.437 + 1.438 + PR_LOG(sLog, PR_LOG_DEBUG, 1.439 + ("idleService: Register idle observer %p for %d seconds", 1.440 + aObserver, aIdleTimeInS)); 1.441 +#ifdef MOZ_WIDGET_ANDROID 1.442 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.443 + "Register idle observer %p for %d seconds", 1.444 + aObserver, aIdleTimeInS); 1.445 +#endif 1.446 + 1.447 + // Put the time + observer in a struct we can keep: 1.448 + IdleListener listener(aObserver, aIdleTimeInS); 1.449 + 1.450 + if (!mArrayListeners.AppendElement(listener)) { 1.451 + return NS_ERROR_OUT_OF_MEMORY; 1.452 + } 1.453 + 1.454 + // Create our timer callback if it's not there already. 1.455 + if (!mTimer) { 1.456 + nsresult rv; 1.457 + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); 1.458 + NS_ENSURE_SUCCESS(rv, rv); 1.459 + } 1.460 + 1.461 + // Check if the newly added observer has a smaller wait time than what we 1.462 + // are waiting for now. 1.463 + if (mDeltaToNextIdleSwitchInS > aIdleTimeInS) { 1.464 + // If it is, then this is the next to move to idle (at this point we 1.465 + // don't care if it should have switched already). 1.466 + PR_LOG(sLog, PR_LOG_DEBUG, 1.467 + ("idleService: Register: adjusting next switch from %d to %d seconds", 1.468 + mDeltaToNextIdleSwitchInS, aIdleTimeInS)); 1.469 +#ifdef MOZ_WIDGET_ANDROID 1.470 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.471 + "Register: adjusting next switch from %d to %d seconds", 1.472 + mDeltaToNextIdleSwitchInS, aIdleTimeInS); 1.473 +#endif 1.474 + 1.475 + mDeltaToNextIdleSwitchInS = aIdleTimeInS; 1.476 + } 1.477 + 1.478 + // Ensure timer is running. 1.479 + ReconfigureTimer(); 1.480 + 1.481 + return NS_OK; 1.482 +} 1.483 + 1.484 +NS_IMETHODIMP 1.485 +nsIdleService::RemoveIdleObserver(nsIObserver* aObserver, uint32_t aTimeInS) 1.486 +{ 1.487 + 1.488 + NS_ENSURE_ARG_POINTER(aObserver); 1.489 + NS_ENSURE_ARG(aTimeInS); 1.490 + 1.491 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.492 + dom::ContentChild* cpc = dom::ContentChild::GetSingleton(); 1.493 + cpc->RemoveIdleObserver(aObserver, aTimeInS); 1.494 + return NS_OK; 1.495 + } 1.496 + 1.497 + IdleListener listener(aObserver, aTimeInS); 1.498 + 1.499 + // Find the entry and remove it, if it was the last entry, we just let the 1.500 + // existing timer run to completion (there might be a new registration in a 1.501 + // little while. 1.502 + IdleListenerComparator c; 1.503 + nsTArray<IdleListener>::index_type listenerIndex = mArrayListeners.IndexOf(listener, 0, c); 1.504 + if (listenerIndex != mArrayListeners.NoIndex) { 1.505 + if (mArrayListeners.ElementAt(listenerIndex).isIdle) 1.506 + mIdleObserverCount--; 1.507 + mArrayListeners.RemoveElementAt(listenerIndex); 1.508 + PR_LOG(sLog, PR_LOG_DEBUG, 1.509 + ("idleService: Remove observer %p (%d seconds), %d remain idle", 1.510 + aObserver, aTimeInS, mIdleObserverCount)); 1.511 +#ifdef MOZ_WIDGET_ANDROID 1.512 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.513 + "Remove observer %p (%d seconds), %d remain idle", 1.514 + aObserver, aTimeInS, mIdleObserverCount); 1.515 +#endif 1.516 + return NS_OK; 1.517 + } 1.518 + 1.519 + // If we get here, we haven't removed anything: 1.520 + PR_LOG(sLog, PR_LOG_WARNING, 1.521 + ("idleService: Failed to remove idle observer %p (%d seconds)", 1.522 + aObserver, aTimeInS)); 1.523 +#ifdef MOZ_WIDGET_ANDROID 1.524 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.525 + "Failed to remove idle observer %p (%d seconds)", 1.526 + aObserver, aTimeInS); 1.527 +#endif 1.528 + return NS_ERROR_FAILURE; 1.529 +} 1.530 + 1.531 +NS_IMETHODIMP 1.532 +nsIdleService::ResetIdleTimeOut(uint32_t idleDeltaInMS) 1.533 +{ 1.534 + PR_LOG(sLog, PR_LOG_DEBUG, 1.535 + ("idleService: Reset idle timeout (last interaction %u msec)", 1.536 + idleDeltaInMS)); 1.537 + 1.538 + // Store the time 1.539 + mLastUserInteraction = TimeStamp::Now() - 1.540 + TimeDuration::FromMilliseconds(idleDeltaInMS); 1.541 + 1.542 + // If no one is idle, then we are done, any existing timers can keep running. 1.543 + if (mIdleObserverCount == 0) { 1.544 + PR_LOG(sLog, PR_LOG_DEBUG, 1.545 + ("idleService: Reset idle timeout: no idle observers")); 1.546 + return NS_OK; 1.547 + } 1.548 + 1.549 + // Mark all idle services as non-idle, and calculate the next idle timeout. 1.550 + Telemetry::AutoTimer<Telemetry::IDLE_NOTIFY_BACK_MS> timer; 1.551 + nsCOMArray<nsIObserver> notifyList; 1.552 + mDeltaToNextIdleSwitchInS = UINT32_MAX; 1.553 + 1.554 + // Loop through all listeners, and find any that have detected idle. 1.555 + for (uint32_t i = 0; i < mArrayListeners.Length(); i++) { 1.556 + IdleListener& curListener = mArrayListeners.ElementAt(i); 1.557 + 1.558 + // If the listener was idle, then he shouldn't be any longer. 1.559 + if (curListener.isIdle) { 1.560 + notifyList.AppendObject(curListener.observer); 1.561 + curListener.isIdle = false; 1.562 + } 1.563 + 1.564 + // Check if the listener is the next one to timeout. 1.565 + mDeltaToNextIdleSwitchInS = std::min(mDeltaToNextIdleSwitchInS, 1.566 + curListener.reqIdleTime); 1.567 + } 1.568 + 1.569 + // When we are done, then we wont have anyone idle. 1.570 + mIdleObserverCount = 0; 1.571 + 1.572 + // Restart the idle timer, and do so before anyone can delay us. 1.573 + ReconfigureTimer(); 1.574 + 1.575 + int32_t numberOfPendingNotifications = notifyList.Count(); 1.576 + Telemetry::Accumulate(Telemetry::IDLE_NOTIFY_BACK_LISTENERS, 1.577 + numberOfPendingNotifications); 1.578 + 1.579 + // Bail if nothing to do. 1.580 + if (!numberOfPendingNotifications) { 1.581 + return NS_OK; 1.582 + } 1.583 + 1.584 + // Now send "active" events to all, if any should have timed out already, 1.585 + // then they will be reawaken by the timer that is already running. 1.586 + 1.587 + // We need a text string to send with any state change events. 1.588 + nsAutoString timeStr; 1.589 + 1.590 + timeStr.AppendInt((int32_t)(idleDeltaInMS / PR_MSEC_PER_SEC)); 1.591 + 1.592 + // Send the "non-idle" events. 1.593 + while (numberOfPendingNotifications--) { 1.594 + PR_LOG(sLog, PR_LOG_DEBUG, 1.595 + ("idleService: Reset idle timeout: tell observer %p user is back", 1.596 + notifyList[numberOfPendingNotifications])); 1.597 +#ifdef MOZ_WIDGET_ANDROID 1.598 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.599 + "Reset idle timeout: tell observer %p user is back", 1.600 + notifyList[numberOfPendingNotifications]); 1.601 +#endif 1.602 + notifyList[numberOfPendingNotifications]->Observe(this, 1.603 + OBSERVER_TOPIC_ACTIVE, 1.604 + timeStr.get()); 1.605 + } 1.606 + return NS_OK; 1.607 +} 1.608 + 1.609 +NS_IMETHODIMP 1.610 +nsIdleService::GetIdleTime(uint32_t* idleTime) 1.611 +{ 1.612 + // Check sanity of in parameter. 1.613 + if (!idleTime) { 1.614 + return NS_ERROR_NULL_POINTER; 1.615 + } 1.616 + 1.617 + // Polled idle time in ms. 1.618 + uint32_t polledIdleTimeMS; 1.619 + 1.620 + bool polledIdleTimeIsValid = PollIdleTime(&polledIdleTimeMS); 1.621 + 1.622 + PR_LOG(sLog, PR_LOG_DEBUG, 1.623 + ("idleService: Get idle time: polled %u msec, valid = %d", 1.624 + polledIdleTimeMS, polledIdleTimeIsValid)); 1.625 + 1.626 + // timeSinceReset is in milliseconds. 1.627 + TimeDuration timeSinceReset = TimeStamp::Now() - mLastUserInteraction; 1.628 + uint32_t timeSinceResetInMS = timeSinceReset.ToMilliseconds(); 1.629 + 1.630 + PR_LOG(sLog, PR_LOG_DEBUG, 1.631 + ("idleService: Get idle time: time since reset %u msec", 1.632 + timeSinceResetInMS)); 1.633 +#ifdef MOZ_WIDGET_ANDROID 1.634 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.635 + "Get idle time: time since reset %u msec", 1.636 + timeSinceResetInMS); 1.637 +#endif 1.638 + 1.639 + // If we did't get pulled data, return the time since last idle reset. 1.640 + if (!polledIdleTimeIsValid) { 1.641 + // We need to convert to ms before returning the time. 1.642 + *idleTime = timeSinceResetInMS; 1.643 + return NS_OK; 1.644 + } 1.645 + 1.646 + // Otherwise return the shortest time detected (in ms). 1.647 + *idleTime = std::min(timeSinceResetInMS, polledIdleTimeMS); 1.648 + 1.649 + return NS_OK; 1.650 +} 1.651 + 1.652 + 1.653 +bool 1.654 +nsIdleService::PollIdleTime(uint32_t* /*aIdleTime*/) 1.655 +{ 1.656 + // Default behavior is not to have the ability to poll an idle time. 1.657 + return false; 1.658 +} 1.659 + 1.660 +bool 1.661 +nsIdleService::UsePollMode() 1.662 +{ 1.663 + uint32_t dummy; 1.664 + return PollIdleTime(&dummy); 1.665 +} 1.666 + 1.667 +void 1.668 +nsIdleService::StaticIdleTimerCallback(nsITimer* aTimer, void* aClosure) 1.669 +{ 1.670 + static_cast<nsIdleService*>(aClosure)->IdleTimerCallback(); 1.671 +} 1.672 + 1.673 +void 1.674 +nsIdleService::IdleTimerCallback(void) 1.675 +{ 1.676 + // Remember that we no longer have a timer running. 1.677 + mCurrentlySetToTimeoutAt = TimeStamp(); 1.678 + 1.679 + // Find the last detected idle time. 1.680 + uint32_t lastIdleTimeInMS = static_cast<uint32_t>((TimeStamp::Now() - 1.681 + mLastUserInteraction).ToMilliseconds()); 1.682 + // Get the current idle time. 1.683 + uint32_t currentIdleTimeInMS; 1.684 + 1.685 + if (NS_FAILED(GetIdleTime(¤tIdleTimeInMS))) { 1.686 + PR_LOG(sLog, PR_LOG_ALWAYS, 1.687 + ("idleService: Idle timer callback: failed to get idle time")); 1.688 +#ifdef MOZ_WIDGET_ANDROID 1.689 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.690 + "Idle timer callback: failed to get idle time"); 1.691 +#endif 1.692 + return; 1.693 + } 1.694 + 1.695 + PR_LOG(sLog, PR_LOG_DEBUG, 1.696 + ("idleService: Idle timer callback: current idle time %u msec", 1.697 + currentIdleTimeInMS)); 1.698 +#ifdef MOZ_WIDGET_ANDROID 1.699 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.700 + "Idle timer callback: current idle time %u msec", 1.701 + currentIdleTimeInMS); 1.702 +#endif 1.703 + 1.704 + // Check if we have had some user interaction we didn't handle previously 1.705 + // we do the calculation in ms to lessen the chance for rounding errors to 1.706 + // trigger wrong results. 1.707 + if (lastIdleTimeInMS > currentIdleTimeInMS) 1.708 + { 1.709 + // We had user activity, so handle that part first (to ensure the listeners 1.710 + // don't risk getting an non-idle after they get a new idle indication. 1.711 + ResetIdleTimeOut(currentIdleTimeInMS); 1.712 + 1.713 + // NOTE: We can't bail here, as we might have something already timed out. 1.714 + } 1.715 + 1.716 + // Find the idle time in S. 1.717 + uint32_t currentIdleTimeInS = currentIdleTimeInMS / PR_MSEC_PER_SEC; 1.718 + 1.719 + // Restart timer and bail if no-one are expected to be in idle 1.720 + if (mDeltaToNextIdleSwitchInS > currentIdleTimeInS) { 1.721 + // If we didn't expect anyone to be idle, then just re-start the timer. 1.722 + ReconfigureTimer(); 1.723 + return; 1.724 + } 1.725 + 1.726 + // Tell expired listeners they are expired,and find the next timeout 1.727 + Telemetry::AutoTimer<Telemetry::IDLE_NOTIFY_IDLE_MS> timer; 1.728 + 1.729 + // We need to initialise the time to the next idle switch. 1.730 + mDeltaToNextIdleSwitchInS = UINT32_MAX; 1.731 + 1.732 + // Create list of observers that should be notified. 1.733 + nsCOMArray<nsIObserver> notifyList; 1.734 + 1.735 + for (uint32_t i = 0; i < mArrayListeners.Length(); i++) { 1.736 + IdleListener& curListener = mArrayListeners.ElementAt(i); 1.737 + 1.738 + // We are only interested in items, that are not in the idle state. 1.739 + if (!curListener.isIdle) { 1.740 + // If they have an idle time smaller than the actual idle time. 1.741 + if (curListener.reqIdleTime <= currentIdleTimeInS) { 1.742 + // Then add the listener to the list of listeners that should be 1.743 + // notified. 1.744 + notifyList.AppendObject(curListener.observer); 1.745 + // This listener is now idle. 1.746 + curListener.isIdle = true; 1.747 + // Remember we have someone idle. 1.748 + mIdleObserverCount++; 1.749 + } else { 1.750 + // Listeners that are not timed out yet are candidates for timing out. 1.751 + mDeltaToNextIdleSwitchInS = std::min(mDeltaToNextIdleSwitchInS, 1.752 + curListener.reqIdleTime); 1.753 + } 1.754 + } 1.755 + } 1.756 + 1.757 + // Restart the timer before any notifications that could slow us down are 1.758 + // done. 1.759 + ReconfigureTimer(); 1.760 + 1.761 + int32_t numberOfPendingNotifications = notifyList.Count(); 1.762 + Telemetry::Accumulate(Telemetry::IDLE_NOTIFY_IDLE_LISTENERS, 1.763 + numberOfPendingNotifications); 1.764 + 1.765 + // Bail if nothing to do. 1.766 + if (!numberOfPendingNotifications) { 1.767 + PR_LOG(sLog, PR_LOG_DEBUG, 1.768 + ("idleService: **** Idle timer callback: no observers to message.")); 1.769 + return; 1.770 + } 1.771 + 1.772 + // We need a text string to send with any state change events. 1.773 + nsAutoString timeStr; 1.774 + timeStr.AppendInt(currentIdleTimeInS); 1.775 + 1.776 + // Notify all listeners that just timed out. 1.777 + while (numberOfPendingNotifications--) { 1.778 + PR_LOG(sLog, PR_LOG_DEBUG, 1.779 + ("idleService: **** Idle timer callback: tell observer %p user is idle", 1.780 + notifyList[numberOfPendingNotifications])); 1.781 +#ifdef MOZ_WIDGET_ANDROID 1.782 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.783 + "Idle timer callback: tell observer %p user is idle", 1.784 + notifyList[numberOfPendingNotifications]); 1.785 +#endif 1.786 + notifyList[numberOfPendingNotifications]->Observe(this, 1.787 + OBSERVER_TOPIC_IDLE, 1.788 + timeStr.get()); 1.789 + } 1.790 +} 1.791 + 1.792 +void 1.793 +nsIdleService::SetTimerExpiryIfBefore(TimeStamp aNextTimeout) 1.794 +{ 1.795 +#if defined(PR_LOGGING) || defined(MOZ_WIDGET_ANDROID) 1.796 + TimeDuration nextTimeoutDuration = aNextTimeout - TimeStamp::Now(); 1.797 +#endif 1.798 + 1.799 + PR_LOG(sLog, PR_LOG_DEBUG, 1.800 + ("idleService: SetTimerExpiryIfBefore: next timeout %0.f msec from now", 1.801 + nextTimeoutDuration.ToMilliseconds())); 1.802 + 1.803 +#ifdef MOZ_WIDGET_ANDROID 1.804 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.805 + "SetTimerExpiryIfBefore: next timeout %0.f msec from now", 1.806 + nextTimeoutDuration.ToMilliseconds()); 1.807 +#endif 1.808 + 1.809 + // Bail if we don't have a timer service. 1.810 + if (!mTimer) { 1.811 + return; 1.812 + } 1.813 + 1.814 + // If the new timeout is before the old one or we don't have a timer running, 1.815 + // then restart the timer. 1.816 + if (mCurrentlySetToTimeoutAt.IsNull() || 1.817 + mCurrentlySetToTimeoutAt > aNextTimeout) { 1.818 + 1.819 + mCurrentlySetToTimeoutAt = aNextTimeout; 1.820 + 1.821 + // Stop the current timer (it's ok to try'n stop it, even it isn't running). 1.822 + mTimer->Cancel(); 1.823 + 1.824 + // Check that the timeout is actually in the future, otherwise make it so. 1.825 + TimeStamp currentTime = TimeStamp::Now(); 1.826 + if (currentTime > mCurrentlySetToTimeoutAt) { 1.827 + mCurrentlySetToTimeoutAt = currentTime; 1.828 + } 1.829 + 1.830 + // Add 10 ms to ensure we don't undershoot, and never get a "0" timer. 1.831 + mCurrentlySetToTimeoutAt += TimeDuration::FromMilliseconds(10); 1.832 + 1.833 + TimeDuration deltaTime = mCurrentlySetToTimeoutAt - currentTime; 1.834 + PR_LOG(sLog, PR_LOG_DEBUG, 1.835 + ("idleService: IdleService reset timer expiry to %0.f msec from now", 1.836 + deltaTime.ToMilliseconds())); 1.837 +#ifdef MOZ_WIDGET_ANDROID 1.838 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.839 + "reset timer expiry to %0.f msec from now", 1.840 + deltaTime.ToMilliseconds()); 1.841 +#endif 1.842 + 1.843 + // Start the timer 1.844 + mTimer->InitWithFuncCallback(StaticIdleTimerCallback, 1.845 + this, 1.846 + deltaTime.ToMilliseconds(), 1.847 + nsITimer::TYPE_ONE_SHOT); 1.848 + 1.849 + } 1.850 +} 1.851 + 1.852 +void 1.853 +nsIdleService::ReconfigureTimer(void) 1.854 +{ 1.855 + // Check if either someone is idle, or someone will become idle. 1.856 + if ((mIdleObserverCount == 0) && UINT32_MAX == mDeltaToNextIdleSwitchInS) { 1.857 + // If not, just let any existing timers run to completion 1.858 + // And bail out. 1.859 + PR_LOG(sLog, PR_LOG_DEBUG, 1.860 + ("idleService: ReconfigureTimer: no idle or waiting observers")); 1.861 +#ifdef MOZ_WIDGET_ANDROID 1.862 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.863 + "ReconfigureTimer: no idle or waiting observers"); 1.864 +#endif 1.865 + return; 1.866 + } 1.867 + 1.868 + // Find the next timeout value, assuming we are not polling. 1.869 + 1.870 + // We need to store the current time, so we don't get artifacts from the time 1.871 + // ticking while we are processing. 1.872 + TimeStamp curTime = TimeStamp::Now(); 1.873 + 1.874 + TimeStamp nextTimeoutAt = mLastUserInteraction + 1.875 + TimeDuration::FromSeconds(mDeltaToNextIdleSwitchInS); 1.876 + 1.877 +#if defined(PR_LOGGING) || defined(MOZ_WIDGET_ANDROID) 1.878 + TimeDuration nextTimeoutDuration = nextTimeoutAt - curTime; 1.879 +#endif 1.880 + 1.881 + PR_LOG(sLog, PR_LOG_DEBUG, 1.882 + ("idleService: next timeout %0.f msec from now", 1.883 + nextTimeoutDuration.ToMilliseconds())); 1.884 + 1.885 +#ifdef MOZ_WIDGET_ANDROID 1.886 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.887 + "next timeout %0.f msec from now", 1.888 + nextTimeoutDuration.ToMilliseconds()); 1.889 +#endif 1.890 + 1.891 + // Check if we should correct the timeout time because we should poll before. 1.892 + if ((mIdleObserverCount > 0) && UsePollMode()) { 1.893 + TimeStamp pollTimeout = 1.894 + curTime + TimeDuration::FromMilliseconds(MIN_IDLE_POLL_INTERVAL_MSEC); 1.895 + 1.896 + if (nextTimeoutAt > pollTimeout) { 1.897 + PR_LOG(sLog, PR_LOG_DEBUG, 1.898 + ("idleService: idle observers, reducing timeout to %lu msec from now", 1.899 + MIN_IDLE_POLL_INTERVAL_MSEC)); 1.900 +#ifdef MOZ_WIDGET_ANDROID 1.901 + __android_log_print(ANDROID_LOG_INFO, "IdleService", 1.902 + "idle observers, reducing timeout to %lu msec from now", 1.903 + MIN_IDLE_POLL_INTERVAL_MSEC); 1.904 +#endif 1.905 + nextTimeoutAt = pollTimeout; 1.906 + } 1.907 + } 1.908 + 1.909 + SetTimerExpiryIfBefore(nextTimeoutAt); 1.910 +}