1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/webui/help/version_updater_mac.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "chrome/browser/lifetime/application_lifetime.h"
10#import "chrome/browser/mac/keystone_glue.h"
11#include "chrome/browser/mac/obsolete_system.h"
12#include "chrome/grit/chromium_strings.h"
13#include "chrome/grit/generated_resources.h"
14#include "ui/base/l10n/l10n_util.h"
15
16// KeystoneObserver is a simple notification observer for Keystone status
17// updates. It will be created and managed by VersionUpdaterMac.
18@interface KeystoneObserver : NSObject {
19 @private
20  VersionUpdaterMac* versionUpdater_;  // Weak.
21}
22
23// Initialize an observer with an updater. The updater owns this object.
24- (id)initWithUpdater:(VersionUpdaterMac*)updater;
25
26// Notification callback, called with the status of keystone operations.
27- (void)handleStatusNotification:(NSNotification*)notification;
28
29@end  // @interface KeystoneObserver
30
31@implementation KeystoneObserver
32
33- (id)initWithUpdater:(VersionUpdaterMac*)updater {
34  if ((self = [super init])) {
35    versionUpdater_ = updater;
36    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
37    [center addObserver:self
38               selector:@selector(handleStatusNotification:)
39                   name:kAutoupdateStatusNotification
40                 object:nil];
41  }
42  return self;
43}
44
45- (void)dealloc {
46  [[NSNotificationCenter defaultCenter] removeObserver:self];
47  [super dealloc];
48}
49
50- (void)handleStatusNotification:(NSNotification*)notification {
51  versionUpdater_->UpdateStatus([notification userInfo]);
52}
53
54@end  // @implementation KeystoneObserver
55
56
57VersionUpdater* VersionUpdater::Create() {
58  return new VersionUpdaterMac;
59}
60
61VersionUpdaterMac::VersionUpdaterMac()
62    : show_promote_button_(false),
63      keystone_observer_([[KeystoneObserver alloc] initWithUpdater:this]) {
64}
65
66VersionUpdaterMac::~VersionUpdaterMac() {
67}
68
69void VersionUpdaterMac::CheckForUpdate(
70    const StatusCallback& status_callback,
71    const PromoteCallback& promote_callback) {
72  // Copy the callbacks, we will re-use this for the remaining lifetime
73  // of this object.
74  status_callback_ = status_callback;
75  promote_callback_ = promote_callback;
76
77  KeystoneGlue* keystone_glue = [KeystoneGlue defaultKeystoneGlue];
78  if (keystone_glue && ![keystone_glue isOnReadOnlyFilesystem]) {
79    AutoupdateStatus recent_status = [keystone_glue recentStatus];
80    if ([keystone_glue asyncOperationPending] ||
81        recent_status == kAutoupdateRegisterFailed ||
82        recent_status == kAutoupdateNeedsPromotion) {
83      // If an asynchronous update operation is currently pending, such as a
84      // check for updates or an update installation attempt, set the status
85      // up correspondingly without launching a new update check.
86      //
87      // If registration failed, no other operations make sense, so just go
88      // straight to the error.
89      UpdateStatus([[keystone_glue recentNotification] userInfo]);
90    } else {
91      // Launch a new update check, even if one was already completed, because
92      // a new update may be available or a new update may have been installed
93      // in the background since the last time the Help page was displayed.
94      [keystone_glue checkForUpdate];
95
96      // Immediately, kAutoupdateStatusNotification will be posted, with status
97      // kAutoupdateChecking.
98      //
99      // Upon completion, kAutoupdateStatusNotification will be posted with a
100      // status indicating the result of the check.
101    }
102
103    UpdateShowPromoteButton();
104  } else {
105    // There is no glue, or the application is on a read-only filesystem.
106    // Updates and promotions are impossible.
107    status_callback_.Run(DISABLED, 0, base::string16());
108  }
109}
110
111void VersionUpdaterMac::PromoteUpdater() const {
112  // Tell Keystone to make software updates available for all users.
113  [[KeystoneGlue defaultKeystoneGlue] promoteTicket];
114
115  // Immediately, kAutoupdateStatusNotification will be posted, and
116  // UpdateStatus() will be called with status kAutoupdatePromoting.
117  //
118  // Upon completion, kAutoupdateStatusNotification will be posted, and
119  // UpdateStatus() will be called with a status indicating a result of the
120  // installation attempt.
121  //
122  // If the promotion was successful, KeystoneGlue will re-register the ticket
123  // and UpdateStatus() will be called again indicating first that
124  // registration is in progress and subsequently that it has completed.
125}
126
127void VersionUpdaterMac::RelaunchBrowser() const {
128  // Tell the Broweser to restart if possible.
129  chrome::AttemptRestart();
130}
131
132void VersionUpdaterMac::UpdateStatus(NSDictionary* dictionary) {
133  AutoupdateStatus keystone_status = static_cast<AutoupdateStatus>(
134      [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]);
135
136  bool enable_promote_button = true;
137  base::string16 message;
138
139  Status status;
140  switch (keystone_status) {
141    case kAutoupdateRegistering:
142    case kAutoupdateChecking:
143      status = CHECKING;
144      enable_promote_button = false;
145      break;
146
147    case kAutoupdateRegistered:
148    case kAutoupdatePromoted:
149      UpdateShowPromoteButton();
150      // Go straight into an update check. Return immediately, this routine
151      // will be re-entered shortly with kAutoupdateChecking.
152      [[KeystoneGlue defaultKeystoneGlue] checkForUpdate];
153      return;
154
155    case kAutoupdateCurrent:
156      status = UPDATED;
157      break;
158
159    case kAutoupdateAvailable:
160      // Install the update automatically. Return immediately, this routine
161      // will be re-entered shortly with kAutoupdateInstalling.
162      [[KeystoneGlue defaultKeystoneGlue] installUpdate];
163      return;
164
165    case kAutoupdateInstalling:
166      status = UPDATING;
167      enable_promote_button = false;
168      break;
169
170    case kAutoupdateInstalled:
171      status = NEARLY_UPDATED;
172      break;
173
174    case kAutoupdatePromoting:
175#if 1
176      // TODO(mark): KSRegistration currently handles the promotion
177      // synchronously, meaning that the main thread's loop doesn't spin,
178      // meaning that animations and other updates to the window won't occur
179      // until KSRegistration is done with promotion. This looks laggy and bad
180      // and probably qualifies as "jank." For now, there just won't be any
181      // visual feedback while promotion is in progress, but it should complete
182      // (or fail) very quickly.  http://b/2290009.
183      return;
184#endif
185      status = CHECKING;
186      enable_promote_button = false;
187      break;
188
189    case kAutoupdateRegisterFailed:
190      enable_promote_button = false;
191      // Fall through.
192    case kAutoupdateCheckFailed:
193    case kAutoupdateInstallFailed:
194    case kAutoupdatePromoteFailed:
195      status = FAILED;
196      message = l10n_util::GetStringFUTF16Int(IDS_UPGRADE_ERROR,
197                                              keystone_status);
198      break;
199
200    case kAutoupdateNeedsPromotion:
201      {
202        status = FAILED;
203        base::string16 product_name =
204            l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
205        message = l10n_util::GetStringFUTF16(IDS_PROMOTE_INFOBAR_TEXT,
206                                             product_name);
207      }
208      break;
209
210    default:
211      NOTREACHED();
212      return;
213  }
214  if (!status_callback_.is_null())
215    status_callback_.Run(status, 0, message);
216
217  if (!promote_callback_.is_null()) {
218    PromotionState promotion_state = PROMOTE_HIDDEN;
219    if (show_promote_button_)
220      promotion_state = enable_promote_button ? PROMOTE_ENABLED
221                                              : PROMOTE_DISABLED;
222    promote_callback_.Run(promotion_state);
223  }
224}
225
226void VersionUpdaterMac::UpdateShowPromoteButton() {
227  if (ObsoleteSystemMac::Has32BitOnlyCPU() &&
228      ObsoleteSystemMac::Is32BitEndOfTheLine()) {
229    // Promotion is moot upon reaching the end of the line.
230    show_promote_button_ = false;
231    return;
232  }
233
234  KeystoneGlue* keystone_glue = [KeystoneGlue defaultKeystoneGlue];
235  AutoupdateStatus recent_status = [keystone_glue recentStatus];
236  if (recent_status == kAutoupdateRegistering ||
237      recent_status == kAutoupdateRegisterFailed ||
238      recent_status == kAutoupdatePromoted) {
239    // Promotion isn't possible at this point.
240    show_promote_button_ = false;
241  } else if (recent_status == kAutoupdatePromoting ||
242             recent_status == kAutoupdatePromoteFailed) {
243    // Show promotion UI because the user either just clicked that button or
244    // because the user should be able to click it again.
245    show_promote_button_ = true;
246  } else {
247    // Show the promote button if promotion is a possibility.
248    show_promote_button_ = [keystone_glue wantsPromotion];
249  }
250}
251