File: upgrade_detector.cc

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (539 lines) | stat: -rw-r--r-- 20,024 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/upgrade_detector/upgrade_detector.h"

#include <algorithm>
#include <vector>

#include "base/check.h"
#include "base/command_line.h"
#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/rand_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/clock.h"
#include "base/time/tick_clock.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_otr_state.h"
#include "chrome/browser/upgrade_detector/version_history_client.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/network_time/network_time_tracker.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "ui/base/idle/idle.h"

namespace {

// How long to wait between checks for whether the user has been idle.
constexpr int kIdleRepeatingTimerWait = 10;  // Minutes (seconds if testing).

// How much idle time (since last input even was detected) must have passed
// until we notify that a critical update has occurred.
constexpr int kIdleAmount = 2;  // Hours (or seconds, if testing).

// Maximum duration for a relaunch window.
constexpr base::TimeDelta kRelaunchWindowMaxDuration = base::Hours(24);

// The default amount of time between the detector's annoyance level change
// from UPGRADE_ANNOYANCE_GRACE to UPGRADE_ANNOYANCE_HIGH.
constexpr auto kDefaultGracePeriod = base::Hours(1);

bool UseTestingIntervals() {
  // If a command line parameter specifying how long the upgrade check should
  // be, we assume it is for testing and switch to using seconds instead of
  // hours.
  const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
  return !cmd_line.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec)
              .empty();
}

// Returns the start time of the relaunch window on the day of `time`.
base::Time ComputeRelaunchWindowStartForDay(
    const UpgradeDetector::RelaunchWindow& window,
    base::Time time) {
  base::Time::Exploded window_start_exploded;
  time.LocalExplode(&window_start_exploded);
  window_start_exploded.hour = window.hour;
  window_start_exploded.minute = window.minute;
  window_start_exploded.second = 0;
  window_start_exploded.millisecond = 0;
  base::Time window_start;
  if (!base::Time::FromLocalExploded(window_start_exploded, &window_start)) {
    // The start time doesn't exist on that day; likely due to a TZ change at
    // that precise moment (e.g., `window` is 02:00 for zones that observe DST
    // changes at 2am local time). Move forward/backward by one hour and try
    // again. As of this writing, Australia/Lord_Howe is the only zone that
    // doesn't change by one full hour on transitions. Meaning no disrespect to
    // its residents, it is simpler to be fuzzy for that one timezone than to be
    // absolutely accurate.
    if (window_start_exploded.hour < 23) {
      ++window_start_exploded.hour;
    } else {
      --window_start_exploded.hour;
    }

    // The adjusted time could still fail `Time::FromLocalExploded`. This
    // happens on ARM devices in ChromeOS. Once it happens, it could be sticky
    // and creates a crash loop. Return the unadjusted time in this case.
    // See http://crbug/1307913
    if (!base::Time::FromLocalExploded(window_start_exploded, &window_start)) {
      LOG(ERROR) << "FromLocalExploded failed with time=" << time
                 << ", now=" << base::Time::Now()
                 << ", year=" << window_start_exploded.year
                 << ", month=" << window_start_exploded.month
                 << ", day=" << window_start_exploded.day_of_month;

      base::debug::Alias(&window_start_exploded);

      // Dump once per chrome run.
      static bool dumped = false;
      if (!dumped) {
        dumped = base::debug::DumpWithoutCrashing();
      }

      return time;
    }
  }
  return window_start;
}

// Returns the value of the RelaunchFastIfOutdated policy, or a zero TimeDelta
// if the policy is unset.
base::TimeDelta GetRelaunchFastIfOutdated() {
  auto* local_state = g_browser_process->local_state();
  if (!local_state) {
    return base::TimeDelta();
  }
  return base::Days(
      local_state->GetInteger(prefs::kRelaunchFastIfOutdated));
}

}  // namespace

// static
void UpgradeDetector::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(prefs::kAttemptedToEnableAutoupdate, false);
}

void UpgradeDetector::Init() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Not all tests provide a PrefService for local_state().
  PrefService* local_state = g_browser_process->local_state();
  if (local_state) {
    pref_change_registrar_.Init(local_state);
    MonitorPrefChanges(prefs::kRelaunchNotificationPeriod);
    MonitorPrefChanges(prefs::kRelaunchWindow);
    MonitorPrefChanges(prefs::kRelaunchFastIfOutdated);
  }
}

void UpgradeDetector::Shutdown() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  weak_factory_.InvalidateWeakPtrs();
  pref_change_task_pending_ = false;
  idle_check_timer_.Stop();
  pref_change_registrar_.Reset();
}

