widget/xpwidgets/nsIdleService.cpp

changeset 0
6474c204b198
     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(&currentIdleTimeInMS))) {
   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 +}

mercurial