LCOV - code coverage report
Current view: top level - widget - nsIdleService.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 88 263 33.5 %
Date: 2017-07-14 16:53:18 Functions: 17 30 56.7 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13