void UpgradeDetector::OverrideRelaunchNotificationToRequired(bool overridden) {
  NotifyRelaunchOverriddenToRequired(overridden);
}

void UpgradeDetector::AddObserver(UpgradeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observer_list_.AddObserver(observer);
}

void UpgradeDetector::RemoveObserver(UpgradeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observer_list_.RemoveObserver(observer);
}

void UpgradeDetector::NotifyOutdatedInstall() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (observer_list_.empty())
    return;

  for (auto& observer : observer_list_)
    observer.OnOutdatedInstall();
}

void UpgradeDetector::NotifyOutdatedInstallNoAutoUpdate() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (observer_list_.empty())
    return;

  for (auto& observer : observer_list_)
    observer.OnOutdatedInstallNoAutoUpdate();
}

void UpgradeDetector::NotifyUpgradeForTesting() {
  NotifyUpgrade();
}

UpgradeDetector::UpgradeDetector(const base::Clock* clock,
                                 const base::TickClock* tick_clock)
    : clock_(clock),
      tick_clock_(tick_clock),
      upgrade_available_(UPGRADE_AVAILABLE_NONE),
      best_effort_experiment_updates_available_(false),
      critical_experiment_updates_available_(false),
      critical_update_acknowledged_(false),
      idle_check_timer_(tick_clock_),
      upgrade_notification_stage_(UPGRADE_ANNOYANCE_NONE),
      notify_upgrade_(false) {}

UpgradeDetector::~UpgradeDetector() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Ensure that Shutdown() was called.
  DCHECK(pref_change_registrar_.IsEmpty());
}

void UpgradeDetector::MonitorPrefChanges(const std::string& pref) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Not all tests provide a PrefService to be monitored.
  if (pref_change_registrar_.prefs()) {
    // base::Unretained is safe here because |this| outlives the registrar.
    pref_change_registrar_.Add(
        pref, base::BindRepeating(&UpgradeDetector::OnRelaunchPrefChanged,
                                  base::Unretained(this)));
  }
}

// static
base::TimeDelta UpgradeDetector::GetRelaunchNotificationPeriod() {
  // Not all tests provide a PrefService for local_state().
  auto* local_state = g_browser_process->local_state();
  if (!local_state)
    return base::TimeDelta();
  const auto* preference =
      local_state->FindPreference(prefs::kRelaunchNotificationPeriod);
  const int value = preference->GetValue()->GetInt();
  // Enforce the preference's documented minimum value.
  constexpr base::TimeDelta kMinValue = base::Hours(1);
  if (preference->IsDefaultValue() || value < kMinValue.InMilliseconds())
    return base::TimeDelta();
  return base::Milliseconds(value);
}

// static
bool UpgradeDetector::IsRelaunchNotificationPolicyEnabled() {
  // Not all tests provide a PrefService for local_state().
  auto* local_state = g_browser_process->local_state();
  if (!local_state)
    return false;

  // "Chrome menu only" means that the policy is disabled.
  constexpr int kChromeMenuOnly = 0;
  return local_state->GetInteger(prefs::kRelaunchNotification) !=
         kChromeMenuOnly;
}

