blob: 276ecd23c2847bd4ad9490249a1b561b6565f672 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('mobile', function() {
function MobileSetup() {}
cr.addSingletonGetter(MobileSetup);
MobileSetup.PLAN_ACTIVATION_UNKNOWN = -2;
MobileSetup.PLAN_ACTIVATION_PAGE_LOADING = -1;
MobileSetup.PLAN_ACTIVATION_START = 0;
MobileSetup.PLAN_ACTIVATION_TRYING_OTASP = 1;
MobileSetup.PLAN_ACTIVATION_INITIATING_ACTIVATION = 3;
MobileSetup.PLAN_ACTIVATION_RECONNECTING = 4;
MobileSetup.PLAN_ACTIVATION_WAITING_FOR_CONNECTION = 5;
MobileSetup.PLAN_ACTIVATION_PAYMENT_PORTAL_LOADING = 6;
MobileSetup.PLAN_ACTIVATION_SHOWING_PAYMENT = 7;
MobileSetup.PLAN_ACTIVATION_RECONNECTING_PAYMENT = 8;
MobileSetup.PLAN_ACTIVATION_DELAY_OTASP = 9;
MobileSetup.PLAN_ACTIVATION_START_OTASP = 10;
MobileSetup.PLAN_ACTIVATION_OTASP = 11;
MobileSetup.PLAN_ACTIVATION_DONE = 12;
MobileSetup.PLAN_ACTIVATION_ERROR = 0xFF;
MobileSetup.EXTENSION_PAGE_URL =
'chrome-extension://iadeocfgjdjdmpenejdbfeaocpbikmab';
MobileSetup.ACTIVATION_PAGE_URL =
MobileSetup.EXTENSION_PAGE_URL + '/activation.html';
MobileSetup.PORTAL_OFFLINE_PAGE_URL =
MobileSetup.EXTENSION_PAGE_URL + '/portal_offline.html';
MobileSetup.prototype = {
// Mobile device information.
deviceInfo_: null,
initialized_: false,
fakedTransaction_: false,
paymentShown_: false,
carrierPageUrl_: null,
spinnerInt_: -1,
// UI states.
state_: MobileSetup.PLAN_ACTIVATION_UNKNOWN,
STATE_UNKNOWN_: 'unknown',
STATE_CONNECTING_: 'connecting',
STATE_ERROR_: 'error',
STATE_PAYMENT_: 'payment',
STATE_ACTIVATING_: 'activating',
STATE_CONNECTED_: 'connected',
initialize: function(frame_name, carrierPage) {
if (this.initialized_) {
console.log('calling initialize() again?');
return;
}
this.initialized_ = true;
self = this;
cr.ui.dialogs.BaseDialog.OK_LABEL = loadTimeData.getString('ok_button');
cr.ui.dialogs.BaseDialog.CANCEL_LABEL =
loadTimeData.getString('cancel_button');
this.confirm_ = new cr.ui.dialogs.ConfirmDialog(document.body);
window.addEventListener('message', function(e) {
self.onMessageReceived_(e);
});
$('closeButton').addEventListener('click', function(e) {
$('finalStatus').classList.add('hidden');
});
// Kick off activation process.
chrome.send('startActivation');
},
startSpinner_: function() {
this.stopSpinner_();
this.spinnerInt_ = setInterval(mobile.MobileSetup.drawProgress, 100);
},
stopSpinner_: function() {
if (this.spinnerInt_ != -1) {
clearInterval(this.spinnerInt_);
this.spinnerInt_ = -1;
}
},
/**
* Handler for loadabort event on the payment portal webview.
* Notifies Chrome that the payment portal load failed.
* @param {string} paymentUrl The payment portal URL, as provided by the
* cellular service.
* @param {!Object} evt Load abort event.
* @private
*/
paymentLoadAborted_: function(paymentUrl, evt) {
if (!evt.isTopLevel ||
new URL(evt.url).origin != new URL(paymentUrl).origin) {
return;
}
chrome.send('paymentPortalLoad', ['failed']);
},
/**
* Handler for loadcommit event on the payment portal webview.
* Notifies Chrome that the payment portal was loaded.
* @param {string} paymentUrl The payment portal URL, as provided by the
* cellular service.
* @param {!Object} evt Load commit event.
* @private
*/
paymentLoadCommitted_: function(paymentUrl, evt) {
if (!evt.isTopLevel ||
new URL(evt.url).origin != new URL(paymentUrl).origin) {
return;
}
// Workaround for https://crbug.com/893248 - for some reason, the payment
// portal does not load properly after the payment frame submition.
// Reloading the frame seems to bypass the problem.
// TODO(tbarzic): Remove this once the problem has been properly
// addressed.
if (!this.paymentPortalReloaded_) {
this.paymentPortalReloaded_ = true;
$('portalFrameWebview').reload();
} else {
chrome.send('paymentPortalLoad', ['ok']);
}
},
/**
* Sends a <code>loadedInWebview</code> message to the mobile service
* portal webview.
* @param {string} paymentUrl The payment portal URL - used to restrict
* origins to which the message is sent.
*/
sendInitialMessage_: function(paymentUrl) {
$('portalFrameWebview')
.contentWindow.postMessage({msg: 'loadedInWebview'}, paymentUrl);
},
/**
* Loads payment URL defined by <code>deviceInfo</code> into the
* portal frame webview.
* If the webview element already exists, it will not be reused - the
* existing webview will be removed from DOM, and a new one will be
* created.
* If deviceInfo provides post data to be sent to the payment URL, the
* webview will be initilized using
* <code>mobile.util.postDeviceDataToWebview</code>, otherwise the payment
* URL will be loaded directly into the webview.
*
* Note that the portal frame webview will only ever contain data and web
* URLs - it will never embed the mobile setup extension resources.
*
* @param {!Object} deviceInfo The cellular service info - contains the
* information that should be passed to the payment portal.
* @private
*/
loadPaymentFrame_: function(deviceInfo) {
if (!deviceInfo)
return;
this.deviceInfo_ = deviceInfo;
var existingWebview = $('portalFrameWebview');
if (existingWebview)
existingWebview.remove();
var frame = document.createElement('webview');
frame.id = 'portalFrameWebview';
this.paymentPortalReloaded_ = false;
$('portalFrame').appendChild(frame);
frame.addEventListener(
'loadabort',
this.paymentLoadAborted_.bind(this, deviceInfo.payment_url));
frame.addEventListener(
'loadcommit',
this.paymentLoadCommitted_.bind(this, deviceInfo.payment_url));
// Send a message to the loaded webview, so it can get a reference to
// which to send messages as needed.
frame.addEventListener(
'loadstop',
this.sendInitialMessage_.bind(this, deviceInfo.payment_url));
if (deviceInfo.post_data && deviceInfo.post_data.length) {
mobile.util.postDeviceDataToWebview(frame, deviceInfo);
} else {
frame.src = deviceInfo.payment_url;
}
},
onMessageReceived_: function(e) {
if (e.origin !=
this.deviceInfo_.payment_url.substring(0, e.origin.length))
return;
if (e.data.type == 'requestDeviceInfoMsg') {
this.sendDeviceInfo_();
} else if (e.data.type == 'reportTransactionStatusMsg') {
console.log('calling setTransactionStatus from onMessageReceived_');
chrome.send('setTransactionStatus', [e.data.status]);
}
},
changeState_: function(deviceInfo) {
var newState = deviceInfo.state;
if (this.state_ == newState)
return;
// The mobile setup is already in its final state.
if (this.state_ == MobileSetup.PLAN_ACTIVATION_DONE ||
this.state_ == MobileSetup.PLAN_ACTIVATION_ERROR) {
return;
}
// Map handler state to UX.
var simpleActivationFlow =
(deviceInfo.activation_type == 'NonCellular' ||
deviceInfo.activation_type == 'OTA');
switch (newState) {
case MobileSetup.PLAN_ACTIVATION_PAGE_LOADING:
case MobileSetup.PLAN_ACTIVATION_START:
case MobileSetup.PLAN_ACTIVATION_DELAY_OTASP:
case MobileSetup.PLAN_ACTIVATION_START_OTASP:
case MobileSetup.PLAN_ACTIVATION_RECONNECTING:
case MobileSetup.PLAN_ACTIVATION_RECONNECTING_PAYMENT:
// Activation page should not be shown for the simple activation
// flow.
if (simpleActivationFlow)
break;
$('statusHeader').textContent =
loadTimeData.getString('connecting_header');
$('auxHeader').textContent = loadTimeData.getString('please_wait');
$('portalFrame').classList.add('hidden');
$('finalStatus').classList.add('hidden');
this.setCarrierPage_(MobileSetup.ACTIVATION_PAGE_URL);
$('systemStatus').classList.remove('hidden');
$('canvas').classList.remove('hidden');
this.startSpinner_();
break;
case MobileSetup.PLAN_ACTIVATION_TRYING_OTASP:
case MobileSetup.PLAN_ACTIVATION_INITIATING_ACTIVATION:
case MobileSetup.PLAN_ACTIVATION_OTASP:
// Activation page should not be shown for the simple activation
// flow.
if (simpleActivationFlow)
break;
$('statusHeader').textContent =
loadTimeData.getString('activating_header');
$('auxHeader').textContent = loadTimeData.getString('please_wait');
$('portalFrame').classList.add('hidden');
$('finalStatus').classList.add('hidden');
this.setCarrierPage_(MobileSetup.ACTIVATION_PAGE_URL);
$('systemStatus').classList.remove('hidden');
$('canvas').classList.remove('hidden');
this.startSpinner_();
break;
case MobileSetup.PLAN_ACTIVATION_PAYMENT_PORTAL_LOADING:
// Activation page should not be shown for the simple activation
// flow.
if (!simpleActivationFlow) {
$('statusHeader').textContent =
loadTimeData.getString('connecting_header');
$('auxHeader').textContent = '';
$('portalFrame').classList.add('hidden');
$('finalStatus').classList.add('hidden');
this.setCarrierPage_(MobileSetup.ACTIVATION_PAGE_URL);
$('systemStatus').classList.remove('hidden');
$('canvas').classList.remove('hidden');
}
this.loadPaymentFrame_(deviceInfo);
break;
case MobileSetup.PLAN_ACTIVATION_WAITING_FOR_CONNECTION:
var statusHeaderText;
var carrierPage;
if (deviceInfo.activation_type == 'NonCellular') {
statusHeaderText =
loadTimeData.getString('portal_unreachable_header');
carrierPage = MobileSetup.PORTAL_OFFLINE_PAGE_URL;
} else if (deviceInfo.activation_type == 'OTA') {
statusHeaderText = loadTimeData.getString('connecting_header');
carrierPage = MobileSetup.ACTIVATION_PAGE_URL;
}
$('statusHeader').textContent = statusHeaderText;
$('auxHeader').textContent = '';
$('auxHeader').classList.add('hidden');
$('portalFrame').classList.add('hidden');
$('finalStatus').classList.add('hidden');
$('systemStatus').classList.remove('hidden');
this.setCarrierPage_(carrierPage);
$('canvas').classList.remove('hidden');
this.startSpinner_();
break;
case MobileSetup.PLAN_ACTIVATION_SHOWING_PAYMENT:
$('statusHeader').textContent = '';
$('auxHeader').textContent = '';
$('finalStatus').classList.add('hidden');
$('systemStatus').classList.add('hidden');
$('portalFrame').classList.remove('hidden');
$('canvas').classList.add('hidden');
this.stopSpinner_();
this.paymentShown_ = true;
break;
case MobileSetup.PLAN_ACTIVATION_DONE:
$('statusHeader').textContent = '';
$('auxHeader').textContent = '';
$('finalHeader').textContent =
loadTimeData.getString('completed_header');
$('finalMessage').textContent =
loadTimeData.getString('completed_text');
$('systemStatus').classList.add('hidden');
$('closeButton').classList.remove('hidden');
$('finalStatus').classList.remove('hidden');
$('canvas').classList.add('hidden');
$('closeButton').classList.toggle('hidden', !this.paymentShown_);
$('portalFrame').classList.toggle('hidden', !this.paymentShown_);
this.stopSpinner_();
break;
case MobileSetup.PLAN_ACTIVATION_ERROR:
$('statusHeader').textContent = '';
$('auxHeader').textContent = '';
$('finalHeader').textContent = loadTimeData.getString('error_header');
$('finalMessage').textContent = deviceInfo.error;
$('systemStatus').classList.add('hidden');
$('canvas').classList.add('hidden');
$('closeButton').classList.toggle('hidden', !this.paymentShown_);
$('portalFrame').classList.toggle('hidden', !this.paymentShown_);
$('finalStatus').classList.remove('hidden');
this.stopSpinner_();
break;
}
this.state_ = newState;
},
/**
* Embeds a URL into the <code>carrierPage</code> webview. The webview is
* ever expected to contain mobile setup extension URLs.
* @param {string} url The URL to embed into the carrierPage webview.
* @param
*/
setCarrierPage_: function(url) {
if (this.carrierPageUrl_ == url)
return;
this.carrierPageUrl_ = url;
$('carrierPage').src = url;
},
updateDeviceStatus_: function(deviceInfo) {
this.changeState_(deviceInfo);
},
sendDeviceInfo_: function() {
var msg = {
type: 'deviceInfoMsg',
domain: document.location,
payload: {
'carrier': this.deviceInfo_.carrier,
'MEID': this.deviceInfo_.MEID,
'IMEI': this.deviceInfo_.IMEI,
'MDN': this.deviceInfo_.MDN
}
};
$('portalFrameWebview')
.contentWindow.postMessage(msg, this.deviceInfo_.payment_url);
}
};
MobileSetup.drawProgress = function() {
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
var segmentCount = Math.min(12, canvas.width / 1.6); // Number of segments
var rotation = 0.75; // Counterclockwise rotation
// Rotate canvas over time
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI * 2 / (segmentCount + rotation));
ctx.translate(-canvas.width / 2, -canvas.height / 2);
var gap = canvas.width / 24; // Gap between segments
var oRadius = canvas.width / 2; // Outer radius
var iRadius = oRadius * 0.618; // Inner radius
var oCircumference = Math.PI * 2 * oRadius; // Outer circumference
var iCircumference = Math.PI * 2 * iRadius; // Inner circumference
var oGap = gap / oCircumference; // Gap size as fraction of outer ring
var iGap = gap / iCircumference; // Gap size as fraction of inner ring
var oArc = Math.PI * 2 * (1 / segmentCount - oGap); // Angle of outer arcs
var iArc = Math.PI * 2 * (1 / segmentCount - iGap); // Angle of inner arcs
for (i = 0; i < segmentCount; i++) { // Draw each segment
var opacity = Math.pow(1.0 - i / segmentCount, 3.0);
opacity = (0.15 + opacity * 0.8); // Vary from 0.15 to 0.95
var angle = -Math.PI * 2 * i / segmentCount;
ctx.beginPath();
ctx.arc(
canvas.width / 2, canvas.height / 2, oRadius, angle - oArc / 2,
angle + oArc / 2, false);
ctx.arc(
canvas.width / 2, canvas.height / 2, iRadius, angle + iArc / 2,
angle - iArc / 2, true);
ctx.closePath();
ctx.fillStyle = 'rgba(240, 30, 29, ' + opacity + ')';
ctx.fill();
}
};
MobileSetup.deviceStateChanged = function(deviceInfo) {
MobileSetup.getInstance().updateDeviceStatus_(deviceInfo);
};
MobileSetup.loadPage = function() {
mobile.MobileSetup.getInstance().initialize(
mobile.MobileSetup.ACTIVATION_PAGE_URL);
};
// Export
return {MobileSetup: MobileSetup};
});
document.addEventListener('DOMContentLoaded', mobile.MobileSetup.loadPage);