Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/widget/nsIdleService.cpp
Line
Count
Source (jump to first uncovered line)
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
0
#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
0
#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
0
#define DAILY_SHORTENED_IDLE_SERVICE_SEC 60
42
43
// Pref for last time (seconds since epoch) daily notification was sent.
44
0
#define PREF_LAST_DAILY "idle.lastDailyNotification"
45
46
// Number of seconds in a day.
47
0
#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
  bool Equals(IdleListener a, IdleListener b) const
59
0
  {
60
0
    return (a.observer == b.observer) &&
61
0
           (a.reqIdleTime == b.reqIdleTime);
62
0
  }
63
};
64
65
////////////////////////////////////////////////////////////////////////////////
66
//// nsIdleServiceDaily
67
68
NS_IMPL_ISUPPORTS(nsIdleServiceDaily, nsIObserver, nsISupportsWeakReference)
69
70
NS_IMETHODIMP
71
nsIdleServiceDaily::Observe(nsISupports *,
72
                            const char *aTopic,
73
                            const char16_t *)
74
0
{
75
0
  MOZ_LOG(sLog, LogLevel::Debug,
76
0
         ("nsIdleServiceDaily: Observe '%s' (%d)",
77
0
          aTopic, mShutdownInProgress));
78
0
79
0
  if (strcmp(aTopic, "profile-after-change") == 0) {
80
0
    // We are back. Start sending notifications again.
81
0
    mShutdownInProgress = false;
82
0
    return NS_OK;
83
0
  }
84
0
85
0
  if (strcmp(aTopic, "xpcom-will-shutdown") == 0 ||
86
0
      strcmp(aTopic, "profile-change-teardown") == 0) {
87
0
    mShutdownInProgress = true;
88
0
  }
89
0
90
0
  if (mShutdownInProgress || strcmp(aTopic, OBSERVER_TOPIC_ACTIVE) == 0) {
91
0
    return NS_OK;
92
0
  }
93
0
  MOZ_ASSERT(strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0);
94
0
95
0
  MOZ_LOG(sLog, LogLevel::Debug,
96
0
         ("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
0
  // Send the idle-daily observer event
103
0
  nsCOMPtr<nsIObserverService> observerService =
104
0
    mozilla::services::GetObserverService();
105
0
  NS_ENSURE_STATE(observerService);
106
0
  (void)observerService->NotifyObservers(nullptr,
107
0
                                         OBSERVER_TOPIC_IDLE_DAILY,
108
0
                                         nullptr);
109
0
110
0
  // 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
0
  }
116
0
117
0
  // Stop observing idle for today.
118
0
  (void)mIdleService->RemoveIdleObserver(this, mIdleDailyTriggerWait);
119
0
120
0
  // 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
0
124
0
  // Force that to be stored so we don't retrigger twice a day under
125
0
  // any circumstances.
126
0
  nsIPrefService* prefs = Preferences::GetService();
127
0
  if (prefs) {
128
0
    prefs->SavePrefFile(nullptr);
129
0
  }
130
0
131
0
  MOZ_LOG(sLog, LogLevel::Debug,
132
0
         ("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
0
  // Note the moment we expect to get the next timer callback
139
0
  mExpectedTriggerTime  = PR_Now() + ((PRTime)SECONDS_PER_DAY *
140
0
                                      (PRTime)PR_USEC_PER_SEC);
141
0
142
0
  MOZ_LOG(sLog, LogLevel::Debug,
143
0
         ("nsIdleServiceDaily: Restarting daily timer"));
144
0
145
0
  // Start timer for the next check in one day.
146
0
  (void)mTimer->InitWithNamedFuncCallback(DailyCallback,
147
0
                                          this,
148
0
                                          SECONDS_PER_DAY * PR_MSEC_PER_SEC,
149
0
                                          nsITimer::TYPE_ONE_SHOT,
150
0
                                          "nsIdleServiceDaily::Observe");
151
0
152
0
  return NS_OK;
153
0
}
154
155
nsIdleServiceDaily::nsIdleServiceDaily(nsIIdleService* aIdleService)
156
  : mIdleService(aIdleService)
157
  , mTimer(NS_NewTimer())
158
  , mCategoryObservers(OBSERVER_TOPIC_IDLE_DAILY)
159
  , mShutdownInProgress(false)
160
  , mExpectedTriggerTime(0)
161
  , mIdleDailyTriggerWait(DAILY_SIGNIFICANT_IDLE_SERVICE_SEC)
162
0
{
163
0
}
164
165
void
166
nsIdleServiceDaily::Init()
167
0
{
168
0
  // First check the time of the last idle-daily event notification. If it
169
0
  // has been 24 hours or higher, or if we have never sent an idle-daily,
170
0
  // get ready to send an idle-daily event. Otherwise set a timer targeted
171
0
  // at 24 hours past the last idle-daily we sent.
172
0
173
0
  int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
174
0
  int32_t lastDaily = Preferences::GetInt(PREF_LAST_DAILY, 0);
175
0
  if (lastDaily < 0 || lastDaily > nowSec) {
176
0
    // The time is bogus, use default.
177
0
    lastDaily = 0;
178
0
  }
179
0
  int32_t secondsSinceLastDaily = nowSec - lastDaily;
180
0
181
0
  MOZ_LOG(sLog, LogLevel::Debug,
182
0
         ("nsIdleServiceDaily: Init: seconds since last daily: %d",
183
0
          secondsSinceLastDaily));
184
0
185
0
  // If it has been twenty four hours or more or if we have never sent an
186
0
  // idle-daily event get ready to send it during the next idle period.
187
0
  if (secondsSinceLastDaily > SECONDS_PER_DAY) {
188
0
    // Check for a "long wait", e.g. 48-hours or more.
189
0
    bool hasBeenLongWait = (lastDaily &&
190
0
                            (secondsSinceLastDaily > (SECONDS_PER_DAY * 2)));
191
0
192
0
    MOZ_LOG(sLog, LogLevel::Debug,
193
0
           ("nsIdleServiceDaily: has been long wait? %d",
194
0
            hasBeenLongWait));
195
0
196
0
    // StageIdleDaily sets up a wait for the user to become idle and then
197
0
    // sends the idle-daily event.
198
0
    StageIdleDaily(hasBeenLongWait);
199
0
  } else {
200
0
    MOZ_LOG(sLog, LogLevel::Debug,
201
0
           ("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
0
    // According to our last idle-daily pref, the last idle-daily was fired
208
0
    // 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
0
212
0
    MOZ_LOG(sLog, LogLevel::Debug,
213
0
           ("nsIdleServiceDaily: Seconds till next timeout: %d",
214
0
            (SECONDS_PER_DAY - secondsSinceLastDaily)));
215
0
216
0
    // Mark the time at which we expect this to fire. On systems with faulty
217
0
    // timers, we need to be able to cross check that the timer fired at the
218
0
    // expected time.
219
0
    mExpectedTriggerTime  = PR_Now() +
220
0
      (milliSecLeftUntilDaily * PR_USEC_PER_MSEC);
221
0
222
0
    (void)mTimer->InitWithNamedFuncCallback(DailyCallback,
223
0
                                            this,
224
0
                                            milliSecLeftUntilDaily,
225
0
                                            nsITimer::TYPE_ONE_SHOT,
226
0
                                            "nsIdleServiceDaily::Init");
227
0
  }
228
0
229
0
  // Register for when we should terminate/pause
230
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
231
0
  if (obs) {
232
0
    MOZ_LOG(sLog, LogLevel::Debug,
233
0
           ("nsIdleServiceDaily: Registering for system event observers."));
234
0
    obs->AddObserver(this, "xpcom-will-shutdown", true);
235
0
    obs->AddObserver(this, "profile-change-teardown", true);
236
0
    obs->AddObserver(this, "profile-after-change", true);
237
0
  }
238
0
}
239
240
nsIdleServiceDaily::~nsIdleServiceDaily()
241
0
{
242
0
  if (mTimer) {
243
0
    mTimer->Cancel();
244
0
    mTimer = nullptr;
245
0
  }
246
0
}
247
248
249
void
250
nsIdleServiceDaily::StageIdleDaily(bool aHasBeenLongWait)
251
0
{
252
0
  NS_ASSERTION(mIdleService, "No idle service available?");
253
0
  MOZ_LOG(sLog, LogLevel::Debug,
254
0
          ("nsIdleServiceDaily: Registering Idle observer callback "
255
0
           "(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
  mIdleDailyTriggerWait = (aHasBeenLongWait ?
261
0
                             DAILY_SHORTENED_IDLE_SERVICE_SEC :
262
0
                             DAILY_SIGNIFICANT_IDLE_SERVICE_SEC);
263
0
  (void)mIdleService->AddIdleObserver(this, mIdleDailyTriggerWait);
264
0
}
265
266
// static
267
void
268
nsIdleServiceDaily::DailyCallback(nsITimer* aTimer, void* aClosure)
269
0
{
270
0
  MOZ_LOG(sLog, LogLevel::Debug,
271
0
          ("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
0
279
0
  // Check to be sure the timer didn't fire early. This currently only
280
0
  // happens on android.
281
0
  PRTime now = PR_Now();
282
0
  if (self->mExpectedTriggerTime && now < self->mExpectedTriggerTime) {
283
0
    // Timer returned early, reschedule to the appropriate time.
284
0
    PRTime delayTime = self->mExpectedTriggerTime - now;
285
0
286
0
    // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
287
0
    delayTime += 10 * PR_USEC_PER_MSEC;
288
0
289
0
    MOZ_LOG(sLog, LogLevel::Debug, ("nsIdleServiceDaily: DailyCallback resetting timer to %" PRId64 " msec",
290
0
                        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
0
      DailyCallback,
299
0
      self,
300
0
      delayTime / PR_USEC_PER_MSEC,
301
0
      nsITimer::TYPE_ONE_SHOT,
302
0
      "nsIdleServiceDaily::DailyCallback");
303
0
    return;
304
0
  }
305
0
306
0
  // Register for a short term wait for idle event. When this fires we fire
307
0
  // our idle-daily event.
308
0
  self->StageIdleDaily(false);
309
0
}
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
nsIdleService::GetInstance()
391
0
{
392
0
  RefPtr<nsIdleService> instance(gIdleService);
393
0
  return instance.forget();
394
0
}
395
396
nsIdleService::nsIdleService() : mCurrentlySetToTimeoutAt(TimeStamp()),
397
                                 mIdleObserverCount(0),
398
                                 mDeltaToNextIdleSwitchInS(UINT32_MAX),
399
                                 mLastUserInteraction(TimeStamp::Now())
400
0
{
401
0
  MOZ_ASSERT(!gIdleService);
402
0
  gIdleService = this;
403
0
  if (XRE_IsParentProcess()) {
404
0
    mDailyIdle = new nsIdleServiceDaily(this);
405
0
    mDailyIdle->Init();
406
0
  }
407
0
}
408
409
nsIdleService::~nsIdleService()
410
0
{
411
0
  if(mTimer) {
412
0
    mTimer->Cancel();
413
0
  }
414
0
415
0
416
0
  MOZ_ASSERT(gIdleService == this);
417
0
  gIdleService = nullptr;
418
0
}
419
420
NS_IMPL_ISUPPORTS(nsIdleService, nsIIdleService, nsIIdleServiceInternal)
421
422
NS_IMETHODIMP
423
nsIdleService::AddIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS)
424
0
{
425
0
  NS_ENSURE_ARG_POINTER(aObserver);
426
0
  // We don't accept idle time at 0, and we can't handle idle time that are too
427
0
  // high either - no more than ~136 years.
428
0
  NS_ENSURE_ARG_RANGE(aIdleTimeInS, 1, (UINT32_MAX / 10) - 1);
429
0
430
0
  if (XRE_IsContentProcess()) {
431
0
    dom::ContentChild* cpc = dom::ContentChild::GetSingleton();
432
0
    cpc->AddIdleObserver(aObserver, aIdleTimeInS);
433
0
    return NS_OK;
434
0
  }
435
0
436
0
  MOZ_LOG(sLog, LogLevel::Debug,
437
0
       ("idleService: Register idle observer %p for %d seconds",
438
0
        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
0
  // Put the time + observer in a struct we can keep:
446
0
  IdleListener listener(aObserver, aIdleTimeInS);
447
0
448
0
  if (!mArrayListeners.AppendElement(listener)) {
449
0
    return NS_ERROR_OUT_OF_MEMORY;
450
0
  }
451
0
452
0
  // Create our timer callback if it's not there already.
453
0
  if (!mTimer) {
454
0
    mTimer = NS_NewTimer();
455
0
    NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY);
456
0
  }
457
0
458
0
  // Check if the newly added observer has a smaller wait time than what we
459
0
  // are waiting for now.
460
0
  if (mDeltaToNextIdleSwitchInS > aIdleTimeInS) {
461
0
    // If it is, then this is the next to move to idle (at this point we
462
0
    // don't care if it should have switched already).
463
0
    MOZ_LOG(sLog, LogLevel::Debug,
464
0
          ("idleService: Register: adjusting next switch from %d to %d seconds",
465
0
           mDeltaToNextIdleSwitchInS, aIdleTimeInS));
466
#ifdef MOZ_WIDGET_ANDROID
467
    __android_log_print(LOG_LEVEL, LOG_TAG,
468
                        "Register: adjusting next switch from %d to %d seconds",
469
                        mDeltaToNextIdleSwitchInS, aIdleTimeInS);
470
#endif
471
472
0
    mDeltaToNextIdleSwitchInS = aIdleTimeInS;
473
0
  }
474
0
475
0
  // Ensure timer is running.
476
0
  ReconfigureTimer();
477
0
478
0
  return NS_OK;
479
0
}
480
481
NS_IMETHODIMP
482
nsIdleService::RemoveIdleObserver(nsIObserver* aObserver, uint32_t aTimeInS)
483
0
{
484
0
485
0
  NS_ENSURE_ARG_POINTER(aObserver);
486
0
  NS_ENSURE_ARG(aTimeInS);
487
0
488
0
  if (XRE_IsContentProcess()) {
489
0
    dom::ContentChild* cpc = dom::ContentChild::GetSingleton();
490
0
    cpc->RemoveIdleObserver(aObserver, aTimeInS);
491
0
    return NS_OK;
492
0
  }
493
0
494
0
  IdleListener listener(aObserver, aTimeInS);
495
0
496
0
  // Find the entry and remove it, if it was the last entry, we just let the
497
0
  // existing timer run to completion (there might be a new registration in a
498
0
  // little while.
499
0
  IdleListenerComparator c;
500
0
  nsTArray<IdleListener>::index_type listenerIndex = mArrayListeners.IndexOf(listener, 0, c);
501
0
  if (listenerIndex != mArrayListeners.NoIndex) {
502
0
    if (mArrayListeners.ElementAt(listenerIndex).isIdle)
503
0
      mIdleObserverCount--;
504
0
    mArrayListeners.RemoveElementAt(listenerIndex);
505
0
    MOZ_LOG(sLog, LogLevel::Debug,
506
0
           ("idleService: Remove observer %p (%d seconds), %d remain idle",
507
0
            aObserver, aTimeInS, mIdleObserverCount));
508
#ifdef MOZ_WIDGET_ANDROID
509
    __android_log_print(LOG_LEVEL, LOG_TAG,
510
                        "Remove observer %p (%d seconds), %d remain idle",
511
                        aObserver, aTimeInS, mIdleObserverCount);
512
#endif
513
    return NS_OK;
514
0
  }
515
0
516
0
  // If we get here, we haven't removed anything:
517
0
  MOZ_LOG(sLog, LogLevel::Warning, 
518
0
         ("idleService: Failed to remove idle observer %p (%d seconds)",
519
0
          aObserver, aTimeInS));
520
#ifdef MOZ_WIDGET_ANDROID
521
  __android_log_print(LOG_LEVEL, LOG_TAG,
522
                      "Failed to remove idle observer %p (%d seconds)",
523
                      aObserver, aTimeInS);
524
#endif
525
  return NS_ERROR_FAILURE;
526
0
}
527
528
NS_IMETHODIMP
529
nsIdleService::ResetIdleTimeOut(uint32_t idleDeltaInMS)
530
0
{
531
0
  MOZ_LOG(sLog, LogLevel::Debug,
532
0
         ("idleService: Reset idle timeout (last interaction %u msec)",
533
0
          idleDeltaInMS));
534
0
535
0
  // Store the time
536
0
  mLastUserInteraction = TimeStamp::Now() -
537
0
                         TimeDuration::FromMilliseconds(idleDeltaInMS);
538
0
539
0
  // If no one is idle, then we are done, any existing timers can keep running.
540
0
  if (mIdleObserverCount == 0) {
541
0
    MOZ_LOG(sLog, LogLevel::Debug,
542
0
           ("idleService: Reset idle timeout: no idle observers"));
543
0
    return NS_OK;
544
0
  }
545
0
546
0
  // Mark all idle services as non-idle, and calculate the next idle timeout.
547
0
  nsCOMArray<nsIObserver> notifyList;
548
0
  mDeltaToNextIdleSwitchInS = UINT32_MAX;
549
0
550
0
  // Loop through all listeners, and find any that have detected idle.
551
0
  for (uint32_t i = 0; i < mArrayListeners.Length(); i++) {
552
0
    IdleListener& curListener = mArrayListeners.ElementAt(i);
553
0
554
0
    // If the listener was idle, then he shouldn't be any longer.
555
0
    if (curListener.isIdle) {
556
0
      notifyList.AppendObject(curListener.observer);
557
0
      curListener.isIdle = false;
558
0
    }
559
0
560
0
    // Check if the listener is the next one to timeout.
561
0
    mDeltaToNextIdleSwitchInS = std::min(mDeltaToNextIdleSwitchInS,
562
0
                                       curListener.reqIdleTime);
563
0
  }
564
0
565
0
  // When we are done, then we wont have anyone idle.
566
0
  mIdleObserverCount = 0;
567
0
568
0
  // Restart the idle timer, and do so before anyone can delay us.
569
0
  ReconfigureTimer();
570
0
571
0
  int32_t numberOfPendingNotifications = notifyList.Count();
572
0
573
0
  // Bail if nothing to do.
574
0
  if (!numberOfPendingNotifications) {
575
0
    return NS_OK;
576
0
  }
577
0
578
0
  // Now send "active" events to all, if any should have timed out already,
579
0
  // then they will be reawaken by the timer that is already running.
580
0
581
0
  // We need a text string to send with any state change events.
582
0
  nsAutoString timeStr;
583
0
584
0
  timeStr.AppendInt((int32_t)(idleDeltaInMS / PR_MSEC_PER_SEC));
585
0
586
0
  // Send the "non-idle" events.
587
0
  while (numberOfPendingNotifications--) {
588
0
    MOZ_LOG(sLog, LogLevel::Debug,
589
0
           ("idleService: Reset idle timeout: tell observer %p user is back",
590
0
            notifyList[numberOfPendingNotifications]));
591
#ifdef MOZ_WIDGET_ANDROID
592
    __android_log_print(LOG_LEVEL, LOG_TAG,
593
                        "Reset idle timeout: tell observer %p user is back",
594
                        notifyList[numberOfPendingNotifications]);
595
#endif
596
    notifyList[numberOfPendingNotifications]->Observe(this,
597
0
                                                      OBSERVER_TOPIC_ACTIVE,
598
0
                                                      timeStr.get());
599
0
  }
600
0
  return NS_OK;
601
0
}
602
603
NS_IMETHODIMP
604
nsIdleService::GetIdleTime(uint32_t* idleTime)
605
0
{
606
0
  // Check sanity of in parameter.
607
0
  if (!idleTime) {
608
0
    return NS_ERROR_NULL_POINTER;
609
0
  }
610
0
611
0
  // Polled idle time in ms.
612
0
  uint32_t polledIdleTimeMS;
613
0
614
0
  bool polledIdleTimeIsValid = PollIdleTime(&polledIdleTimeMS);
615
0
616
0
  MOZ_LOG(sLog, LogLevel::Debug,
617
0
         ("idleService: Get idle time: polled %u msec, valid = %d",
618
0
          polledIdleTimeMS, polledIdleTimeIsValid));
619
0
  
620
0
  // timeSinceReset is in milliseconds.
621
0
  TimeDuration timeSinceReset = TimeStamp::Now() - mLastUserInteraction;
622
0
  uint32_t timeSinceResetInMS = timeSinceReset.ToMilliseconds();
623
0
624
0
  MOZ_LOG(sLog, LogLevel::Debug,
625
0
         ("idleService: Get idle time: time since reset %u msec",
626
0
          timeSinceResetInMS));
627
#ifdef MOZ_WIDGET_ANDROID
628
  __android_log_print(LOG_LEVEL, LOG_TAG,
629
                      "Get idle time: time since reset %u msec",
630
                      timeSinceResetInMS);
631
#endif
632
633
0
  // If we did't get pulled data, return the time since last idle reset.
634
0
  if (!polledIdleTimeIsValid) {
635
0
    // We need to convert to ms before returning the time.
636
0
    *idleTime = timeSinceResetInMS;
637
0
    return NS_OK;
638
0
  }
639
0
640
0
  // Otherwise return the shortest time detected (in ms).
641
0
  *idleTime = std::min(timeSinceResetInMS, polledIdleTimeMS);
642
0
643
0
  return NS_OK;
644
0
}
645
646
647
bool
648
nsIdleService::PollIdleTime(uint32_t* /*aIdleTime*/)
649
0
{
650
0
  // Default behavior is not to have the ability to poll an idle time.
651
0
  return false;
652
0
}
653
654
bool
655
nsIdleService::UsePollMode()
656
0
{
657
0
  uint32_t dummy;
658
0
  return PollIdleTime(&dummy);
659
0
}
660
661
void
662
nsIdleService::StaticIdleTimerCallback(nsITimer* aTimer, void* aClosure)
663
0
{
664
0
  static_cast<nsIdleService*>(aClosure)->IdleTimerCallback();
665
0
}
666
667
void
668
nsIdleService::IdleTimerCallback(void)
669
0
{
670
0
  // Remember that we no longer have a timer running.
671
0
  mCurrentlySetToTimeoutAt = TimeStamp();
672
0
673
0
  // Find the last detected idle time.
674
0
  uint32_t lastIdleTimeInMS = static_cast<uint32_t>((TimeStamp::Now() -
675
0
                              mLastUserInteraction).ToMilliseconds());
676
0
  // Get the current idle time.
677
0
  uint32_t currentIdleTimeInMS;
678
0
679
0
  if (NS_FAILED(GetIdleTime(&currentIdleTimeInMS))) {
680
0
    MOZ_LOG(sLog, LogLevel::Info,
681
0
           ("idleService: Idle timer callback: failed to get idle time"));
682
#ifdef MOZ_WIDGET_ANDROID
683
    __android_log_print(LOG_LEVEL, LOG_TAG,
684
                        "Idle timer callback: failed to get idle time");
685
#endif
686
    return;
687
0
  }
688
0
689
0
  MOZ_LOG(sLog, LogLevel::Debug,
690
0
         ("idleService: Idle timer callback: current idle time %u msec",
691
0
          currentIdleTimeInMS));
692
#ifdef MOZ_WIDGET_ANDROID
693
  __android_log_print(LOG_LEVEL, LOG_TAG,
694
                      "Idle timer callback: current idle time %u msec",
695
                      currentIdleTimeInMS);
696
#endif
697
698
0
  // Check if we have had some user interaction we didn't handle previously
699
0
  // we do the calculation in ms to lessen the chance for rounding errors to
700
0
  // trigger wrong results.
701
0
  if (lastIdleTimeInMS > currentIdleTimeInMS)
702
0
  {
703
0
    // We had user activity, so handle that part first (to ensure the listeners
704
0
    // don't risk getting an non-idle after they get a new idle indication.
705
0
    ResetIdleTimeOut(currentIdleTimeInMS);
706
0
707
0
    // NOTE: We can't bail here, as we might have something already timed out.
708
0
  }
709
0
710
0
  // Find the idle time in S.
711
0
  uint32_t currentIdleTimeInS = currentIdleTimeInMS / PR_MSEC_PER_SEC;
712
0
713
0
  // Restart timer and bail if no-one are expected to be in idle
714
0
  if (mDeltaToNextIdleSwitchInS > currentIdleTimeInS) {
715
0
    // If we didn't expect anyone to be idle, then just re-start the timer.
716
0
    ReconfigureTimer();
717
0
    return;
718
0
  }
719
0
720
0
  // Tell expired listeners they are expired,and find the next timeout
721
0
  Telemetry::AutoTimer<Telemetry::IDLE_NOTIFY_IDLE_MS> timer;
722
0
723
0
  // We need to initialise the time to the next idle switch.
724
0
  mDeltaToNextIdleSwitchInS = UINT32_MAX;
725
0
726
0
  // Create list of observers that should be notified.
727
0
  nsCOMArray<nsIObserver> notifyList;
728
0
729
0
  for (uint32_t i = 0; i < mArrayListeners.Length(); i++) {
730
0
    IdleListener& curListener = mArrayListeners.ElementAt(i);
731
0
732
0
    // We are only interested in items, that are not in the idle state.
733
0
    if (!curListener.isIdle) {
734
0
      // If they have an idle time smaller than the actual idle time.
735
0
      if (curListener.reqIdleTime <= currentIdleTimeInS) {
736
0
        // Then add the listener to the list of listeners that should be
737
0
        // notified.
738
0
        notifyList.AppendObject(curListener.observer);
739
0
        // This listener is now idle.
740
0
        curListener.isIdle = true;
741
0
        // Remember we have someone idle.
742
0
        mIdleObserverCount++;
743
0
      } else {
744
0
        // Listeners that are not timed out yet are candidates for timing out.
745
0
        mDeltaToNextIdleSwitchInS = std::min(mDeltaToNextIdleSwitchInS,
746
0
                                           curListener.reqIdleTime);
747
0
      }
748
0
    }
749
0
  }
750
0
751
0
  // Restart the timer before any notifications that could slow us down are
752
0
  // done.
753
0
  ReconfigureTimer();
754
0
755
0
  int32_t numberOfPendingNotifications = notifyList.Count();
756
0
757
0
  // Bail if nothing to do.
758
0
  if (!numberOfPendingNotifications) {
759
0
    MOZ_LOG(sLog, LogLevel::Debug,
760
0
           ("idleService: **** Idle timer callback: no observers to message."));
761
0
    return;
762
0
  }
763
0
764
0
  // We need a text string to send with any state change events.
765
0
  nsAutoString timeStr;
766
0
  timeStr.AppendInt(currentIdleTimeInS);
767
0
768
0
  // Notify all listeners that just timed out.
769
0
  while (numberOfPendingNotifications--) {
770
0
    MOZ_LOG(sLog, LogLevel::Debug,
771
0
           ("idleService: **** Idle timer callback: tell observer %p user is idle",
772
0
            notifyList[numberOfPendingNotifications]));
773
#ifdef MOZ_WIDGET_ANDROID
774
  __android_log_print(LOG_LEVEL, LOG_TAG,
775
                      "Idle timer callback: tell observer %p user is idle",
776
                      notifyList[numberOfPendingNotifications]);
777
#endif
778
    notifyList[numberOfPendingNotifications]->Observe(this,
779
0
                                                      OBSERVER_TOPIC_IDLE,
780
0
                                                      timeStr.get());
781
0
  }
782
0
}
783
784
void
785
nsIdleService::SetTimerExpiryIfBefore(TimeStamp aNextTimeout)
786
0
{
787
0
  TimeDuration nextTimeoutDuration = aNextTimeout - TimeStamp::Now();
788
0
789
0
  MOZ_LOG(sLog, LogLevel::Debug,
790
0
         ("idleService: SetTimerExpiryIfBefore: next timeout %0.f msec from now",
791
0
          nextTimeoutDuration.ToMilliseconds()));
792
0
793
#ifdef MOZ_WIDGET_ANDROID
794
  __android_log_print(LOG_LEVEL, LOG_TAG,
795
                      "SetTimerExpiryIfBefore: next timeout %0.f msec from now",
796
                      nextTimeoutDuration.ToMilliseconds());
797
#endif
798
799
0
  // Bail if we don't have a timer service.
800
0
  if (!mTimer) {
801
0
    return;
802
0
  }
803
0
804
0
  // If the new timeout is before the old one or we don't have a timer running,
805
0
  // then restart the timer.
806
0
  if (mCurrentlySetToTimeoutAt.IsNull() ||
807
0
      mCurrentlySetToTimeoutAt > aNextTimeout) {
808
0
809
0
    mCurrentlySetToTimeoutAt = aNextTimeout;
810
0
811
0
    // Stop the current timer (it's ok to try'n stop it, even it isn't running).
812
0
    mTimer->Cancel();
813
0
814
0
    // Check that the timeout is actually in the future, otherwise make it so.
815
0
    TimeStamp currentTime = TimeStamp::Now();
816
0
    if (currentTime > mCurrentlySetToTimeoutAt) {
817
0
      mCurrentlySetToTimeoutAt = currentTime;
818
0
    }
819
0
820
0
    // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
821
0
    mCurrentlySetToTimeoutAt += TimeDuration::FromMilliseconds(10);
822
0
823
0
    TimeDuration deltaTime = mCurrentlySetToTimeoutAt - currentTime;
824
0
    MOZ_LOG(sLog, LogLevel::Debug,
825
0
           ("idleService: IdleService reset timer expiry to %0.f msec from now",
826
0
            deltaTime.ToMilliseconds()));
827
#ifdef MOZ_WIDGET_ANDROID
828
    __android_log_print(LOG_LEVEL, LOG_TAG,
829
                        "reset timer expiry to %0.f msec from now",
830
                        deltaTime.ToMilliseconds());
831
#endif
832
833
0
    // Start the timer
834
0
    mTimer->InitWithNamedFuncCallback(StaticIdleTimerCallback,
835
0
                                      this,
836
0
                                      deltaTime.ToMilliseconds(),
837
0
                                      nsITimer::TYPE_ONE_SHOT,
838
0
                                      "nsIdleService::SetTimerExpiryIfBefore");
839
0
  }
840
0
}
841
842
void
843
nsIdleService::ReconfigureTimer(void)
844
0
{
845
0
  // Check if either someone is idle, or someone will become idle.
846
0
  if ((mIdleObserverCount == 0) && UINT32_MAX == mDeltaToNextIdleSwitchInS) {
847
0
    // If not, just let any existing timers run to completion
848
0
    // And bail out.
849
0
    MOZ_LOG(sLog, LogLevel::Debug,
850
0
           ("idleService: ReconfigureTimer: no idle or waiting observers"));
851
#ifdef MOZ_WIDGET_ANDROID
852
  __android_log_print(LOG_LEVEL, LOG_TAG,
853
                      "ReconfigureTimer: no idle or waiting observers");
854
#endif
855
    return;
856
0
  }
857
0
858
0
  // Find the next timeout value, assuming we are not polling.
859
0
860
0
  // We need to store the current time, so we don't get artifacts from the time
861
0
  // ticking while we are processing.
862
0
  TimeStamp curTime = TimeStamp::Now();
863
0
864
0
  TimeStamp nextTimeoutAt = mLastUserInteraction +
865
0
                            TimeDuration::FromSeconds(mDeltaToNextIdleSwitchInS);
866
0
867
0
  TimeDuration nextTimeoutDuration = nextTimeoutAt - curTime;
868
0
869
0
  MOZ_LOG(sLog, LogLevel::Debug,
870
0
         ("idleService: next timeout %0.f msec from now",
871
0
          nextTimeoutDuration.ToMilliseconds()));
872
0
873
#ifdef MOZ_WIDGET_ANDROID
874
  __android_log_print(LOG_LEVEL, LOG_TAG,
875
                      "next timeout %0.f msec from now",
876
                      nextTimeoutDuration.ToMilliseconds());
877
#endif
878
879
0
  // Check if we should correct the timeout time because we should poll before.
880
0
  if ((mIdleObserverCount > 0) && UsePollMode()) {
881
0
    TimeStamp pollTimeout =
882
0
        curTime + TimeDuration::FromMilliseconds(MIN_IDLE_POLL_INTERVAL_MSEC);
883
0
884
0
    if (nextTimeoutAt > pollTimeout) {
885
0
      MOZ_LOG(sLog, LogLevel::Debug,
886
0
           ("idleService: idle observers, reducing timeout to %lu msec from now",
887
0
            MIN_IDLE_POLL_INTERVAL_MSEC));
888
#ifdef MOZ_WIDGET_ANDROID
889
      __android_log_print(LOG_LEVEL, LOG_TAG,
890
                          "idle observers, reducing timeout to %lu msec from now",
891
                          MIN_IDLE_POLL_INTERVAL_MSEC);
892
#endif
893
      nextTimeoutAt = pollTimeout;
894
0
    }
895
0
  }
896
0
897
0
  SetTimerExpiryIfBefore(nextTimeoutAt);
898
0
}