// static
base::Time UpgradeDetector::AdjustDeadline(base::Time deadline,
                                           const RelaunchWindow& window) {
  DCHECK(window.IsValid());
  const base::TimeDelta duration = window.duration;

  // Window duration greater than equal to 24 hours means window covers the
  // whole day, so no need to adjust.
  if (duration >= kRelaunchWindowMaxDuration)
    return deadline;

  // Compute the window on the day of the deadline.
  const base::Time window_start =
      ComputeRelaunchWindowStartForDay(window, deadline);

  if (deadline >= window_start + duration) {
    // Push the deadline forward into a random interval in the next day's
    // window. The next day may be 25, 24 or 23 hours in the future. Take a stab
    // at 24 hours (the norm) and retry once if needed.
    base::Time next_window_start =
        ComputeRelaunchWindowStartForDay(window, deadline + base::Hours(24));
    if (next_window_start == window_start) {
      // The clocks must be set back, yielding a longer day. For example, 24
      // hours after a deadline of 00:30 could be at 23:30 on the same day due
      // to a DST change in the interim that sets clocks backward by one hour.
      // Try again. Use 26 rather than 25 in case some jurisdiction decides to
      // implement a shift of greater than 1 hour.
      next_window_start =
          ComputeRelaunchWindowStartForDay(window, deadline + base::Hours(26));
    } else if (next_window_start - window_start >= base::Hours(26)) {
      // The clocks must be set forward, yielding a shorter day, and we jumped
      // two days rather than one. For example, 24 hours after a deadline of
      // 23:30 could be at 00:30 two days later due to a DST change in the
      // interim that sets clocks forward by one hour". Try again.
      next_window_start =
          ComputeRelaunchWindowStartForDay(window, deadline + base::Hours(23));
    }
    return next_window_start + base::RandTimeDeltaUpTo(duration);
  }

  // Is the deadline within this day's window?
  if (deadline >= window_start)
    return deadline;

  // Compute the relaunch window starting on the day prior to the deadline for
  // cases where the relaunch window straddles midnight.
  base::Time prev_window_start =
      ComputeRelaunchWindowStartForDay(window, deadline - base::Hours(24));
  // The above cases do not apply here:
  // a) Previous day window jumped two days rather than one - This could arise
  // if, for example, 24 hours before the deadline of 00:30 is 23:30 two days
  // ago due to a DST change in the interim that set clocks forward by one hour.
  // But then 00:30 would actually mean 01:30 this day which would mean 00:30 on
  // the previous day. b) Previous day window on the same day - This could arise
  // if, for example, 24 hours before the deadline of 23:30 is 00:30 on the same
  // day due to clocks set back at by one hour. This is already covered in the
  // above condition `deadline >= window_start`.
  if (deadline < prev_window_start + duration)
    return deadline;

  // The deadline is after previous day's window. Push the deadline forward into
  // a random interval in the day's window.
  return window_start + base::RandTimeDeltaUpTo(duration);
}

// static
std::optional<UpgradeDetector::RelaunchWindow>
UpgradeDetector::GetRelaunchWindowPolicyValue() {
  // Not all tests provide a PrefService for local_state().
  auto* local_state = g_browser_process->local_state();
  if (!local_state)
    return std::nullopt;

  const auto* preference = local_state->FindPreference(prefs::kRelaunchWindow);
  DCHECK(preference);
  if (preference->IsDefaultValue())
    return std::nullopt;

  const base::Value* policy_value = preference->GetValue();
  DCHECK(policy_value->is_dict());

  const base::Value::List* entries =
      policy_value->GetDict().FindList("entries");
  if (!entries || entries->empty()) {
    return std::nullopt;
  }

  // Currently only single daily window is supported.
  const auto& window = entries->front().GetDict();
  const std::optional<int> hour = window.FindIntByDottedPath("start.hour");
  const std::optional<int> minute = window.FindIntByDottedPath("start.minute");
  const std::optional<int> duration_mins = window.FindInt("duration_mins");

  if (!hour || !minute || !duration_mins)
    return std::nullopt;

  return RelaunchWindow(hour.value(), minute.value(),
                        base::Minutes(duration_mins.value()));
}

// static
base::TimeDelta UpgradeDetector::GetGracePeriod(
    base::TimeDelta elevated_to_high_delta) {
  return std::min(kDefaultGracePeriod, elevated_to_high_delta / 2);
}

bool UpgradeDetector::GetNetworkTimeWithFallback(base::Time& current_time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (g_browser_process->network_time_tracker()->GetNetworkTime(
          &current_time, /*uncertainty=*/nullptr) ==
      network_time::NetworkTimeTracker::NETWORK_TIME_AVAILABLE) {
    return true;
  }
  // When network time has not been initialized yet, simply rely on the
  // machine's current time.
  current_time = base::Time::Now();
  return false;
}

// static
bool UpgradeDetector::ShouldRelaunchFast() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!last_served_date_.has_value()) {
    return false;
  }
  base::TimeDelta max_age = GetRelaunchFastIfOutdated();
  if (max_age.is_zero()) {
    return false;
  }
  base::Time current_time;
  GetNetworkTimeWithFallback(current_time);
  return current_time - last_served_date_.value() > max_age;
}

bool UpgradeDetector::ShouldFetchLastServedDate() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return !fetched_last_served_date_ &&
         !GetRelaunchFastIfOutdated().is_zero();
}

void UpgradeDetector::FetchLastServedDate() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (fetched_last_served_date_) {
    // Only do it once.
    return;
  }
  fetched_last_served_date_ = true;
  GetLastServedDate(version_info::GetVersion(),
                    base::BindOnce(&UpgradeDetector::OnGotLastServedDate,
                                   weak_factory_.GetWeakPtr()));
}

