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 "grit/chromium_strings.h" 13#include "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