widget/xpwidgets/nsIdleService.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

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

mercurial