void UpgradeDetector::NotifyUpgrade() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // An implementation will request that a notification be sent after dropping
  // back to the "none" annoyance level if the RelaunchNotificationPeriod
  // setting changes to a large enough value such that none of the revised
  // thresholds have been hit. In this case, consumers should not perceive that
  // an upgrade is available when checking notify_upgrade(). In practice, this
  // is only the case on desktop Chrome and not Chrome OS, where the lowest
  // threshold is hit the moment the upgrade is detected.
  notify_upgrade_ = upgrade_notification_stage_ != UPGRADE_ANNOYANCE_NONE;

  NotifyUpgradeRecommended();
  if (upgrade_available_ == UPGRADE_NEEDED_OUTDATED_INSTALL) {
    NotifyOutdatedInstall();
  } else if (upgrade_available_ == UPGRADE_NEEDED_OUTDATED_INSTALL_NO_AU) {
    NotifyOutdatedInstallNoAutoUpdate();
  } else if (upgrade_available_ == UPGRADE_AVAILABLE_CRITICAL ||
             critical_experiment_updates_available_) {
    TriggerCriticalUpdate();
  }
}

void UpgradeDetector::NotifyUpgradeRecommended() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (observer_list_.empty())
    return;

  for (auto& observer : observer_list_)
    observer.OnUpgradeRecommended();
}

void UpgradeDetector::NotifyCriticalUpgradeInstalled() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (observer_list_.empty())
    return;

  for (auto& observer : observer_list_)
    observer.OnCriticalUpgradeInstalled();
}

void UpgradeDetector::NotifyUpdateDeferred(bool use_notification) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (observer_list_.empty())
    return;

  for (auto& observer : observer_list_)
    observer.OnUpdateDeferred(use_notification);
}

void UpgradeDetector::NotifyUpdateOverCellularAvailable() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (observer_list_.empty())
    return;

  for (auto& observer : observer_list_)
    observer.OnUpdateOverCellularAvailable();
}

void UpgradeDetector::NotifyUpdateOverCellularOneTimePermissionGranted() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (observer_list_.empty())
    return;

  for (auto& observer : observer_list_)
    observer.OnUpdateOverCellularOneTimePermissionGranted();
}

void UpgradeDetector::NotifyRelaunchOverriddenToRequired(bool overridden) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (observer_list_.empty())
    return;

  for (auto& observer : observer_list_)
    observer.OnRelaunchOverriddenToRequired(overridden);
}

void UpgradeDetector::TriggerCriticalUpdate() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const base::TimeDelta idle_timer =
      UseTestingIntervals() ? base::Seconds(kIdleRepeatingTimerWait)
                            : base::Minutes(kIdleRepeatingTimerWait);
  idle_check_timer_.Start(FROM_HERE, idle_timer, this,
                          &UpgradeDetector::CheckIdle);
}

void UpgradeDetector::CheckIdle() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Don't proceed while an off-the-record or Guest window is open. The timer
  // will still keep firing, so this function will get a chance to re-evaluate
  // this.
  if (IsOffTheRecordSessionActive() || BrowserList::GetGuestBrowserCount()) {
    return;
  }

  // CalculateIdleState expects an interval in seconds.
  int idle_time_allowed =
      UseTestingIntervals() ? kIdleAmount : kIdleAmount * 60 * 60;

  ui::IdleState state = ui::CalculateIdleState(idle_time_allowed);

  switch (state) {
    case ui::IDLE_STATE_LOCKED:
      // Computer is locked, auto-restart.
      idle_check_timer_.Stop();
      chrome::AttemptRestart();
      break;
    case ui::IDLE_STATE_IDLE:
      // Computer has been idle for long enough, show warning.
      idle_check_timer_.Stop();
      NotifyCriticalUpgradeInstalled();
      break;
    case ui::IDLE_STATE_ACTIVE:
    case ui::IDLE_STATE_UNKNOWN:
      break;
  }
}

void UpgradeDetector::OnRelaunchPrefChanged() {
  // Coalesce simultaneous changes to multiple prefs into a single call to the
  // implementation's RecomputeSchedule method by making the call in a
  // task that will run after processing returns to the main event loop.
  if (pref_change_task_pending_)
    return;

  pref_change_task_pending_ = true;
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(
                     [](base::WeakPtr<UpgradeDetector> weak_this) {
                       if (weak_this) {
                         weak_this->pref_change_task_pending_ = false;
                         weak_this->RecomputeSchedule();
                       }
                     },
                     weak_factory_.GetWeakPtr()));
}

void UpgradeDetector::OnGotLastServedDate(
    std::optional<base::Time> last_served_date) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (last_served_date.has_value()) {
    last_served_date_ = last_served_date;
    RecomputeSchedule();
  }
}