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 6 cr.define('mobile', function() { 7 8 function MobileSetup() { 9 } 10 11 cr.addSingletonGetter(MobileSetup); 12 13 MobileSetup.PLAN_ACTIVATION_UNKNOWN = -2; 14 MobileSetup.PLAN_ACTIVATION_PAGE_LOADING = -1; 15 MobileSetup.PLAN_ACTIVATION_START = 0; 16 MobileSetup.PLAN_ACTIVATION_TRYING_OTASP = 1; 17 MobileSetup.PLAN_ACTIVATION_INITIATING_ACTIVATION = 3; 18 MobileSetup.PLAN_ACTIVATION_RECONNECTING = 4; 19 MobileSetup.PLAN_ACTIVATION_WAITING_FOR_CONNECTION = 5; 20 MobileSetup.PLAN_ACTIVATION_PAYMENT_PORTAL_LOADING = 6; 21 MobileSetup.PLAN_ACTIVATION_SHOWING_PAYMENT = 7; 22 MobileSetup.PLAN_ACTIVATION_RECONNECTING_PAYMENT = 8; 23 MobileSetup.PLAN_ACTIVATION_DELAY_OTASP = 9; 24 MobileSetup.PLAN_ACTIVATION_START_OTASP = 10; 25 MobileSetup.PLAN_ACTIVATION_OTASP = 11; 26 MobileSetup.PLAN_ACTIVATION_DONE = 12; 27 MobileSetup.PLAN_ACTIVATION_ERROR = 0xFF; 28 29 MobileSetup.EXTENSION_PAGE_URL = 30 'chrome-extension://iadeocfgjdjdmpenejdbfeaocpbikmab'; 31 MobileSetup.ACTIVATION_PAGE_URL = MobileSetup.EXTENSION_PAGE_URL + 32 '/activation.html'; 33 MobileSetup.PORTAL_OFFLINE_PAGE_URL = MobileSetup.EXTENSION_PAGE_URL + 34 '/portal_offline.html'; 35 MobileSetup.REDIRECT_POST_PAGE_URL = MobileSetup.EXTENSION_PAGE_URL + 36 '/redirect.html'; 37 38 MobileSetup.prototype = { 39 // Mobile device information. 40 deviceInfo_: null, 41 frameName_: '', 42 initialized_: false, 43 fakedTransaction_: false, 44 paymentShown_: false, 45 frameLoadError_: 0, 46 frameLoadIgnored_: true, 47 carrierPageUrl_: null, 48 spinnerInt_: -1, 49 // UI states. 50 state_: MobileSetup.PLAN_ACTIVATION_UNKNOWN, 51 STATE_UNKNOWN_: 'unknown', 52 STATE_CONNECTING_: 'connecting', 53 STATE_ERROR_: 'error', 54 STATE_PAYMENT_: 'payment', 55 STATE_ACTIVATING_: 'activating', 56 STATE_CONNECTED_: 'connected', 57 58 initialize: function(frame_name, carrierPage) { 59 if (this.initialized_) { 60 console.log('calling initialize() again?'); 61 return; 62 } 63 this.initialized_ = true; 64 self = this; 65 this.frameName_ = frame_name; 66 67 cr.ui.dialogs.BaseDialog.OK_LABEL = 68 loadTimeData.getString('ok_button'); 69 cr.ui.dialogs.BaseDialog.CANCEL_LABEL = 70 loadTimeData.getString('cancel_button'); 71 this.confirm_ = new cr.ui.dialogs.ConfirmDialog(document.body); 72 73 window.addEventListener('message', function(e) { 74 self.onMessageReceived_(e); 75 }); 76 77 $('closeButton').addEventListener('click', function(e) { 78 $('finalStatus').classList.add('hidden'); 79 }); 80 81 // Kick off activation process. 82 chrome.send('startActivation'); 83 }, 84 85 startSpinner_: function() { 86 this.stopSpinner_(); 87 this.spinnerInt_ = setInterval(mobile.MobileSetup.drawProgress, 100); 88 }, 89 90 stopSpinner_: function() { 91 if (this.spinnerInt_ != -1) { 92 clearInterval(this.spinnerInt_); 93 this.spinnerInt_ = -1; 94 } 95 }, 96 97 onFrameLoaded_: function(success) { 98 chrome.send('paymentPortalLoad', [success ? 'ok' : 'failed']); 99 }, 100 101 loadPaymentFrame_: function(deviceInfo) { 102 if (deviceInfo) { 103 this.frameLoadError_ = 0; 104 this.deviceInfo_ = deviceInfo; 105 if (deviceInfo.post_data && deviceInfo.post_data.length) { 106 this.frameLoadIgnored_ = true; 107 $(this.frameName_).contentWindow.location.href = 108 MobileSetup.REDIRECT_POST_PAGE_URL + 109 '?post_data=' + escape(deviceInfo.post_data) + 110 '&formUrl=' + escape(deviceInfo.payment_url); 111 } else { 112 this.frameLoadIgnored_ = false; 113 $(this.frameName_).contentWindow.location.href = 114 deviceInfo.payment_url; 115 } 116 } 117 }, 118 119 onMessageReceived_: function(e) { 120 if (e.origin != 121 this.deviceInfo_.payment_url.substring(0, e.origin.length) && 122 e.origin != MobileSetup.EXTENSION_PAGE_URL) 123 return; 124 125 if (e.data.type == 'requestDeviceInfoMsg') { 126 this.sendDeviceInfo_(); 127 } else if (e.data.type == 'framePostReady') { 128 this.frameLoadIgnored_ = false; 129 this.sendPostFrame_(e.origin); 130 } else if (e.data.type == 'reportTransactionStatusMsg') { 131 console.log('calling setTransactionStatus from onMessageReceived_'); 132 chrome.send('setTransactionStatus', [e.data.status]); 133 } 134 }, 135 136 changeState_: function(deviceInfo) { 137 var newState = deviceInfo.state; 138 if (this.state_ == newState) 139 return; 140 141 // The mobile setup is already in its final state. 142 if (this.state_ == MobileSetup.PLAN_ACTIVATION_DONE || 143 this.state_ == MobileSetup.PLAN_ACTIVATION_ERROR) { 144 return; 145 } 146 147 // Map handler state to UX. 148 var simpleActivationFlow = 149 (deviceInfo.activation_type == 'NonCellular' || 150 deviceInfo.activation_type == 'OTA'); 151 switch (newState) { 152 case MobileSetup.PLAN_ACTIVATION_PAGE_LOADING: 153 case MobileSetup.PLAN_ACTIVATION_START: 154 case MobileSetup.PLAN_ACTIVATION_DELAY_OTASP: 155 case MobileSetup.PLAN_ACTIVATION_START_OTASP: 156 case MobileSetup.PLAN_ACTIVATION_RECONNECTING: 157 case MobileSetup.PLAN_ACTIVATION_RECONNECTING_PAYMENT: 158 // Activation page should not be shown for the simple activation flow. 159 if (simpleActivationFlow) 160 break; 161 162 $('statusHeader').textContent = 163 loadTimeData.getString('connecting_header'); 164 $('auxHeader').textContent = 165 loadTimeData.getString('please_wait'); 166 $('paymentForm').classList.add('hidden'); 167 $('finalStatus').classList.add('hidden'); 168 this.setCarrierPage_(MobileSetup.ACTIVATION_PAGE_URL); 169 $('systemStatus').classList.remove('hidden'); 170 $('canvas').classList.remove('hidden'); 171 this.startSpinner_(); 172 break; 173 case MobileSetup.PLAN_ACTIVATION_TRYING_OTASP: 174 case MobileSetup.PLAN_ACTIVATION_INITIATING_ACTIVATION: 175 case MobileSetup.PLAN_ACTIVATION_OTASP: 176 // Activation page should not be shown for the simple activation flow. 177 if (simpleActivationFlow) 178 break; 179 180 $('statusHeader').textContent = 181 loadTimeData.getString('activating_header'); 182 $('auxHeader').textContent = 183 loadTimeData.getString('please_wait'); 184 $('paymentForm').classList.add('hidden'); 185 $('finalStatus').classList.add('hidden'); 186 this.setCarrierPage_(MobileSetup.ACTIVATION_PAGE_URL); 187 $('systemStatus').classList.remove('hidden'); 188 $('canvas').classList.remove('hidden'); 189 this.startSpinner_(); 190 break; 191 case MobileSetup.PLAN_ACTIVATION_PAYMENT_PORTAL_LOADING: 192 // Activation page should not be shown for the simple activation flow. 193 if (!simpleActivationFlow) { 194 $('statusHeader').textContent = 195 loadTimeData.getString('connecting_header'); 196 $('auxHeader').textContent = ''; 197 $('paymentForm').classList.add('hidden'); 198 $('finalStatus').classList.add('hidden'); 199 this.setCarrierPage_(MobileSetup.ACTIVATION_PAGE_URL); 200 $('systemStatus').classList.remove('hidden'); 201 $('canvas').classList.remove('hidden'); 202 } 203 this.loadPaymentFrame_(deviceInfo); 204 break; 205 case MobileSetup.PLAN_ACTIVATION_WAITING_FOR_CONNECTION: 206 var statusHeaderText; 207 var carrierPage; 208 if (deviceInfo.activation_type == 'NonCellular') { 209 statusHeaderText = loadTimeData.getString( 210 'portal_unreachable_header'); 211 carrierPage = MobileSetup.PORTAL_OFFLINE_PAGE_URL; 212 } else if (deviceInfo.activation_type == 'OTA') { 213 statusHeaderText = 214 loadTimeData.getString('connecting_header'); 215 carrierPage = MobileSetup.ACTIVATION_PAGE_URL; 216 } 217 $('statusHeader').textContent = statusHeaderText; 218 $('auxHeader').textContent = ''; 219 $('auxHeader').classList.add('hidden'); 220 $('paymentForm').classList.add('hidden'); 221 $('finalStatus').classList.add('hidden'); 222 $('systemStatus').classList.remove('hidden'); 223 this.setCarrierPage_(carrierPage); 224 $('canvas').classList.remove('hidden'); 225 this.startSpinner_(); 226 break; 227 case MobileSetup.PLAN_ACTIVATION_SHOWING_PAYMENT: 228 $('statusHeader').textContent = ''; 229 $('auxHeader').textContent = ''; 230 $('finalStatus').classList.add('hidden'); 231 $('systemStatus').classList.add('hidden'); 232 $('paymentForm').classList.remove('hidden'); 233 $('canvas').classList.add('hidden'); 234 this.stopSpinner_(); 235 this.paymentShown_ = true; 236 break; 237 case MobileSetup.PLAN_ACTIVATION_DONE: 238 $('statusHeader').textContent = ''; 239 $('auxHeader').textContent = ''; 240 $('finalHeader').textContent = 241 loadTimeData.getString('completed_header'); 242 $('finalMessage').textContent = 243 loadTimeData.getString('completed_text'); 244 $('systemStatus').classList.add('hidden'); 245 $('closeButton').classList.remove('hidden'); 246 $('finalStatus').classList.remove('hidden'); 247 $('canvas').classList.add('hidden'); 248 $('closeButton').classList.toggle('hidden', !this.paymentShown_); 249 $('paymentForm').classList.toggle('hidden', !this.paymentShown_); 250 this.stopSpinner_(); 251 break; 252 case MobileSetup.PLAN_ACTIVATION_ERROR: 253 $('statusHeader').textContent = ''; 254 $('auxHeader').textContent = ''; 255 $('finalHeader').textContent = 256 loadTimeData.getString('error_header'); 257 $('finalMessage').textContent = deviceInfo.error; 258 $('systemStatus').classList.add('hidden'); 259 $('canvas').classList.add('hidden'); 260 $('closeButton').classList.toggle('hidden', !this.paymentShown_); 261 $('paymentForm').classList.toggle('hidden', !this.paymentShown_); 262 $('finalStatus').classList.remove('hidden'); 263 this.stopSpinner_(); 264 break; 265 } 266 this.state_ = newState; 267 }, 268 269 setCarrierPage_: function(url) { 270 if (this.carrierPageUrl_ == url) 271 return; 272 this.carrierPageUrl_ = url; 273 $('carrierPage').contentWindow.location.href = url; 274 }, 275 276 updateDeviceStatus_: function(deviceInfo) { 277 this.changeState_(deviceInfo); 278 }, 279 280 portalFrameLoadError_: function(errorCode) { 281 if (this.frameLoadIgnored_) 282 return; 283 console.log('Portal frame load error detected: ', errorCode); 284 this.frameLoadError_ = errorCode; 285 }, 286 287 portalFrameLoadCompleted_: function() { 288 if (this.frameLoadIgnored_) 289 return; 290 console.log('Portal frame load completed!'); 291 this.onFrameLoaded_(this.frameLoadError_ == 0); 292 }, 293 294 sendPostFrame_: function(frameUrl) { 295 var msg = { type: 'postFrame' }; 296 $(this.frameName_).contentWindow.postMessage(msg, frameUrl); 297 }, 298 299 sendDeviceInfo_: function() { 300 var msg = { 301 type: 'deviceInfoMsg', 302 domain: document.location, 303 payload: { 304 'carrier': this.deviceInfo_.carrier, 305 'MEID': this.deviceInfo_.MEID, 306 'IMEI': this.deviceInfo_.IMEI, 307 'MDN': this.deviceInfo_.MDN 308 } 309 }; 310 $(this.frameName_).contentWindow.postMessage(msg, 311 this.deviceInfo_.payment_url); 312 } 313 314 }; 315 316 MobileSetup.drawProgress = function() { 317 var ctx = canvas.getContext('2d'); 318 ctx.clearRect(0, 0, canvas.width, canvas.height); 319 320 var segmentCount = Math.min(12, canvas.width / 1.6); // Number of segments 321 var rotation = 0.75; // Counterclockwise rotation 322 323 // Rotate canvas over time 324 ctx.translate(canvas.width / 2, canvas.height / 2); 325 ctx.rotate(Math.PI * 2 / (segmentCount + rotation)); 326 ctx.translate(-canvas.width / 2, -canvas.height / 2); 327 328 var gap = canvas.width / 24; // Gap between segments 329 var oRadius = canvas.width / 2; // Outer radius 330 var iRadius = oRadius * 0.618; // Inner radius 331 var oCircumference = Math.PI * 2 * oRadius; // Outer circumference 332 var iCircumference = Math.PI * 2 * iRadius; // Inner circumference 333 var oGap = gap / oCircumference; // Gap size as fraction of outer ring 334 var iGap = gap / iCircumference; // Gap size as fraction of inner ring 335 var oArc = Math.PI * 2 * (1 / segmentCount - oGap); // Angle of outer arcs 336 var iArc = Math.PI * 2 * (1 / segmentCount - iGap); // Angle of inner arcs 337 338 for (i = 0; i < segmentCount; i++) { // Draw each segment 339 var opacity = Math.pow(1.0 - i / segmentCount, 3.0); 340 opacity = (0.15 + opacity * 0.8); // Vary from 0.15 to 0.95 341 var angle = - Math.PI * 2 * i / segmentCount; 342 343 ctx.beginPath(); 344 ctx.arc(canvas.width / 2, canvas.height / 2, oRadius, 345 angle - oArc / 2, angle + oArc / 2, false); 346 ctx.arc(canvas.width / 2, canvas.height / 2, iRadius, 347 angle + iArc / 2, angle - iArc / 2, true); 348 ctx.closePath(); 349 ctx.fillStyle = 'rgba(240, 30, 29, ' + opacity + ')'; 350 ctx.fill(); 351 } 352 }; 353 354 MobileSetup.deviceStateChanged = function(deviceInfo) { 355 MobileSetup.getInstance().updateDeviceStatus_(deviceInfo); 356 }; 357 358 MobileSetup.portalFrameLoadError = function(errorCode) { 359 MobileSetup.getInstance().portalFrameLoadError_(errorCode); 360 }; 361 362 MobileSetup.portalFrameLoadCompleted = function() { 363 MobileSetup.getInstance().portalFrameLoadCompleted_(); 364 }; 365 366 MobileSetup.loadPage = function() { 367 mobile.MobileSetup.getInstance().initialize('paymentForm', 368 mobile.MobileSetup.ACTIVATION_PAGE_URL); 369 }; 370 371 // Export 372 return { 373 MobileSetup: MobileSetup 374 }; 375 }); 376 377 document.addEventListener('DOMContentLoaded', mobile.MobileSetup.loadPage); 378