Merge m-c to inbound. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-09-22 15:51:51 -04:00
commit 6a5ec97f20
143 changed files with 4438 additions and 1514 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f3e998242fb9a857cf50f5bf3a02304a530ea617"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f3e998242fb9a857cf50f5bf3a02304a530ea617"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "a491e07757d721d4bea7302cfbb2b18460a3820d",
"revision": "36ec1e83a5bc8dc4106bd3fa1a506f1085611ba2",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f3e998242fb9a857cf50f5bf3a02304a530ea617"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3802009e1ab6c3ddfc3eb15522e3140a96b33336"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e1fd99454b6cd5da4f2c58f928fc04c6d03f478f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -697,12 +697,15 @@ pref("plugin.state.ciscowebcommunicator", 2);
pref("plugin.state.npmcafeemss", 2);
#endif
// Cisco VGConnect for directv.com, bug 981403
// Cisco VGConnect for directv.com, bug 981403 & bug 1051772
#ifdef XP_WIN
pref("plugin.state.npplayerplugin", 2);
#endif
#ifdef XP_MACOSX
pref("plugin.state.playerplugin", 2);
pref("plugin.state.playerplugin.dtv", 2);
pref("plugin.state.playerplugin.ciscodrm", 2);
pref("plugin.state.playerplugin.charter", 2);
#endif
// Cisco Jabber Client, bug 981905
@ -843,6 +846,15 @@ pref("plugin.state.personalplugin", 2);
pref("plugin.state.libplugins", 2);
#endif
// Novell iPrint Client, bug 1036693
#ifdef XP_WIN
pref("plugin.state.npnipp", 2);
pref("plugin.state.npnisp", 2);
#endif
#ifdef XP_MACOSX
pref("plugin.state.iprint", 2);
#endif
#ifdef XP_MACOSX
pref("browser.preferences.animateFadeIn", true);
#else

View File

@ -114,7 +114,7 @@ loop.Client = (function($) {
} else {
sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST;
}
this.mozLoop.hawkRequest(sessionType, "/call-url/", "POST",
{callerId: nickname},
function (error, responseText) {

View File

@ -406,6 +406,7 @@ loop.conversation = (function(OT, mozL10n) {
/*jshint newcap:false*/
this.loadReactComponent(sharedViews.ConversationView({
initiate: true,
sdk: OT,
model: this._conversation,
video: {enabled: videoStream}
@ -440,7 +441,8 @@ loop.conversation = (function(OT, mozL10n) {
});
this.loadReactComponent(sharedViews.FeedbackView({
feedbackApiClient: feedbackClient
feedbackApiClient: feedbackClient,
onAfterFeedbackReceived: window.close.bind(window)
}));
}
});

View File

@ -406,6 +406,7 @@ loop.conversation = (function(OT, mozL10n) {
/*jshint newcap:false*/
this.loadReactComponent(sharedViews.ConversationView({
initiate: true,
sdk: OT,
model: this._conversation,
video: {enabled: videoStream}
@ -440,7 +441,8 @@ loop.conversation = (function(OT, mozL10n) {
});
this.loadReactComponent(sharedViews.FeedbackView({
feedbackApiClient: feedbackClient
feedbackApiClient: feedbackClient,
onAfterFeedbackReceived: window.close.bind(window)
}));
}
});

View File

@ -51,7 +51,8 @@ loop.FeedbackAPIClient = (function($, _) {
"platform",
"version",
"channel",
"user_agent"],
"user_agent",
"url"],
/**
* Creates a formatted payload object compliant with the Feedback API spec

View File

@ -30,11 +30,12 @@ loop.shared.views = (function(_, OT, l10n) {
scope: React.PropTypes.string.isRequired,
type: React.PropTypes.string.isRequired,
action: React.PropTypes.func.isRequired,
enabled: React.PropTypes.bool.isRequired
enabled: React.PropTypes.bool.isRequired,
visible: React.PropTypes.bool.isRequired
},
getDefaultProps: function() {
return {enabled: true};
return {enabled: true, visible: true};
},
handleClick: function() {
@ -48,7 +49,8 @@ loop.shared.views = (function(_, OT, l10n) {
"btn": true,
"media-control": true,
"local-media": this.props.scope === "local",
"muted": !this.props.enabled
"muted": !this.props.enabled,
"hide": !this.props.visible
};
classesObj["btn-mute-" + this.props.type] = true;
return cx(classesObj);
@ -78,8 +80,8 @@ loop.shared.views = (function(_, OT, l10n) {
var ConversationToolbar = React.createClass({displayName: 'ConversationToolbar',
getDefaultProps: function() {
return {
video: {enabled: true},
audio: {enabled: true}
video: {enabled: true, visible: true},
audio: {enabled: true, visible: true}
};
},
@ -103,7 +105,7 @@ loop.shared.views = (function(_, OT, l10n) {
},
render: function() {
/* jshint ignore:start */
var cx = React.addons.classSet;
return (
React.DOM.ul({className: "conversation-toolbar"},
React.DOM.li({className: "conversation-toolbar-btn-box"},
@ -115,25 +117,31 @@ loop.shared.views = (function(_, OT, l10n) {
React.DOM.li({className: "conversation-toolbar-btn-box"},
MediaControlButton({action: this.handleToggleVideo,
enabled: this.props.video.enabled,
visible: this.props.video.visible,
scope: "local", type: "video"})
),
React.DOM.li({className: "conversation-toolbar-btn-box"},
MediaControlButton({action: this.handleToggleAudio,
enabled: this.props.audio.enabled,
visible: this.props.audio.visible,
scope: "local", type: "audio"})
)
)
);
/* jshint ignore:end */
}
});
/**
* Conversation view.
*/
var ConversationView = React.createClass({displayName: 'ConversationView',
mixins: [Backbone.Events],
propTypes: {
sdk: React.PropTypes.object.isRequired,
model: React.PropTypes.object.isRequired
video: React.PropTypes.object,
audio: React.PropTypes.object,
initiate: React.PropTypes.bool
},
// height set to 100%" to fix video layout on Google Chrome
@ -149,10 +157,11 @@ loop.shared.views = (function(_, OT, l10n) {
}
},
getInitialProps: function() {
getDefaultProps: function() {
return {
video: {enabled: true},
audio: {enabled: true}
initiate: true,
video: {enabled: true, visible: true},
audio: {enabled: true, visible: true}
};
},
@ -164,26 +173,30 @@ loop.shared.views = (function(_, OT, l10n) {
},
componentWillMount: function() {
this.publisherConfig.publishVideo = this.props.video.enabled;
if (this.props.initiate) {
this.publisherConfig.publishVideo = this.props.video.enabled;
}
},
componentDidMount: function() {
this.listenTo(this.props.model, "session:connected",
this.startPublishing);
this.listenTo(this.props.model, "session:stream-created",
this._streamCreated);
this.listenTo(this.props.model, ["session:peer-hungup",
"session:network-disconnected",
"session:ended"].join(" "),
this.stopPublishing);
this.props.model.startSession();
if (this.props.initiate) {
this.listenTo(this.props.model, "session:connected",
this.startPublishing);
this.listenTo(this.props.model, "session:stream-created",
this._streamCreated);
this.listenTo(this.props.model, ["session:peer-hungup",
"session:network-disconnected",
"session:ended"].join(" "),
this.stopPublishing);
this.props.model.startSession();
}
/**
* OT inserts inline styles into the markup. Using a listener for
* resize events helps us trigger a full width/height on the element
* so that they update to the correct dimensions.
* */
* XXX: this should be factored as a mixin.
*/
window.addEventListener('orientationchange', this.updateVideoContainer);
window.addEventListener('resize', this.updateVideoContainer);
},
@ -282,10 +295,12 @@ loop.shared.views = (function(_, OT, l10n) {
* Unpublishes local stream.
*/
stopPublishing: function() {
// Unregister listeners for publisher events
this.stopListening(this.publisher);
if (this.publisher) {
// Unregister listeners for publisher events
this.stopListening(this.publisher);
this.props.model.session.unpublish(this.publisher);
this.props.model.session.unpublish(this.publisher);
}
},
render: function() {
@ -362,7 +377,7 @@ loop.shared.views = (function(_, OT, l10n) {
return {category: "", description: ""};
},
getInitialProps: function() {
getDefaultProps: function() {
return {pending: false};
},
@ -467,8 +482,16 @@ loop.shared.views = (function(_, OT, l10n) {
/**
* Feedback received view.
*
* Props:
* - {Function} onAfterFeedbackReceived Function to execute after the
* WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed
*/
var FeedbackReceived = React.createClass({displayName: 'FeedbackReceived',
propTypes: {
onAfterFeedbackReceived: React.PropTypes.func
},
getInitialState: function() {
return {countdown: WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS};
},
@ -488,7 +511,9 @@ loop.shared.views = (function(_, OT, l10n) {
render: function() {
if (this.state.countdown < 1) {
clearInterval(this._timer);
window.close();
if (this.props.onAfterFeedbackReceived) {
this.props.onAfterFeedbackReceived();
}
}
return (
FeedbackLayout({title: l10n.get("feedback_thank_you_heading")},
@ -509,6 +534,7 @@ loop.shared.views = (function(_, OT, l10n) {
propTypes: {
// A loop.FeedbackAPIClient instance
feedbackApiClient: React.PropTypes.object.isRequired,
onAfterFeedbackReceived: React.PropTypes.func,
// The current feedback submission flow step name
step: React.PropTypes.oneOf(["start", "form", "finished"])
},
@ -517,7 +543,7 @@ loop.shared.views = (function(_, OT, l10n) {
return {pending: false, step: this.props.step || "start"};
},
getInitialProps: function() {
getDefaultProps: function() {
return {step: "start"};
},
@ -552,7 +578,10 @@ loop.shared.views = (function(_, OT, l10n) {
render: function() {
switch(this.state.step) {
case "finished":
return FeedbackReceived(null);
return (
FeedbackReceived({
onAfterFeedbackReceived: this.props.onAfterFeedbackReceived})
);
case "form":
return FeedbackForm({feedbackApiClient: this.props.feedbackApiClient,
sendFeedback: this.sendFeedback,

View File

@ -30,11 +30,12 @@ loop.shared.views = (function(_, OT, l10n) {
scope: React.PropTypes.string.isRequired,
type: React.PropTypes.string.isRequired,
action: React.PropTypes.func.isRequired,
enabled: React.PropTypes.bool.isRequired
enabled: React.PropTypes.bool.isRequired,
visible: React.PropTypes.bool.isRequired
},
getDefaultProps: function() {
return {enabled: true};
return {enabled: true, visible: true};
},
handleClick: function() {
@ -48,7 +49,8 @@ loop.shared.views = (function(_, OT, l10n) {
"btn": true,
"media-control": true,
"local-media": this.props.scope === "local",
"muted": !this.props.enabled
"muted": !this.props.enabled,
"hide": !this.props.visible
};
classesObj["btn-mute-" + this.props.type] = true;
return cx(classesObj);
@ -78,8 +80,8 @@ loop.shared.views = (function(_, OT, l10n) {
var ConversationToolbar = React.createClass({
getDefaultProps: function() {
return {
video: {enabled: true},
audio: {enabled: true}
video: {enabled: true, visible: true},
audio: {enabled: true, visible: true}
};
},
@ -103,7 +105,7 @@ loop.shared.views = (function(_, OT, l10n) {
},
render: function() {
/* jshint ignore:start */
var cx = React.addons.classSet;
return (
<ul className="conversation-toolbar">
<li className="conversation-toolbar-btn-box">
@ -115,25 +117,31 @@ loop.shared.views = (function(_, OT, l10n) {
<li className="conversation-toolbar-btn-box">
<MediaControlButton action={this.handleToggleVideo}
enabled={this.props.video.enabled}
visible={this.props.video.visible}
scope="local" type="video" />
</li>
<li className="conversation-toolbar-btn-box">
<MediaControlButton action={this.handleToggleAudio}
enabled={this.props.audio.enabled}
visible={this.props.audio.visible}
scope="local" type="audio" />
</li>
</ul>
);
/* jshint ignore:end */
}
});
/**
* Conversation view.
*/
var ConversationView = React.createClass({
mixins: [Backbone.Events],
propTypes: {
sdk: React.PropTypes.object.isRequired,
model: React.PropTypes.object.isRequired
video: React.PropTypes.object,
audio: React.PropTypes.object,
initiate: React.PropTypes.bool
},
// height set to 100%" to fix video layout on Google Chrome
@ -149,10 +157,11 @@ loop.shared.views = (function(_, OT, l10n) {
}
},
getInitialProps: function() {
getDefaultProps: function() {
return {
video: {enabled: true},
audio: {enabled: true}
initiate: true,
video: {enabled: true, visible: true},
audio: {enabled: true, visible: true}
};
},
@ -164,26 +173,30 @@ loop.shared.views = (function(_, OT, l10n) {
},
componentWillMount: function() {
this.publisherConfig.publishVideo = this.props.video.enabled;
if (this.props.initiate) {
this.publisherConfig.publishVideo = this.props.video.enabled;
}
},
componentDidMount: function() {
this.listenTo(this.props.model, "session:connected",
this.startPublishing);
this.listenTo(this.props.model, "session:stream-created",
this._streamCreated);
this.listenTo(this.props.model, ["session:peer-hungup",
"session:network-disconnected",
"session:ended"].join(" "),
this.stopPublishing);
this.props.model.startSession();
if (this.props.initiate) {
this.listenTo(this.props.model, "session:connected",
this.startPublishing);
this.listenTo(this.props.model, "session:stream-created",
this._streamCreated);
this.listenTo(this.props.model, ["session:peer-hungup",
"session:network-disconnected",
"session:ended"].join(" "),
this.stopPublishing);
this.props.model.startSession();
}
/**
* OT inserts inline styles into the markup. Using a listener for
* resize events helps us trigger a full width/height on the element
* so that they update to the correct dimensions.
* */
* XXX: this should be factored as a mixin.
*/
window.addEventListener('orientationchange', this.updateVideoContainer);
window.addEventListener('resize', this.updateVideoContainer);
},
@ -282,10 +295,12 @@ loop.shared.views = (function(_, OT, l10n) {
* Unpublishes local stream.
*/
stopPublishing: function() {
// Unregister listeners for publisher events
this.stopListening(this.publisher);
if (this.publisher) {
// Unregister listeners for publisher events
this.stopListening(this.publisher);
this.props.model.session.unpublish(this.publisher);
this.props.model.session.unpublish(this.publisher);
}
},
render: function() {
@ -362,7 +377,7 @@ loop.shared.views = (function(_, OT, l10n) {
return {category: "", description: ""};
},
getInitialProps: function() {
getDefaultProps: function() {
return {pending: false};
},
@ -467,8 +482,16 @@ loop.shared.views = (function(_, OT, l10n) {
/**
* Feedback received view.
*
* Props:
* - {Function} onAfterFeedbackReceived Function to execute after the
* WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS timeout has elapsed
*/
var FeedbackReceived = React.createClass({
propTypes: {
onAfterFeedbackReceived: React.PropTypes.func
},
getInitialState: function() {
return {countdown: WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS};
},
@ -488,7 +511,9 @@ loop.shared.views = (function(_, OT, l10n) {
render: function() {
if (this.state.countdown < 1) {
clearInterval(this._timer);
window.close();
if (this.props.onAfterFeedbackReceived) {
this.props.onAfterFeedbackReceived();
}
}
return (
<FeedbackLayout title={l10n.get("feedback_thank_you_heading")}>
@ -509,6 +534,7 @@ loop.shared.views = (function(_, OT, l10n) {
propTypes: {
// A loop.FeedbackAPIClient instance
feedbackApiClient: React.PropTypes.object.isRequired,
onAfterFeedbackReceived: React.PropTypes.func,
// The current feedback submission flow step name
step: React.PropTypes.oneOf(["start", "form", "finished"])
},
@ -517,7 +543,7 @@ loop.shared.views = (function(_, OT, l10n) {
return {pending: false, step: this.props.step || "start"};
},
getInitialProps: function() {
getDefaultProps: function() {
return {step: "start"};
},
@ -552,7 +578,10 @@ loop.shared.views = (function(_, OT, l10n) {
render: function() {
switch(this.state.step) {
case "finished":
return <FeedbackReceived />;
return (
<FeedbackReceived
onAfterFeedbackReceived={this.props.onAfterFeedbackReceived} />
);
case "form":
return <FeedbackForm feedbackApiClient={this.props.feedbackApiClient}
sendFeedback={this.sendFeedback}

View File

@ -13,6 +13,9 @@
# to the Gruntfile and getting rid of this Makefile entirely.
LOOP_SERVER_URL := $(shell echo $${LOOP_SERVER_URL-http://localhost:5000})
LOOP_FEEDBACK_API_URL := $(shell echo $${LOOP_FEEDBACK_API_URL-"https://input.allizom.org/api/v1/feedback"})
LOOP_FEEDBACK_PRODUCT_NAME := $(shell echo $${LOOP_FEEDBACK_PRODUCT_NAME-Loop})
NODE_LOCAL_BIN=./node_modules/.bin
install: npm_install tos
@ -67,4 +70,6 @@ remove_old_config:
config:
@echo "var loop = loop || {};" > content/config.js
@echo "loop.config = loop.config || {};" >> content/config.js
@echo "loop.config.serverUrl = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
@echo "loop.config.serverUrl = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
@echo "loop.config.feedbackApiUrl = '`echo $(LOOP_FEEDBACK_API_URL)`';" >> content/config.js
@echo "loop.config.feedbackProductName = '`echo $(LOOP_FEEDBACK_PRODUCT_NAME)`';" >> content/config.js

View File

@ -29,8 +29,12 @@ appropriate configuration file:
- `LOOP_SERVER_URL` defines the root url of the loop server, without trailing
slash (default: `http://localhost:5000`).
- `LOOP_PENDING_CALL_TIMEOUT` defines the amount of time a pending outgoing call
should be considered timed out, in milliseconds (default: `20000`).
- `LOOP_FEEDBACK_API_URL` sets the root URL for the
[input API](https://input.mozilla.org/); defaults to the input stage server
(https://input.allizom.org/api/v1/feedback). **Don't forget to set this
value to the production server URL when deploying to production.**
- `LOOP_FEEDBACK_PRODUCT_NAME` defines the product name to be sent to the input
API (defaults: Loop).
Usage
-----

View File

@ -207,3 +207,41 @@ body,
flex: 1;
}
/**
* Feedback form overlay (standalone only)
*/
.standalone .ended-conversation {
position: relative;
height: 100%;
background-color: #444;
text-align: left; /* as backup */
text-align: start;
}
.standalone .ended-conversation .feedback {
position: absolute;
width: 50%;
max-width: 400px;
margin: 10px auto;
top: 20px;
left: 10%;
right: 10%;
background: #FFF;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.4);
border-radius: 3px;
z-index: 1002; /* ensures the form is always on top of the control bar */
}
.standalone .ended-conversation .local-stream {
/* Hide local media stream when feedback form is shown. */
display: none;
}
@media screen and (max-width:640px) {
.standalone .ended-conversation .feedback {
width: 92%;
top: 10%;
left: 5px;
right: 5px;
}
}

View File

@ -41,6 +41,7 @@
<script type="text/javascript" src="shared/js/models.js"></script>
<script type="text/javascript" src="shared/js/mixins.js"></script>
<script type="text/javascript" src="shared/js/views.js"></script>
<script type="text/javascript" src="shared/js/feedbackApiClient.js"></script>
<script type="text/javascript" src="shared/js/websocket.js"></script>
<script type="text/javascript" src="js/standaloneClient.js"></script>
<script type="text/javascript" src="js/webapp.js"></script>

View File

@ -122,7 +122,7 @@ loop.StandaloneClient = (function($) {
try {
cb(null, this._validate(sessionData, expectedCallsProperties));
} catch (err) {
console.log("Error requesting call info", err);
console.error("Error requesting call info", err.message);
cb(err);
}
}.bind(this));

View File

@ -5,7 +5,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global loop:true, React */
/* jshint newcap:false */
/* jshint newcap:false, maxlen:false */
var loop = loop || {};
loop.webapp = (function($, _, OT, mozL10n) {
@ -17,12 +17,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
var sharedModels = loop.shared.models,
sharedViews = loop.shared.views;
/**
* App router.
* @type {loop.webapp.WebappRouter}
*/
var router;
/**
* Homepage view.
*/
@ -30,7 +24,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
render: function() {
return (
React.DOM.p(null, mozL10n.get("welcome"))
)
);
}
});
@ -104,7 +98,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
render: function() {
/* jshint ignore:start */
return (
React.DOM.div({className: "expired-url-info"},
React.DOM.div({className: "info-panel"},
@ -115,7 +108,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
PromoteFirefoxView({helper: this.props.helper})
)
);
/* jshint ignore:end */
}
});
@ -146,7 +138,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
});
return (
/* jshint ignore:start */
React.DOM.header({className: "standalone-header header-box container-box"},
ConversationBranding(null),
React.DOM.div({className: "loop-logo", title: "Firefox WebRTC! logo"}),
@ -157,7 +148,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
callUrlCreationDateString
)
)
/* jshint ignore:end */
);
}
});
@ -176,7 +166,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
getInitialState: function() {
return {
callState: this.props.callState || "connecting"
}
};
},
propTypes: {
@ -200,7 +190,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
render: function() {
var callState = mozL10n.get("call_progress_" + this.state.callState + "_description");
return (
/* jshint ignore:start */
React.DOM.div({className: "container"},
React.DOM.div({className: "container-box"},
React.DOM.header({className: "pending-header header-box"},
@ -229,7 +218,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
ConversationFooter(null)
)
/* jshint ignore:end */
);
}
});
@ -237,18 +225,21 @@ loop.webapp = (function($, _, OT, mozL10n) {
/**
* Conversation launcher view. A ConversationModel is associated and attached
* as a `model` property.
*
* Required properties:
* - {loop.shared.models.ConversationModel} model Conversation model.
* - {loop.shared.models.NotificationCollection} notifications
*/
var StartConversationView = React.createClass({displayName: 'StartConversationView',
/**
* Constructor.
*
* Required options:
* - {loop.shared.models.ConversationModel} model Conversation model.
* - {loop.shared.models.NotificationCollection} notifications
*
*/
propTypes: {
model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
// XXX Check more tightly here when we start injecting window.loop.*
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
getInitialProps: function() {
getDefaultProps: function() {
return {showCallOptionsMenu: false};
},
@ -260,14 +251,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
};
},
propTypes: {
model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
// XXX Check more tightly here when we start injecting window.loop.*
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
componentDidMount: function() {
// Listen for events & hide dropdown menu if user clicks away
window.addEventListener("click", this.clickHandler);
@ -348,7 +331,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
});
return (
/* jshint ignore:start */
React.DOM.div({className: "container"},
React.DOM.div({className: "container-box"},
@ -407,7 +389,37 @@ loop.webapp = (function($, _, OT, mozL10n) {
ConversationFooter(null)
)
/* jshint ignore:end */
);
}
});
/**
* Ended conversation view.
*/
var EndedConversationView = React.createClass({displayName: 'EndedConversationView',
propTypes: {
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
sdk: React.PropTypes.object.isRequired,
feedbackApiClient: React.PropTypes.object.isRequired,
onAfterFeedbackReceived: React.PropTypes.func.isRequired
},
render: function() {
return (
React.DOM.div({className: "ended-conversation"},
sharedViews.FeedbackView({
feedbackApiClient: this.props.feedbackApiClient,
onAfterFeedbackReceived: this.props.onAfterFeedbackReceived}
),
sharedViews.ConversationView({
initiate: false,
sdk: this.props.sdk,
model: this.props.conversation,
audio: {enabled: false, visible: false},
video: {enabled: false, visible: false}}
)
)
);
}
});
@ -426,7 +438,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
sdk: React.PropTypes.object.isRequired
sdk: React.PropTypes.object.isRequired,
feedbackApiClient: React.PropTypes.object.isRequired
},
getInitialState: function() {
@ -450,13 +463,23 @@ loop.webapp = (function($, _, OT, mozL10n) {
this.props.conversation.off(null, null, this);
},
shouldComponentUpdate: function(nextProps, nextState) {
// Only rerender if current state has actually changed
return nextState.callStatus !== this.state.callStatus;
},
callStatusSwitcher: function(status) {
return function() {
this.setState({callStatus: status});
}.bind(this);
},
/**
* Renders the conversation views.
*/
render: function() {
switch (this.state.callStatus) {
case "failure":
case "end":
case "start": {
return (
StartConversationView({
@ -472,19 +495,30 @@ loop.webapp = (function($, _, OT, mozL10n) {
case "connected": {
return (
sharedViews.ConversationView({
initiate: true,
sdk: this.props.sdk,
model: this.props.conversation,
video: {enabled: this.props.conversation.hasVideoStream("outgoing")}}
)
);
}
case "end": {
return (
EndedConversationView({
sdk: this.props.sdk,
conversation: this.props.conversation,
feedbackApiClient: this.props.feedbackApiClient,
onAfterFeedbackReceived: this.callStatusSwitcher("start")}
)
);
}
case "expired": {
return (
CallUrlExpiredView({helper: this.props.helper})
);
}
default: {
return HomeView(null)
return HomeView(null);
}
}
},
@ -494,7 +528,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
* @param {{code: number, message: string}} error
*/
_notifyError: function(error) {
console.log(error);
console.error(error);
this.props.notifications.errorL10n("connection_error_see_console_notification");
this.setState({callStatus: "end"});
},
@ -628,13 +662,15 @@ loop.webapp = (function($, _, OT, mozL10n) {
* @param {String} reason The reason the call was terminated.
*/
_handleCallTerminated: function(reason) {
this.setState({callStatus: "end"});
// For reasons other than cancel, display some notification text.
if (reason !== "cancel") {
// XXX This should really display the call failed view - bug 1046959
// will implement this.
this.props.notifications.errorL10n("call_timeout_notification_text");
}
// redirects the user to the call start view
// XXX should switch callStatus to failed for specific reasons when we
// get the call failed view; for now, switch back to start.
this.setState({callStatus: "start"});
},
/**
@ -657,7 +693,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
sdk: React.PropTypes.object.isRequired
sdk: React.PropTypes.object.isRequired,
feedbackApiClient: React.PropTypes.object.isRequired
},
getInitialState: function() {
@ -679,7 +716,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
conversation: this.props.conversation,
helper: this.props.helper,
notifications: this.props.notifications,
sdk: this.props.sdk}
sdk: this.props.sdk,
feedbackApiClient: this.props.feedbackApiClient}
)
);
} else {
@ -721,6 +759,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
var conversation = new sharedModels.ConversationModel({}, {
sdk: OT
});
var feedbackApiClient = new loop.FeedbackAPIClient(
loop.config.feedbackApiUrl, {
product: loop.config.feedbackProductName,
user_agent: navigator.userAgent,
url: document.location.origin
});
// Obtain the loopToken and pass it to the conversation
var locationHash = helper.locationHash();
@ -733,7 +777,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
conversation: conversation,
helper: helper,
notifications: notifications,
sdk: OT}
sdk: OT,
feedbackApiClient: feedbackApiClient}
), document.querySelector("#main"));
// Set the 'lang' and 'dir' attributes to <html> when the page is translated
@ -746,6 +791,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
PendingConversationView: PendingConversationView,
StartConversationView: StartConversationView,
OutgoingConversationView: OutgoingConversationView,
EndedConversationView: EndedConversationView,
HomeView: HomeView,
UnsupportedBrowserView: UnsupportedBrowserView,
UnsupportedDeviceView: UnsupportedDeviceView,

View File

@ -5,7 +5,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global loop:true, React */
/* jshint newcap:false */
/* jshint newcap:false, maxlen:false */
var loop = loop || {};
loop.webapp = (function($, _, OT, mozL10n) {
@ -17,12 +17,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
var sharedModels = loop.shared.models,
sharedViews = loop.shared.views;
/**
* App router.
* @type {loop.webapp.WebappRouter}
*/
var router;
/**
* Homepage view.
*/
@ -30,7 +24,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
render: function() {
return (
<p>{mozL10n.get("welcome")}</p>
)
);
}
});
@ -104,7 +98,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
},
render: function() {
/* jshint ignore:start */
return (
<div className="expired-url-info">
<div className="info-panel">
@ -115,7 +108,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
<PromoteFirefoxView helper={this.props.helper} />
</div>
);
/* jshint ignore:end */
}
});
@ -146,7 +138,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
});
return (
/* jshint ignore:start */
<header className="standalone-header header-box container-box">
<ConversationBranding />
<div className="loop-logo" title="Firefox WebRTC! logo"></div>
@ -157,7 +148,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
{callUrlCreationDateString}
</h4>
</header>
/* jshint ignore:end */
);
}
});
@ -176,7 +166,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
getInitialState: function() {
return {
callState: this.props.callState || "connecting"
}
};
},
propTypes: {
@ -200,7 +190,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
render: function() {
var callState = mozL10n.get("call_progress_" + this.state.callState + "_description");
return (
/* jshint ignore:start */
<div className="container">
<div className="container-box">
<header className="pending-header header-box">
@ -229,7 +218,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
<ConversationFooter />
</div>
/* jshint ignore:end */
);
}
});
@ -237,18 +225,21 @@ loop.webapp = (function($, _, OT, mozL10n) {
/**
* Conversation launcher view. A ConversationModel is associated and attached
* as a `model` property.
*
* Required properties:
* - {loop.shared.models.ConversationModel} model Conversation model.
* - {loop.shared.models.NotificationCollection} notifications
*/
var StartConversationView = React.createClass({
/**
* Constructor.
*
* Required options:
* - {loop.shared.models.ConversationModel} model Conversation model.
* - {loop.shared.models.NotificationCollection} notifications
*
*/
propTypes: {
model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
// XXX Check more tightly here when we start injecting window.loop.*
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
getInitialProps: function() {
getDefaultProps: function() {
return {showCallOptionsMenu: false};
},
@ -260,14 +251,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
};
},
propTypes: {
model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
// XXX Check more tightly here when we start injecting window.loop.*
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
componentDidMount: function() {
// Listen for events & hide dropdown menu if user clicks away
window.addEventListener("click", this.clickHandler);
@ -348,7 +331,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
});
return (
/* jshint ignore:start */
<div className="container">
<div className="container-box">
@ -407,7 +389,37 @@ loop.webapp = (function($, _, OT, mozL10n) {
<ConversationFooter />
</div>
/* jshint ignore:end */
);
}
});
/**
* Ended conversation view.
*/
var EndedConversationView = React.createClass({
propTypes: {
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
sdk: React.PropTypes.object.isRequired,
feedbackApiClient: React.PropTypes.object.isRequired,
onAfterFeedbackReceived: React.PropTypes.func.isRequired
},
render: function() {
return (
<div className="ended-conversation">
<sharedViews.FeedbackView
feedbackApiClient={this.props.feedbackApiClient}
onAfterFeedbackReceived={this.props.onAfterFeedbackReceived}
/>
<sharedViews.ConversationView
initiate={false}
sdk={this.props.sdk}
model={this.props.conversation}
audio={{enabled: false, visible: false}}
video={{enabled: false, visible: false}}
/>
</div>
);
}
});
@ -426,7 +438,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
sdk: React.PropTypes.object.isRequired
sdk: React.PropTypes.object.isRequired,
feedbackApiClient: React.PropTypes.object.isRequired
},
getInitialState: function() {
@ -450,13 +463,23 @@ loop.webapp = (function($, _, OT, mozL10n) {
this.props.conversation.off(null, null, this);
},
shouldComponentUpdate: function(nextProps, nextState) {
// Only rerender if current state has actually changed
return nextState.callStatus !== this.state.callStatus;
},
callStatusSwitcher: function(status) {
return function() {
this.setState({callStatus: status});
}.bind(this);
},
/**
* Renders the conversation views.
*/
render: function() {
switch (this.state.callStatus) {
case "failure":
case "end":
case "start": {
return (
<StartConversationView
@ -472,19 +495,30 @@ loop.webapp = (function($, _, OT, mozL10n) {
case "connected": {
return (
<sharedViews.ConversationView
initiate={true}
sdk={this.props.sdk}
model={this.props.conversation}
video={{enabled: this.props.conversation.hasVideoStream("outgoing")}}
/>
);
}
case "end": {
return (
<EndedConversationView
sdk={this.props.sdk}
conversation={this.props.conversation}
feedbackApiClient={this.props.feedbackApiClient}
onAfterFeedbackReceived={this.callStatusSwitcher("start")}
/>
);
}
case "expired": {
return (
<CallUrlExpiredView helper={this.props.helper} />
);
}
default: {
return <HomeView />
return <HomeView />;
}
}
},
@ -494,7 +528,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
* @param {{code: number, message: string}} error
*/
_notifyError: function(error) {
console.log(error);
console.error(error);
this.props.notifications.errorL10n("connection_error_see_console_notification");
this.setState({callStatus: "end"});
},
@ -628,13 +662,15 @@ loop.webapp = (function($, _, OT, mozL10n) {
* @param {String} reason The reason the call was terminated.
*/
_handleCallTerminated: function(reason) {
this.setState({callStatus: "end"});
// For reasons other than cancel, display some notification text.
if (reason !== "cancel") {
// XXX This should really display the call failed view - bug 1046959
// will implement this.
this.props.notifications.errorL10n("call_timeout_notification_text");
}
// redirects the user to the call start view
// XXX should switch callStatus to failed for specific reasons when we
// get the call failed view; for now, switch back to start.
this.setState({callStatus: "start"});
},
/**
@ -657,7 +693,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper: React.PropTypes.instanceOf(WebappHelper).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
sdk: React.PropTypes.object.isRequired
sdk: React.PropTypes.object.isRequired,
feedbackApiClient: React.PropTypes.object.isRequired
},
getInitialState: function() {
@ -680,6 +717,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper={this.props.helper}
notifications={this.props.notifications}
sdk={this.props.sdk}
feedbackApiClient={this.props.feedbackApiClient}
/>
);
} else {
@ -721,6 +759,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
var conversation = new sharedModels.ConversationModel({}, {
sdk: OT
});
var feedbackApiClient = new loop.FeedbackAPIClient(
loop.config.feedbackApiUrl, {
product: loop.config.feedbackProductName,
user_agent: navigator.userAgent,
url: document.location.origin
});
// Obtain the loopToken and pass it to the conversation
var locationHash = helper.locationHash();
@ -734,6 +778,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
helper={helper}
notifications={notifications}
sdk={OT}
feedbackApiClient={feedbackApiClient}
/>, document.querySelector("#main"));
// Set the 'lang' and 'dir' attributes to <html> when the page is translated
@ -746,6 +791,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
PendingConversationView: PendingConversationView,
StartConversationView: StartConversationView,
OutgoingConversationView: OutgoingConversationView,
EndedConversationView: EndedConversationView,
HomeView: HomeView,
UnsupportedBrowserView: UnsupportedBrowserView,
UnsupportedDeviceView: UnsupportedDeviceView,

View File

@ -46,3 +46,32 @@ clientShortname=WebRTC!
call_url_creation_date_label=(from {{call_url_creation_date}})
call_progress_connecting_description=Connecting…
call_progress_ringing_description=Ringing…
feedback_call_experience_heading2=How was your conversation?
feedback_what_makes_you_sad=What makes you sad?
feedback_thank_you_heading=Thank you for your feedback!
feedback_category_audio_quality=Audio quality
feedback_category_video_quality=Video quality
feedback_category_was_disconnected=Was disconnected
feedback_category_confusing=Confusing
feedback_category_other=Other:
feedback_custom_category_text_placeholder=What went wrong?
feedback_submit_button=Submit
feedback_back_button=Back
## LOCALIZATION NOTE (feedback_window_will_close_in2):
## Gaia l10n format; see https://github.com/mozilla-b2g/gaia/blob/f108c706fae43cd61628babdd9463e7695b2496e/apps/email/locales/email.en-US.properties#L387
## In this item, don't translate the part between {{..}}
feedback_window_will_close_in2={[ plural(countdown) ]}
feedback_window_will_close_in2[one] = This window will close in {{countdown}} second
feedback_window_will_close_in2[two] = This window will close in {{countdown}} seconds
feedback_window_will_close_in2[few] = This window will close in {{countdown}} seconds
feedback_window_will_close_in2[many] = This window will close in {{countdown}} seconds
feedback_window_will_close_in2[other] = This window will close in {{countdown}} seconds
## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
## a signed-in to signed-in user call.
## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#feedback
feedback_rejoin_button=Rejoin
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
## an abusive user.
feedback_report_user_button=Report User

View File

@ -7,17 +7,21 @@ var app = express();
var port = process.env.PORT || 3000;
var loopServerPort = process.env.LOOP_SERVER_PORT || 5000;
var feedbackApiUrl = process.env.LOOP_FEEDBACK_API_URL ||
"https://input.allizom.org/api/v1/feedback";
var feedbackProductName = process.env.LOOP_FEEDBACK_PRODUCT_NAME || "Loop";
function getConfigFile(req, res) {
"use strict";
res.set('Content-Type', 'text/javascript');
res.send(
"var loop = loop || {};" +
"loop.config = loop.config || {};" +
"loop.config.serverUrl = 'http://localhost:" + loopServerPort + "';" +
"loop.config.pendingCallTimeout = 20000;"
);
res.send([
"var loop = loop || {};",
"loop.config = loop.config || {};",
"loop.config.serverUrl = 'http://localhost:" + loopServerPort + "';",
"loop.config.feedbackApiUrl = '" + feedbackApiUrl + "';",
"loop.config.feedbackProductName = '" + feedbackProductName + "';",
].join("\n"));
}
app.get('/content/config.js', getConfigFile);

View File

@ -108,8 +108,7 @@ describe("loop.conversation", function() {
beforeEach(function() {
client = new loop.Client();
conversation = new loop.shared.models.ConversationModel({}, {
sdk: {},
pendingCallTimeout: 1000,
sdk: {}
});
sandbox.spy(conversation, "setIncomingSessionData");
sandbox.stub(conversation, "setOutgoingSessionData");

View File

@ -138,6 +138,13 @@ describe("loop.FeedbackAPIClient", function() {
expect(parsed.user_agent).eql("MOZAGENT");
});
it("should send url information when provided", function() {
client.send({url: "http://fake.invalid"}, function(){});
var parsed = JSON.parse(requests[0].requestBody);
expect(parsed.url).eql("http://fake.invalid");
});
it("should throw on invalid feedback data", function() {
expect(function() {
client.send("invalid data", function(){});

View File

@ -60,8 +60,7 @@ describe("loop.shared.router", function() {
conversation = new loop.shared.models.ConversationModel({
loopToken: "fakeToken"
}, {
sdk: {},
pendingCallTimeout: 1000
sdk: {}
});
});

View File

@ -187,13 +187,12 @@ describe("loop.shared.views", function() {
initSession: sandbox.stub().returns(fakeSession)
};
model = new sharedModels.ConversationModel(fakeSessionData, {
sdk: fakeSDK,
pendingCallTimeout: 1000
sdk: fakeSDK
});
});
describe("#componentDidMount", function() {
it("should start a session", function() {
it("should start a session by default", function() {
sandbox.stub(model, "startSession");
mountTestComponent({
@ -205,6 +204,19 @@ describe("loop.shared.views", function() {
sinon.assert.calledOnce(model.startSession);
});
it("shouldn't start a session if initiate is false", function() {
sandbox.stub(model, "startSession");
mountTestComponent({
initiate: false,
sdk: fakeSDK,
model: model,
video: {enabled: true}
});
sinon.assert.notCalled(model.startSession);
});
it("should set the correct stream publish options", function() {
var component = mountTestComponent({

View File

@ -36,6 +36,7 @@
<script src="../../content/shared/js/mixins.js"></script>
<script src="../../content/shared/js/views.js"></script>
<script src="../../content/shared/js/websocket.js"></script>
<script src="../../content/shared/js/feedbackApiClient.js"></script>
<script src="../../standalone/content/js/standaloneClient.js"></script>
<script src="../../standalone/content/js/webapp.js"></script>
<!-- Test scripts -->

View File

@ -13,11 +13,15 @@ describe("loop.webapp", function() {
var sharedModels = loop.shared.models,
sharedViews = loop.shared.views,
sandbox,
notifications;
notifications,
feedbackApiClient;
beforeEach(function() {
sandbox = sinon.sandbox.create();
notifications = new sharedModels.NotificationCollection();
feedbackApiClient = new loop.FeedbackAPIClient("http://invalid", {
product: "Loop"
});
});
afterEach(function() {
@ -31,6 +35,7 @@ describe("loop.webapp", function() {
sandbox.stub(React, "renderComponent");
sandbox.stub(loop.webapp.WebappHelper.prototype,
"locationHash").returns("#call/fake-Token");
loop.config.feedbackApiUrl = "http://fake.invalid";
conversationSetStub =
sandbox.stub(sharedModels.ConversationModel.prototype, "set");
});
@ -77,7 +82,8 @@ describe("loop.webapp", function() {
client: client,
conversation: conversation,
notifications: notifications,
sdk: {}
sdk: {},
feedbackApiClient: feedbackApiClient
});
});
@ -305,7 +311,7 @@ describe("loop.webapp", function() {
conversation.trigger("session:ended");
TestUtils.findRenderedComponentWithType(ocView,
loop.webapp.StartConversationView);
loop.webapp.EndedConversationView);
});
});
@ -314,7 +320,7 @@ describe("loop.webapp", function() {
conversation.trigger("session:peer-hungup");
TestUtils.findRenderedComponentWithType(ocView,
loop.webapp.StartConversationView);
loop.webapp.EndedConversationView);
});
it("should notify the user", function() {
@ -333,7 +339,7 @@ describe("loop.webapp", function() {
conversation.trigger("session:network-disconnected");
TestUtils.findRenderedComponentWithType(ocView,
loop.webapp.StartConversationView);
loop.webapp.EndedConversationView);
});
it("should notify the user", function() {
@ -474,8 +480,10 @@ describe("loop.webapp", function() {
loop.webapp.WebappRootView({
client: client,
helper: webappHelper,
notifications: notifications,
sdk: sdk,
conversation: conversationModel
conversation: conversationModel,
feedbackApiClient: feedbackApiClient
}));
}
@ -772,6 +780,32 @@ describe("loop.webapp", function() {
});
});
describe("EndedConversationView", function() {
var view, conversation;
beforeEach(function() {
conversation = new sharedModels.ConversationModel({}, {
sdk: {}
});
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.EndedConversationView({
conversation: conversation,
sdk: {},
feedbackApiClient: feedbackApiClient,
onAfterFeedbackReceived: function(){}
})
);
});
it("should render a ConversationView", function() {
TestUtils.findRenderedComponentWithType(view, sharedViews.ConversationView);
});
it("should render a FeedbackView", function() {
TestUtils.findRenderedComponentWithType(view, sharedViews.FeedbackView);
});
});
describe("PromoteFirefoxView", function() {
describe("#render", function() {
it("should not render when using Firefox", function() {

View File

@ -9,6 +9,10 @@
* @type {Object}
*/
navigator.mozL10n = document.mozL10n = {
initialize: function(){},
getDirection: function(){},
get: function(stringId, vars) {
// upcase the first letter

View File

@ -7,6 +7,7 @@
* @type {Object}
*/
navigator.mozLoop = {
ensureRegistered: function() {},
getLoopCharPref: function() {},
getLoopBoolPref: function() {}
};

View File

@ -37,7 +37,7 @@
.showcase > section {
position: relative;
padding-top: 12em;
padding-top: 14em;
clear: both;
}
@ -149,3 +149,9 @@
* When tokbox inserts the markup into the page the problem goes away */
bottom: auto;
}
.standalone .ended-conversation .remote_wrapper,
.standalone .video-layout-wrapper {
/* Removes the fake video image for ended conversations */
background: none;
}

View File

@ -23,6 +23,7 @@
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
var PendingConversationView = loop.webapp.PendingConversationView;
var StartConversationView = loop.webapp.StartConversationView;
var EndedConversationView = loop.webapp.EndedConversationView;
// 3. Shared components
var ConversationToolbar = loop.shared.views.ConversationToolbar;
@ -338,6 +339,19 @@
)
),
Section({name: "EndedConversationView"},
Example({summary: "Displays the feedback form"},
React.DOM.div({className: "standalone"},
EndedConversationView({sdk: mockSDK,
video: {enabled: true},
audio: {enabled: true},
conversation: mockConversationModel,
feedbackApiClient: stageFeedbackApiClient,
onAfterFeedbackReceived: noop})
)
)
),
Section({name: "AlertMessages"},
Example({summary: "Various alerts"},
React.DOM.div({className: "alert alert-warning"},

View File

@ -23,6 +23,7 @@
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
var PendingConversationView = loop.webapp.PendingConversationView;
var StartConversationView = loop.webapp.StartConversationView;
var EndedConversationView = loop.webapp.EndedConversationView;
// 3. Shared components
var ConversationToolbar = loop.shared.views.ConversationToolbar;
@ -338,6 +339,19 @@
</Example>
</Section>
<Section name="EndedConversationView">
<Example summary="Displays the feedback form">
<div className="standalone">
<EndedConversationView sdk={mockSDK}
video={{enabled: true}}
audio={{enabled: true}}
conversation={mockConversationModel}
feedbackApiClient={stageFeedbackApiClient}
onAfterFeedbackReceived={noop} />
</div>
</Example>
</Section>
<Section name="AlertMessages">
<Example summary="Various alerts">
<div className="alert alert-warning">

View File

@ -75,6 +75,8 @@ let UI = {
}
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
this.lastConnectedRuntime = Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
this.setupDeck();
},
@ -125,6 +127,7 @@ let UI = {
switch (what) {
case "runtimelist":
this.updateRuntimeList();
this.autoConnectRuntime();
break;
case "connection":
this.updateRuntimeButton();
@ -145,6 +148,7 @@ let UI = {
break;
case "runtime":
this.updateRuntimeButton();
this.saveLastConnectedRuntime();
break;
case "project-validated":
this.updateTitle();
@ -343,6 +347,34 @@ let UI = {
}
},
autoConnectRuntime: function () {
// Automatically reconnect to the previously selected runtime,
// if available and has an ID
if (AppManager.selectedRuntime || !this.lastConnectedRuntime) {
return;
}
let [_, type, id] = this.lastConnectedRuntime.match(/^(\w+):(.+)$/);
type = type.toLowerCase();
// Local connection is mapped to AppManager.runtimeList.custom array
if (type == "local") {
type = "custom";
}
// We support most runtimes except simulator, that needs to be manually
// launched
if (type == "usb" || type == "wifi" || type == "custom") {
for (let runtime of AppManager.runtimeList[type]) {
// Some runtimes do not expose getID function and don't support
// autoconnect (like remote connection)
if (typeof(runtime.getID) == "function" && runtime.getID() == id) {
this.connectToRuntime(runtime);
}
}
}
},
connectToRuntime: function(runtime) {
let name = runtime.getName();
let promise = AppManager.connectToRuntime(runtime);
@ -359,6 +391,17 @@ let UI = {
}
},
saveLastConnectedRuntime: function () {
if (AppManager.selectedRuntime &&
typeof(AppManager.selectedRuntime.getID) === "function") {
this.lastConnectedRuntime = AppManager.selectedRuntime.type + ":" + AppManager.selectedRuntime.getID();
} else {
this.lastConnectedRuntime = "";
}
Services.prefs.setCharPref("devtools.webide.lastConnectedRuntime",
this.lastConnectedRuntime);
},
/********** PROJECTS **********/
// Panel & button

View File

@ -651,7 +651,15 @@ exports.AppManager = AppManager = {
let r = new USBRuntime(id);
this.runtimeList.usb.push(r);
r.updateNameFromADB().then(
() => this.update("runtimelist"), () => {});
() => {
this.update("runtimelist");
// Also update the runtime button label, if the currently selected
// runtime name changes
if (r == this.selectedRuntime) {
this.update("runtime");
}
},
() => {});
}
this.update("runtimelist");
},

View File

@ -13,11 +13,21 @@ const promise = require("promise");
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
// These type strings are used for logging events to Telemetry
let RuntimeTypes = {
usb: "USB",
wifi: "WIFI",
simulator: "SIMULATOR",
remote: "REMOTE",
local: "LOCAL"
};
function USBRuntime(id) {
this.id = id;
}
USBRuntime.prototype = {
type: RuntimeTypes.usb,
connect: function(connection) {
let device = Devices.getByName(this.id);
if (!device) {
@ -59,6 +69,7 @@ function WiFiRuntime(deviceName) {
}
WiFiRuntime.prototype = {
type: RuntimeTypes.wifi,
connect: function(connection) {
let service = discovery.getRemoteService("devtools", this.deviceName);
if (!service) {
@ -82,6 +93,7 @@ function SimulatorRuntime(version) {
}
SimulatorRuntime.prototype = {
type: RuntimeTypes.simulator,
connect: function(connection) {
let port = ConnectionManager.getFreeTCPPort();
let simulator = Simulator.getByVersion(this.version);
@ -105,6 +117,7 @@ SimulatorRuntime.prototype = {
}
let gLocalRuntime = {
type: RuntimeTypes.local,
connect: function(connection) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
@ -118,9 +131,13 @@ let gLocalRuntime = {
getName: function() {
return Strings.GetStringFromName("local_runtime");
},
getID: function () {
return "local";
}
}
let gRemoteRuntime = {
type: RuntimeTypes.remote,
connect: function(connection) {
let win = Services.wm.getMostRecentWindow("devtools:webide");
if (!win) {

View File

@ -31,3 +31,4 @@ support-files =
[test_manifestUpdate.html]
[test_addons.html]
[test_deviceinfo.html]
[test_autoconnect_runtime.html]

View File

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title></title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
<script type="application/javascript;version=1.8" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<script type="application/javascript;version=1.8">
window.onload = function() {
SimpleTest.waitForExplicitFinish();
Task.spawn(function* () {
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
let win = yield openWebIDE();
let fakeRuntime = {
type: "USB",
connect: function(connection) {
ok(connection, win.AppManager.connection, "connection is valid");
connection.host = null; // force connectPipe
connection.connect();
return promise.resolve();
},
getID: function() {
return "fakeRuntime";
},
getName: function() {
return "fakeRuntime";
}
};
win.AppManager.runtimeList.usb.push(fakeRuntime);
win.AppManager.update("runtimelist");
let panelNode = win.document.querySelector("#runtime-panel");
let items = panelNode.querySelectorAll(".runtime-panel-item-usb");
is(items.length, 1, "Found one runtime button");
let deferred = promise.defer();
win.AppManager.connection.once(
win.Connection.Events.CONNECTED,
() => deferred.resolve());
items[0].click();
ok(win.document.querySelector("window").className, "busy", "UI is busy");
yield win.UI._busyPromise;
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
yield nextTick();
yield closeWebIDE(win);
is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected");
win = yield openWebIDE();
win.AppManager.runtimeList.usb.push(fakeRuntime);
win.AppManager.update("runtimelist");
yield waitForUpdate(win, "list-tabs-response");
is(Object.keys(DebuggerServer._connections).length, 1, "Automatically reconnected");
yield win.Cmds.disconnectRuntime();
yield closeWebIDE(win);
DebuggerServer.destroy();
SimpleTest.finish();
});
}
</script>
</body>
</html>

View File

@ -15,3 +15,4 @@ pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozil
pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
pref("devtools.webide.lastConnectedRuntime", "");

View File

@ -105,9 +105,9 @@ g.edgePath.param-connection {
fill: #4c9ed9; /* Select Highlight Blue */
}
/* Text in nodes */
/* Text in nodes and edges */
text {
cursor: pointer;
cursor: default; /* override the "text" cursor */
font-weight: 300;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
font-size: 14px;
@ -123,6 +123,10 @@ text {
fill: #f0f1f2; /* Toolbars */
}
.nodes text {
cursor: pointer;
}
/**
* Inspector Styles
*/

View File

@ -1,38 +1,78 @@
function check_ogg(v, enabled) {
function check_ogg(v, enabled, finish) {
function check(type, expected) {
is(v.canPlayType(type), enabled ? expected : "", type);
}
// Ogg types
check("video/ogg", "maybe");
check("audio/ogg", "maybe");
check("application/ogg", "maybe");
function basic_test() {
return new Promise(function(resolve, reject) {
// Ogg types
check("video/ogg", "maybe");
check("audio/ogg", "maybe");
check("application/ogg", "maybe");
// Supported Ogg codecs
check("audio/ogg; codecs=vorbis", "probably");
check("video/ogg; codecs=vorbis", "probably");
check("video/ogg; codecs=vorbis,theora", "probably");
check("video/ogg; codecs=\"vorbis, theora\"", "probably");
check("video/ogg; codecs=theora", "probably");
// Supported Ogg codecs
check("audio/ogg; codecs=vorbis", "probably");
check("video/ogg; codecs=vorbis", "probably");
check("video/ogg; codecs=vorbis,theora", "probably");
check("video/ogg; codecs=\"vorbis, theora\"", "probably");
check("video/ogg; codecs=theora", "probably");
resolve();
});
}
// Verify Opus support
var OpusEnabled = undefined;
try {
OpusEnabled = SpecialPowers.getBoolPref("media.opus.enabled");
} catch (ex) {
// SpecialPowers failed, perhaps because Opus isn't compiled in
console.log("media.opus.enabled pref not found; skipping Opus validation");
}
if (OpusEnabled !== undefined) {
SpecialPowers.setBoolPref("media.opus.enabled", true);
check("audio/ogg; codecs=opus", "probably");
SpecialPowers.setBoolPref("media.opus.enabled", false);
check("audio/ogg; codecs=opus", "");
SpecialPowers.setBoolPref("media.opus.enabled", OpusEnabled);
function verify_opus_support() {
return new Promise(function(resolve, reject) {
var OpusEnabled = undefined;
try {
OpusEnabled = SpecialPowers.getBoolPref("media.opus.enabled");
} catch (ex) {
// SpecialPowers failed, perhaps because Opus isn't compiled in
console.log("media.opus.enabled pref not found; skipping Opus validation");
}
if (OpusEnabled != undefined) {
resolve();
} else {
reject();
}
});
}
// Unsupported Ogg codecs
check("video/ogg; codecs=xyz", "");
check("video/ogg; codecs=xyz,vorbis", "");
check("video/ogg; codecs=vorbis,xyz", "");
function opus_enable() {
return new Promise(function(resolve, reject) {
SpecialPowers.pushPrefEnv({"set": [['media.opus.enabled', true]]},
function() {
check("audio/ogg; codecs=opus", "probably");
resolve();
});
});
}
function opus_disable() {
return new Promise(function(resolve, reject) {
SpecialPowers.pushPrefEnv({"set": [['media.opus.enabled', false]]},
function() {
check("audio/ogg; codecs=opus", "");
resolve();
});
});
}
function unspported_ogg() {
// Unsupported Ogg codecs
check("video/ogg; codecs=xyz", "");
check("video/ogg; codecs=xyz,vorbis", "");
check("video/ogg; codecs=vorbis,xyz", "");
finish.call();
}
basic_test()
.then(verify_opus_support)
.then(opus_enable)
.then(opus_disable)
.then(unspported_ogg, unspported_ogg);
}

View File

@ -22,7 +22,7 @@
# do ok(true, "Type not supported") and stop the test.
[DEFAULT]
skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug,b2g-desktop(bug 918299)
skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && toolkit != 'gonk') # b2g-desktop(bug 918299)
support-files =
320x240.ogv
320x240.ogv^headers^
@ -320,8 +320,8 @@ skip-if = buildapp == 'mulet' || os == 'win' # bug 894922
skip-if = buildapp == 'b2g' # bug 1021675
[test_can_play_type_no_ogg.html]
[test_can_play_type_ogg.html]
skip-if = buildapp == 'b2g' || e10s # b2g(bug 1021675)
[test_chaining.html]
skip-if = toolkit == 'gonk' && debug
[test_clone_media_element.html]
[test_closing_connections.html]
[test_constants.html]
@ -367,18 +367,24 @@ skip-if = toolkit == 'gonk' && !debug # bug 1021677
[test_mediarecorder_record_gum_video_timeslice.html]
skip-if = buildapp == 'b2g' || toolkit == 'android' # mimetype check, bug 969289
[test_mediarecorder_record_immediate_stop.html]
skip-if = toolkit == 'gonk' && debug
[test_mediarecorder_record_no_timeslice.html]
skip-if = toolkit == 'gonk' && debug
[test_mediarecorder_record_nosrc.html]
[test_mediarecorder_record_session.html]
[test_mediarecorder_record_startstopstart.html]
skip-if = toolkit == 'gonk' && debug
[test_mediarecorder_record_stopms.html]
[test_mediarecorder_record_timeslice.html]
skip-if = toolkit == 'gonk' && debug
[test_mediarecorder_reload_crash.html]
[test_mediarecorder_unsupported_src.html]
[test_mediarecorder_record_getdata_afterstart.html]
skip-if = toolkit == 'gonk' && debug
[test_mediatrack_consuming_mediaresource.html]
[test_mediatrack_consuming_mediastream.html]
[test_mediatrack_events.html]
skip-if = toolkit == 'gonk' && debug # bug 1065924
[test_mediatrack_parsing_ogg.html]
[test_mediatrack_replay_from_end.html]
[test_metadata.html]

View File

@ -24,11 +24,15 @@ a Bug 469247</a>
<script>
SimpleTest.waitForExplicitFinish();
function finish() {
mediaTestCleanup();
SimpleTest.finish();
}
SpecialPowers.pushPrefEnv({"set": [["media.ogg.enabled", false]]},
function() {
check_ogg(document.getElementById('v'), false);
mediaTestCleanup();
SimpleTest.finish();
check_ogg(document.getElementById('v'), false, finish);
}
);

View File

@ -21,9 +21,16 @@ a Bug 469247</a>
<pre id="test">
<script src="can_play_type_ogg.js"></script>
<script>
check_ogg(document.getElementById('v'), true);
mediaTestCleanup();
SimpleTest.waitForExplicitFinish();
function finish() {
mediaTestCleanup();
SimpleTest.finish();
}
check_ogg(document.getElementById('v'), true, finish);
</script>
</pre>
</body>

View File

@ -22,7 +22,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
// Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
// Shared code for AppsServiceChild.jsm, TrustedHostedAppsUtils.jsm,
// Webapps.jsm and Webapps.js
this.EXPORTED_SYMBOLS =
["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
@ -116,6 +117,84 @@ this.AppsUtils = {
return obj;
},
// Creates a nsILoadContext object with a given appId and isBrowser flag.
createLoadContext: function createLoadContext(aAppId, aIsBrowser) {
return {
associatedWindow: null,
topWindow : null,
appId: aAppId,
isInBrowserElement: aIsBrowser,
usePrivateBrowsing: false,
isContent: false,
isAppOfType: function(appType) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext,
Ci.nsIInterfaceRequestor,
Ci.nsISupports]),
getInterface: function(iid) {
if (iid.equals(Ci.nsILoadContext))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
},
// Sends data downloaded from aRequestChannel to a file
// identified by aId and aFileName.
getFile: function(aRequestChannel, aId, aFileName) {
let deferred = Promise.defer();
// Staging the file in TmpD until all the checks are done.
let file = FileUtils.getFile("TmpD", ["webapps", aId, aFileName], true);
// We need an output stream to write the channel content to the out file.
let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
// write, create, truncate
outputStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
let bufferedOutputStream =
Cc['@mozilla.org/network/buffered-output-stream;1']
.createInstance(Ci.nsIBufferedOutputStream);
bufferedOutputStream.init(outputStream, 1024);
// Create a listener that will give data to the file output stream.
let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
.createInstance(Ci.nsISimpleStreamListener);
listener.init(bufferedOutputStream, {
onStartRequest: function(aRequest, aContext) {
// Nothing to do there anymore.
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
bufferedOutputStream.close();
outputStream.close();
if (!Components.isSuccessCode(aStatusCode)) {
deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: true});
return;
}
// If we get a 4XX or a 5XX http status, bail out like if we had a
// network error.
let responseStatus = aRequestChannel.responseStatus;
if (responseStatus >= 400 && responseStatus <= 599) {
// unrecoverable error, don't bug the user
deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: false});
return;
}
deferred.resolve(file);
}
});
aRequestChannel.asyncOpen(listener, null);
return deferred.promise;
},
getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
debug("getAppByManifestURL " + aManifestURL);
// This could be O(1) if |webapps| was a dictionary indexed on manifestURL

View File

@ -48,7 +48,7 @@ this.PermissionsTable = { geolocation: {
},
camera: {
app: DENY_ACTION,
trusted: DENY_ACTION,
trusted: PROMPT_ACTION,
privileged: PROMPT_ACTION,
certified: ALLOW_ACTION
},
@ -99,28 +99,28 @@ this.PermissionsTable = { geolocation: {
},
"device-storage:pictures": {
app: DENY_ACTION,
trusted: DENY_ACTION,
trusted: PROMPT_ACTION,
privileged: PROMPT_ACTION,
certified: ALLOW_ACTION,
access: ["read", "write", "create"]
},
"device-storage:videos": {
app: DENY_ACTION,
trusted: DENY_ACTION,
trusted: PROMPT_ACTION,
privileged: PROMPT_ACTION,
certified: ALLOW_ACTION,
access: ["read", "write", "create"]
},
"device-storage:music": {
app: DENY_ACTION,
trusted: DENY_ACTION,
trusted: PROMPT_ACTION,
privileged: PROMPT_ACTION,
certified: ALLOW_ACTION,
access: ["read", "write", "create"]
},
"device-storage:sdcard": {
app: DENY_ACTION,
trusted: DENY_ACTION,
trusted: PROMPT_ACTION,
privileged: PROMPT_ACTION,
certified: ALLOW_ACTION,
access: ["read", "write", "create"]
@ -368,7 +368,7 @@ this.PermissionsTable = { geolocation: {
},
"audio-channel-publicnotification": {
app: DENY_ACTION,
trusted: DENY_ACTION,
trusted: ALLOW_ACTION,
privileged: DENY_ACTION,
certified: ALLOW_ACTION
},

View File

@ -16,6 +16,8 @@ const APP_TRUSTED_ROOTS= ["AppMarketplaceProdPublicRoot",
"AppMarketplaceDevPublicRoot",
"AppMarketplaceDevReviewersRoot",
"AppMarketplaceStageRoot",
"TrustedHostedAppPublicRoot",
"TrustedHostedAppTestRoot",
"AppXPCShellRoot"];
this.TrustedRootCertificate = {

View File

@ -2,17 +2,25 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global Components, Services, dump */
/* global Components, Services, dump, AppsUtils, NetUtil, XPCOMUtils */
"use strict";
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const signatureFileExtension = ".sig";
this.EXPORTED_SYMBOLS = ["TrustedHostedAppsUtils"];
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
#ifdef MOZ_WIDGET_ANDROID
// On Android, define the "debug" function as a binding of the "d" function
@ -32,8 +40,6 @@ let debug = Services.prefs.getBoolPref("dom.mozApps.debug") ?
/**
* Verification functions for Trusted Hosted Apps.
* (Manifest signature verification is in Webapps.jsm as part of
* regular signature verification.)
*/
this.TrustedHostedAppsUtils = {
@ -65,7 +71,8 @@ this.TrustedHostedAppsUtils = {
throw "CERTDB_ERROR";
}
if (siteSecurityService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP, uri.host, 0)) {
if (siteSecurityService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP,
uri.host, 0)) {
debug("\tvalid certificate pinning for host: " + uri.host + "\n");
return true;
}
@ -100,7 +107,7 @@ this.TrustedHostedAppsUtils = {
.forEach(aList => {
// aList[0] contains the directive name.
// aList[1..n] contains sources.
let directiveName = aList.shift()
let directiveName = aList.shift();
let sources = aList;
if ((-1 == validDirectives.indexOf(directiveName))) {
@ -144,5 +151,122 @@ this.TrustedHostedAppsUtils = {
}
return true;
},
_verifySignedFile: function(aManifestStream, aSignatureStream, aCertDb) {
let deferred = Promise.defer();
let root = Ci.nsIX509CertDB.TrustedHostedAppPublicRoot;
try {
// Check if we should use the test certificates.
// Please note that this should be changed if we ever allow chages to the
// prefs since that would create a way for an attacker to use the test
// root for real apps.
let useTrustedAppTestCerts = Services.prefs
.getBoolPref("dom.mozApps.use_trustedapp_test_certs");
if (useTrustedAppTestCerts) {
root = Ci.nsIX509CertDB.TrustedHostedAppTestRoot;
}
} catch (ex) { }
aCertDb.verifySignedManifestAsync(
root, aManifestStream, aSignatureStream,
function(aRv, aCert) {
debug("Signature verification returned code, cert & root: " + aRv + " " + aCert + " " + root);
if (Components.isSuccessCode(aRv)) {
deferred.resolve(aCert);
} else if (aRv == Cr.NS_ERROR_FILE_CORRUPTED ||
aRv == Cr.NS_ERROR_SIGNED_MANIFEST_FILE_INVALID) {
deferred.reject("MANIFEST_SIGNATURE_FILE_INVALID");
} else {
deferred.reject("MANIFEST_SIGNATURE_VERIFICATION_ERROR");
}
}
);
return deferred.promise;
},
verifySignedManifest: function(aApp, aAppId) {
let deferred = Promise.defer();
let certDb;
try {
certDb = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
} catch (e) {
debug("nsIX509CertDB error: " + e);
// unrecoverable error, don't bug the user
throw "CERTDB_ERROR";
}
let mRequestChannel = NetUtil.newChannel(aApp.manifestURL)
.QueryInterface(Ci.nsIHttpChannel);
mRequestChannel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
mRequestChannel.notificationCallbacks =
AppsUtils.createLoadContext(aAppId, false);
// The manifest signature must be located at the same path as the
// manifest and have the same file name, only the file extension
// should differ. Any fragment or query parameter will be ignored.
let signatureURL;
try {
let mURL = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(aApp.manifestURL, null, null)
.QueryInterface(Ci.nsIURL);
signatureURL = mURL.prePath +
mURL.directory + mURL.fileBaseName + signatureFileExtension;
} catch(e) {
deferred.reject("SIGNATURE_PATH_INVALID");
return;
}
let sRequestChannel = NetUtil.newChannel(signatureURL)
.QueryInterface(Ci.nsIHttpChannel);
sRequestChannel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
sRequestChannel.notificationCallbacks =
AppsUtils.createLoadContext(aAppId, false);
let getAsyncFetchCallback = (resolve, reject) =>
(aInputStream, aResult) => {
if (!Components.isSuccessCode(aResult)) {
debug("Failed to download file");
reject("MANIFEST_FILE_UNAVAILABLE");
return;
}
resolve(aInputStream);
};
Promise.all([
new Promise((resolve, reject) => {
NetUtil.asyncFetch(mRequestChannel,
getAsyncFetchCallback(resolve, reject));
}),
new Promise((resolve, reject) => {
NetUtil.asyncFetch(sRequestChannel,
getAsyncFetchCallback(resolve, reject));
})
]).then(([aManifestStream, aSignatureStream]) => {
this._verifySignedFile(aManifestStream, aSignatureStream, certDb)
.then(deferred.resolve, deferred.reject);
}, deferred.reject);
return deferred.promise;
},
verifyManifest: function(aData) {
return new Promise((resolve, reject) => {
// sanity check on manifest host's CA (proper CA check with
// pinning is done by regular networking code)
if (!this.isHostPinned(aData.app.manifestURL)) {
reject("TRUSTED_APPLICATION_HOST_CERTIFICATE_INVALID");
return;
}
if (!this.verifyCSPWhiteList(aData.app.manifest.csp)) {
reject("TRUSTED_APPLICATION_WHITELIST_VALIDATION_FAILED");
return;
}
this.verifySignedManifest(aData.app, aData.appId).then(resolve, reject);
});
}
};

View File

@ -2021,7 +2021,7 @@ this.DOMApplicationRegistry = {
xhr.setRequestHeader("If-None-Match", app.etag);
}
xhr.channel.notificationCallbacks =
this.createLoadContext(app.installerAppId, app.installerIsBrowser);
AppsUtils.createLoadContext(app.installerAppId, app.installerIsBrowser);
xhr.addEventListener("load", onload.bind(this, xhr, oldManifest), false);
xhr.addEventListener("error", (function() {
@ -2052,30 +2052,6 @@ this.DOMApplicationRegistry = {
});
},
// Creates a nsILoadContext object with a given appId and isBrowser flag.
createLoadContext: function createLoadContext(aAppId, aIsBrowser) {
return {
associatedWindow: null,
topWindow : null,
appId: aAppId,
isInBrowserElement: aIsBrowser,
usePrivateBrowsing: false,
isContent: false,
isAppOfType: function(appType) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext,
Ci.nsIInterfaceRequestor,
Ci.nsISupports]),
getInterface: function(iid) {
if (iid.equals(Ci.nsILoadContext))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
},
updatePackagedApp: Task.async(function*(aData, aId, aApp, aNewManifest) {
debug("updatePackagedApp");
@ -2316,24 +2292,16 @@ this.DOMApplicationRegistry = {
// in which case we don't need to load it.
if (app.manifest) {
if (checkManifest()) {
if (this.kTrustedHosted == this.appKind(app, app.manifest)) {
// sanity check on manifest host's CA
// (proper CA check with pinning is done by regular networking code)
if (!TrustedHostedAppsUtils.isHostPinned(app.manifestURL)) {
sendError("TRUSTED_APPLICATION_HOST_CERTIFICATE_INVALID");
return;
}
// Signature of the manifest should be verified here.
// Bug 1059216.
if (!TrustedHostedAppsUtils.verifyCSPWhiteList(app.manifest.csp)) {
sendError("TRUSTED_APPLICATION_WHITELIST_VALIDATION_FAILED");
return;
}
debug("Installed manifest check OK");
if (this.kTrustedHosted !== this.appKind(app, app.manifest)) {
installApp();
return;
}
installApp();
TrustedHostedAppsUtils.verifyManifest(aData)
.then(installApp, sendError);
} else {
debug("Installed manifest check failed");
// checkManifest() sends error before return
}
return;
}
@ -2342,8 +2310,8 @@ this.DOMApplicationRegistry = {
.createInstance(Ci.nsIXMLHttpRequest);
xhr.open("GET", app.manifestURL, true);
xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId,
aData.isBrowser);
xhr.channel.notificationCallbacks = AppsUtils.createLoadContext(aData.appId,
aData.isBrowser);
xhr.responseType = "json";
xhr.addEventListener("load", (function() {
@ -2356,21 +2324,20 @@ this.DOMApplicationRegistry = {
app.manifest = xhr.response;
if (checkManifest()) {
debug("Downloaded manifest check OK");
app.etag = xhr.getResponseHeader("Etag");
if (this.kTrustedHosted == this.appKind(app, app.manifest)) {
// checking trusted host for pinning is not needed here, since
// network code will have already done that
// Signature of the manifest should be verified here.
// Bug 1059216.
if (!TrustedHostedAppsUtils.verifyCSPWhiteList(app.manifest.csp)) {
sendError("TRUSTED_APPLICATION_WHITELIST_VALIDATION_FAILED");
return;
}
if (this.kTrustedHosted !== this.appKind(app, app.manifest)) {
installApp();
return;
}
installApp();
debug("App kind: " + this.kTrustedHosted);
TrustedHostedAppsUtils.verifyManifest(aData)
.then(installApp, sendError);
return;
} else {
debug("Downloaded manifest check failed");
// checkManifest() sends error before return
}
} else {
sendError("MANIFEST_URL_ERROR");
@ -2455,8 +2422,8 @@ this.DOMApplicationRegistry = {
.createInstance(Ci.nsIXMLHttpRequest);
xhr.open("GET", app.manifestURL, true);
xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId,
aData.isBrowser);
xhr.channel.notificationCallbacks = AppsUtils.createLoadContext(aData.appId,
aData.isBrowser);
xhr.responseType = "json";
xhr.addEventListener("load", (function() {
@ -3219,52 +3186,15 @@ this.DOMApplicationRegistry = {
_getPackage: function(aRequestChannel, aId, aOldApp, aNewApp) {
let deferred = Promise.defer();
// Staging the zip in TmpD until all the checks are done.
let zipFile =
FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
// We need an output stream to write the channel content to the zip file.
let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
// write, create, truncate
outputStream.init(zipFile, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
let bufferedOutputStream =
Cc['@mozilla.org/network/buffered-output-stream;1']
.createInstance(Ci.nsIBufferedOutputStream);
bufferedOutputStream.init(outputStream, 1024);
// Create a listener that will give data to the file output stream.
let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
.createInstance(Ci.nsISimpleStreamListener);
listener.init(bufferedOutputStream, {
onStartRequest: function(aRequest, aContext) {
// Nothing to do there anymore.
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
bufferedOutputStream.close();
outputStream.close();
if (!Components.isSuccessCode(aStatusCode)) {
deferred.reject("NETWORK_ERROR");
return;
}
// If we get a 4XX or a 5XX http status, bail out like if we had a
// network error.
let responseStatus = aRequestChannel.responseStatus;
if (responseStatus >= 400 && responseStatus <= 599) {
// unrecoverable error, don't bug the user
aOldApp.downloadAvailable = false;
deferred.reject("NETWORK_ERROR");
return;
}
deferred.resolve(zipFile);
AppsUtils.getFile(aRequestChannel, aId, "application.zip").then((aFile) => {
deferred.resolve(aFile);
}, function(rejectStatus) {
debug("Failed to download package file: " + rejectStatus.msg);
if (!rejectStatus.downloadAvailable) {
aOldApp.downloadAvailable = false;
}
deferred.reject(rejectStatus.msg);
});
aRequestChannel.asyncOpen(listener, null);
// send a first progress event to correctly set the DOM object's properties
this._sendDownloadProgressEvent(aNewApp, 0);

View File

@ -1220,7 +1220,9 @@ BluetoothHfpManager::Disconnect(BluetoothProfileController* aController)
if (!sBluetoothHfpInterface) {
BT_LOGR("sBluetoothHfpInterface is null");
aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
if (aController) {
aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
}
return;
}

View File

@ -1664,6 +1664,13 @@ DeviceStorageFile::GetStatus(nsAString& aStatus)
aStatus.AssignLiteral("unavailable");
return;
}
bool isUnmounting;
rv = vol->GetIsUnmounting(&isUnmounting);
NS_ENSURE_SUCCESS_VOID(rv);
if (isUnmounting) {
aStatus.AssignLiteral("unavailable");
return;
}
int32_t volState;
rv = vol->GetState(&volState);
NS_ENSURE_SUCCESS_VOID(rv);

View File

@ -1893,12 +1893,14 @@ ContentChild::RecvFileSystemUpdate(const nsString& aFsName,
const bool& aIsMediaPresent,
const bool& aIsSharing,
const bool& aIsFormatting,
const bool& aIsFake)
const bool& aIsFake,
const bool& aIsUnmounting)
{
#ifdef MOZ_WIDGET_GONK
nsRefPtr<nsVolume> volume = new nsVolume(aFsName, aVolumeName, aState,
aMountGeneration, aIsMediaPresent,
aIsSharing, aIsFormatting, aIsFake);
aIsSharing, aIsFormatting, aIsFake,
aIsUnmounting);
nsRefPtr<nsVolumeService> vs = nsVolumeService::GetSingleton();
if (vs) {
@ -1914,6 +1916,7 @@ ContentChild::RecvFileSystemUpdate(const nsString& aFsName,
unused << aIsSharing;
unused << aIsFormatting;
unused << aIsFake;
unused << aIsUnmounting;
#endif
return true;
}

View File

@ -312,7 +312,8 @@ public:
const bool& aIsMediaPresent,
const bool& aIsSharing,
const bool& aIsFormatting,
const bool& aIsFake) MOZ_OVERRIDE;
const bool& aIsFake,
const bool& aIsUnmounting) MOZ_OVERRIDE;
virtual bool RecvNuwaFork() MOZ_OVERRIDE;

View File

@ -2650,6 +2650,7 @@ ContentParent::Observe(nsISupports* aSubject,
bool isSharing;
bool isFormatting;
bool isFake;
bool isUnmounting;
vol->GetName(volName);
vol->GetMountPoint(mountPoint);
@ -2659,6 +2660,7 @@ ContentParent::Observe(nsISupports* aSubject,
vol->GetIsSharing(&isSharing);
vol->GetIsFormatting(&isFormatting);
vol->GetIsFake(&isFake);
vol->GetIsUnmounting(&isUnmounting);
#ifdef MOZ_NUWA_PROCESS
if (!(IsNuwaReady() && IsNuwaProcess()))
@ -2666,7 +2668,8 @@ ContentParent::Observe(nsISupports* aSubject,
{
unused << SendFileSystemUpdate(volName, mountPoint, state,
mountGeneration, isMediaPresent,
isSharing, isFormatting, isFake);
isSharing, isFormatting, isFake,
isUnmounting);
}
} else if (!strcmp(aTopic, "phone-state-changed")) {
nsString state(aData);

View File

@ -301,6 +301,7 @@ struct VolumeInfo {
bool isSharing;
bool isFormatting;
bool isFake;
bool isUnmounting;
};
union MaybeFileDesc {
@ -449,7 +450,7 @@ child:
// VolumeInfo above.
FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState,
int32_t mountGeneration, bool isMediaPresent,
bool isSharing, bool isFormatting, bool isFake);
bool isSharing, bool isFormatting, bool isFake, bool isUnmounting);
// Ask the Nuwa process to create a new child process.
NuwaFork();

View File

@ -25,6 +25,8 @@ const MOBILENETWORKINFO_CID =
Components.ID("{a6c8416c-09b4-46d1-bf29-6520d677d085}");
const MOBILECELLINFO_CID =
Components.ID("{0635d9ab-997e-4cdf-84e7-c1883752dff3}");
const TELEPHONYCALLBACK_CID =
Components.ID("{6e1af17e-37f3-11e4-aed3-60a44c237d2b}");
const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
@ -44,6 +46,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer",
"@mozilla.org/ril;1",
"nsIRadioInterfaceLayer");
XPCOMUtils.defineLazyServiceGetter(this, "gGonkTelephonyService",
"@mozilla.org/telephony/telephonyservice;1",
"nsIGonkTelephonyService");
let DEBUG = RIL.DEBUG_RIL;
function debug(s) {
dump("MobileConnectionService: " + s + "\n");
@ -134,6 +140,41 @@ MMIResult.prototype = {
additionalInformation: 'r'},
};
/**
* Wrap a MobileConnectionCallback to a TelephonyCallback.
*/
function TelephonyCallback(aCallback) {
this.callback = aCallback;
}
TelephonyCallback.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyCallback]),
classID: TELEPHONYCALLBACK_CID,
notifyDialMMI: function(mmiServiceCode) {
this.serviceCode = mmiServiceCode;
},
notifyDialMMISuccess: function(result) {
this.callback.notifySendCancelMmiSuccess(result);
},
notifyDialMMIError: function(error) {
this.callback.notifyError(error, "", this.serviceCode);
},
notifyDialMMIErrorWithInfo: function(error, info) {
this.callback.notifyError(error, "", this.serviceCode, info);
},
notifyDialError: function() {
throw Cr.NS_ERROR_UNEXPECTED;
},
notifyDialSuccess: function() {
throw Cr.NS_ERROR_UNEXPECTED;
},
};
function MobileConnectionProvider(aClientId, aRadioInterface) {
this._clientId = aClientId;
this._radioInterface = aRadioInterface;
@ -188,7 +229,7 @@ MobileConnectionProvider.prototype = {
key = "ro.telephony.default_network";
let indexString = libcutils.property_get(key, "");
let index = parseInt(indexString, 10);
if (DEBUG) this._debug("Fallback to " + key + ": " + index)
if (DEBUG) this._debug("Fallback to " + key + ": " + index);
let networkTypes = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[index];
supportedNetworkTypes = networkTypes ?
@ -382,10 +423,7 @@ MobileConnectionProvider.prototype = {
},
_rulesToCallForwardingOptions: function(aRules) {
for (let i = 0; i < aRules.length; i++) {
let info = new CallForwardingOptions(aRules[i]);
aRules[i] = info;
}
return aRules.map(rule => new CallForwardingOptions(rule));
},
_dispatchNotifyError: function(aCallback, aErrorMsg) {
@ -515,6 +553,13 @@ MobileConnectionProvider.prototype = {
this.deliverListenerEvent("notifyRadioStateChanged");
},
notifyCFStateChanged: function(aAction, aReason, aNumber, aTimeSeconds,
aServiceClass) {
this.deliverListenerEvent("notifyCFStateChanged",
[true, aAction, aReason, aNumber, aTimeSeconds,
aServiceClass]);
},
getSupportedNetworkTypes: function(aTypes) {
aTypes.value = this.supportedNetworkTypes.slice();
return aTypes.value.length;
@ -680,49 +725,8 @@ MobileConnectionProvider.prototype = {
},
sendMMI: function(aMmi, aCallback) {
this._radioInterface.sendWorkerMessage("sendMMI", {mmi: aMmi},
(function(aResponse) {
aResponse.serviceCode = aResponse.mmiServiceCode || "";
// We expect to have an IMEI at this point if the request was supposed
// to query for the IMEI, so getting a successful reply from the RIL
// without containing an actual IMEI number is considered an error.
if (aResponse.serviceCode === RIL.MMI_KS_SC_IMEI &&
!aResponse.statusMessage) {
aResponse.errorMsg = aResponse.errorMsg ||
RIL.GECKO_ERROR_GENERIC_FAILURE;
}
if (aResponse.errorMsg) {
if (aResponse.additionalInformation) {
aCallback.notifyError(aResponse.errorMsg, "",
aResponse.serviceCode,
aResponse.additionalInformation);
} else {
aCallback.notifyError(aResponse.errorMsg, "",
aResponse.serviceCode);
}
return false;
}
if (aResponse.isSetCallForward) {
this.deliverListenerEvent("notifyCFStateChanged",
[!aResponse.errorMsg, aResponse.action,
aResponse.reason, aResponse.number,
aResponse.timeSeconds, aResponse.serviceClass]);
}
// MMI query call forwarding options request returns a set of rules that
// will be exposed in the form of an array of MozCallForwardingOptions
// instances.
if (aResponse.serviceCode === RIL.MMI_KS_SC_CALL_FORWARDING &&
aResponse.additionalInformation) {
this._rulesToCallForwardingOptions(aResponse.additionalInformation);
}
let mmiResult = new MMIResult(aResponse);
aCallback.notifySendCancelMmiSuccess(mmiResult);
return false;
}).bind(this));
let telephonyCallback = new TelephonyCallback(aCallback);
gGonkTelephonyService.dialMMI(this._clientId, aMmi, telephonyCallback);
},
cancelMMI: function(aCallback) {
@ -762,11 +766,9 @@ MobileConnectionProvider.prototype = {
return false;
}
this.deliverListenerEvent("notifyCFStateChanged",
[!aResponse.errorMsg, aResponse.action,
aResponse.reason, aResponse.number,
aResponse.timeSeconds, aResponse.serviceClass]);
this.notifyCFStateChanged(aResponse.action, aResponse.reason,
aResponse.number, aResponse.timeSeconds,
aResponse.serviceClass);
aCallback.notifySuccess();
return false;
}).bind(this));
@ -786,9 +788,8 @@ MobileConnectionProvider.prototype = {
return false;
}
let infos = aResponse.rules;
this._rulesToCallForwardingOptions(infos);
aCallback.notifyGetCallForwardingSuccess(infos);
aCallback.notifyGetCallForwardingSuccess(
this._rulesToCallForwardingOptions(aResponse.rules));
return false;
}).bind(this));
},
@ -1216,6 +1217,17 @@ MobileConnectionService.prototype = {
provider.deliverListenerEvent("notifyLastKnownHomeNetworkChanged");
},
notifyCFStateChanged: function(aClientId, aAction, aReason, aNumber,
aTimeSeconds, aServiceClass) {
if (DEBUG) {
debug("notifyCFStateChanged for " + aClientId);
}
let provider = this.getItemByServiceId(aClientId);
provider.notifyCFStateChanged(aAction, aReason, aNumber, aTimeSeconds,
aServiceClass);
},
/**
* nsIObserver interface.
*/

View File

@ -9,7 +9,7 @@
"@mozilla.org/mobileconnection/gonkmobileconnectionservice;1"
%}
[scriptable, uuid(e54fa0a4-d357-48ef-9a1e-ffc9705b44b1)]
[scriptable, uuid(b0310517-e7f6-4fa5-a52e-fa6ff35c8fc1)]
interface nsIGonkMobileConnectionService : nsIMobileConnectionService
{
void notifyNetworkInfoChanged(in unsigned long clientId, in jsval networkInfo);
@ -46,4 +46,11 @@ interface nsIGonkMobileConnectionService : nsIMobileConnectionService
void notifyLastHomeNetworkChanged(in unsigned long clientId,
in DOMString network);
void notifyCFStateChanged(in unsigned long clientId,
in unsigned short action,
in unsigned short reason,
in DOMString number,
in unsigned short timeSeconds,
in unsigned short serviceClass);
};

View File

@ -33,7 +33,7 @@ function testInvalidMMICode() {
ok(true, MMI_CODE + " fail");
is(aError.name, "emMmiError", "MMI error name");
is(aError.message, "", "No message");
is(aError.serviceCode, "", "No serviceCode");
is(aError.serviceCode, "scUssd", "Service code USSD");
is(aError.additionalInformation, null, "No additional information");
});
}

View File

@ -86,7 +86,7 @@ NotificationStorage.prototype = {
timestamp: new Date().getTime(),
origin: origin,
data: data,
behavior: behavior
mozbehavior: behavior
};
this._notifications[id] = notification;
@ -207,7 +207,7 @@ NotificationStorage.prototype = {
notification.tag,
notification.icon,
notification.data,
notification.behavior);
notification.mozbehavior);
} catch (e) {
if (DEBUG) { debug("Error calling callback handle: " + e); }
}

View File

@ -909,14 +909,16 @@ AutoMounter::UpdateState()
break;
}
// Mark the volume as if we've started sharing. This will cause
// apps which watch device storage notifications to see the volume
// go into the shared state, and prompt them to close any open files
// that they might have.
// Mark the volume as if we've started sharing/formatting/unmmounting.
// This will cause apps which watch device storage notifications to see
// the volume go into the different state, and prompt them to close any
// open files that they might have.
if (tryToShare && vol->IsSharingEnabled()) {
vol->SetIsSharing(true);
} else if (vol->IsFormatRequested()){
vol->SetIsFormatting(true);
} else if (vol->IsUnmountRequested()){
vol->SetIsUnmounting(true);
}
// Check to see if there are any open files on the volume and

View File

@ -67,6 +67,7 @@ Volume::Volume(const nsCSubstring& aName)
mCanBeShared(true),
mIsSharing(false),
mIsFormatting(false),
mIsUnmounting(false),
mId(sNextId++)
{
DBG("Volume %s: created", NameStr());
@ -98,6 +99,18 @@ Volume::SetIsFormatting(bool aIsFormatting)
}
}
void
Volume::SetIsUnmounting(bool aIsUnmounting)
{
if (aIsUnmounting == mIsUnmounting) {
return;
}
mIsUnmounting = aIsUnmounting;
LOG("Volume %s: IsUnmounting set to %d state %s",
NameStr(), (int)mIsUnmounting, StateStr(mState));
mEventObserverList.Broadcast(this);
}
void
Volume::SetMediaPresent(bool aMediaPresent)
{
@ -201,17 +214,20 @@ Volume::SetState(Volume::STATE aNewState)
mIsSharing = false;
mUnmountRequested = false;
mMountRequested = false;
mIsUnmounting = false;
break;
case nsIVolume::STATE_MOUNTED:
mMountRequested = false;
mIsFormatting = false;
mIsSharing = false;
mIsUnmounting = false;
break;
case nsIVolume::STATE_FORMATTING:
mFormatRequested = false;
mIsFormatting = true;
mIsSharing = false;
mIsUnmounting = false;
break;
case nsIVolume::STATE_SHARED:
@ -221,6 +237,14 @@ Volume::SetState(Volume::STATE aNewState)
// it's conceivable that a volume could already be in a shared state
// when b2g starts.
mIsSharing = true;
mIsUnmounting = false;
mIsFormatting = false;
break;
case nsIVolume::STATE_UNMOUNTING:
mIsUnmounting = true;
mIsFormatting = false;
mIsSharing = false;
break;
case nsIVolume::STATE_IDLE:

View File

@ -58,6 +58,7 @@ public:
bool IsUnmountRequested() const { return CanBeMounted() && mUnmountRequested; }
bool IsSharing() const { return mIsSharing; }
bool IsFormatting() const { return mIsFormatting; }
bool IsUnmounting() const { return mIsUnmounting; }
void SetSharingEnabled(bool aSharingEnabled);
void SetFormatRequested(bool aFormatRequested);
@ -88,6 +89,7 @@ private:
void SetIsSharing(bool aIsSharing);
void SetIsFormatting(bool aIsFormatting);
void SetIsUnmounting(bool aIsUnmounting);
void SetState(STATE aNewState);
void SetMediaPresent(bool aMediaPresent);
void SetMountPoint(const nsCSubstring& aMountPoint);
@ -112,6 +114,7 @@ private:
bool mCanBeShared;
bool mIsSharing;
bool mIsFormatting;
bool mIsUnmounting;
uint32_t mId; // Unique ID (used by MTP)
static EventObserverList mEventObserverList;

View File

@ -5,7 +5,7 @@
#include "nsISupports.idl"
#include "nsIVolumeStat.idl"
[scriptable, uuid(13caa69c-8f1f-11e3-8e36-10bf48d707fb)]
[scriptable, uuid(9D0DC356-395D-11E4-9306-A97C1D5D46B0)]
interface nsIVolume : nsISupports
{
// These MUST match the states from android's system/vold/Volume.h header
@ -67,6 +67,8 @@ interface nsIVolume : nsISupports
// once the volume has been formatted and mounted again.
readonly attribute boolean isFormatting;
readonly attribute boolean isUnmounting;
nsIVolumeStat getStats();
// Formats the volume in IO thread, if the volume is ready to be formatted.

View File

@ -56,7 +56,8 @@ nsVolume::nsVolume(const Volume* aVolume)
mIsFake(false),
mIsMediaPresent(aVolume->MediaPresent()),
mIsSharing(aVolume->IsSharing()),
mIsFormatting(aVolume->IsFormatting())
mIsFormatting(aVolume->IsFormatting()),
mIsUnmounting(aVolume->IsUnmounting())
{
}
@ -110,33 +111,45 @@ bool nsVolume::Equals(nsIVolume* aVolume)
return false;
}
bool isUnmounting;
aVolume->GetIsUnmounting(&isUnmounting);
if (mIsUnmounting != isUnmounting) {
return false;
}
return true;
}
NS_IMETHODIMP nsVolume::GetIsMediaPresent(bool *aIsMediaPresent)
NS_IMETHODIMP nsVolume::GetIsMediaPresent(bool* aIsMediaPresent)
{
*aIsMediaPresent = mIsMediaPresent;
return NS_OK;
}
NS_IMETHODIMP nsVolume::GetIsMountLocked(bool *aIsMountLocked)
NS_IMETHODIMP nsVolume::GetIsMountLocked(bool* aIsMountLocked)
{
*aIsMountLocked = mMountLocked;
return NS_OK;
}
NS_IMETHODIMP nsVolume::GetIsSharing(bool *aIsSharing)
NS_IMETHODIMP nsVolume::GetIsSharing(bool* aIsSharing)
{
*aIsSharing = mIsSharing;
return NS_OK;
}
NS_IMETHODIMP nsVolume::GetIsFormatting(bool *aIsFormatting)
NS_IMETHODIMP nsVolume::GetIsFormatting(bool* aIsFormatting)
{
*aIsFormatting = mIsFormatting;
return NS_OK;
}
NS_IMETHODIMP nsVolume::GetIsUnmounting(bool* aIsUnmounting)
{
*aIsUnmounting = mIsUnmounting;
return NS_OK;
}
NS_IMETHODIMP nsVolume::GetName(nsAString& aName)
{
aName = mName;
@ -262,11 +275,11 @@ nsVolume::LogState() const
{
if (mState == nsIVolume::STATE_MOUNTED) {
LOG("nsVolume: %s state %s @ '%s' gen %d locked %d fake %d "
"media %d sharing %d formatting %d",
"media %d sharing %d formatting %d unmounting %d",
NameStr().get(), StateStr(), MountPointStr().get(),
MountGeneration(), (int)IsMountLocked(), (int)IsFake(),
(int)IsMediaPresent(), (int)IsSharing(),
(int)IsFormatting());
(int)IsFormatting(), (int)IsUnmounting());
return;
}
@ -284,6 +297,7 @@ void nsVolume::Set(nsIVolume* aVolume)
aVolume->GetIsMediaPresent(&mIsMediaPresent);
aVolume->GetIsSharing(&mIsSharing);
aVolume->GetIsFormatting(&mIsFormatting);
aVolume->GetIsUnmounting(&mIsUnmounting);
int32_t volMountGeneration;
aVolume->GetMountGeneration(&volMountGeneration);

View File

@ -30,7 +30,8 @@ public:
nsVolume(const nsAString& aName, const nsAString& aMountPoint,
const int32_t& aState, const int32_t& aMountGeneration,
const bool& aIsMediaPresent, const bool& aIsSharing,
const bool& aIsFormatting, const bool& aIsFake)
const bool& aIsFormatting, const bool& aIsFake,
const bool& aIsUnmounting)
: mName(aName),
mMountPoint(aMountPoint),
mState(aState),
@ -39,7 +40,8 @@ public:
mIsFake(aIsFake),
mIsMediaPresent(aIsMediaPresent),
mIsSharing(aIsSharing),
mIsFormatting(aIsFormatting)
mIsFormatting(aIsFormatting),
mIsUnmounting(aIsUnmounting)
{
}
@ -53,7 +55,8 @@ public:
mIsFake(false),
mIsMediaPresent(false),
mIsSharing(false),
mIsFormatting(false)
mIsFormatting(false),
mIsUnmounting(false)
{
}
@ -78,6 +81,7 @@ public:
bool IsMediaPresent() const { return mIsMediaPresent; }
bool IsSharing() const { return mIsSharing; }
bool IsFormatting() const { return mIsFormatting; }
bool IsUnmounting() const { return mIsUnmounting; }
typedef nsTArray<nsRefPtr<nsVolume> > Array;
@ -103,6 +107,7 @@ private:
bool mIsMediaPresent;
bool mIsSharing;
bool mIsFormatting;
bool mIsUnmounting;
};
} // system

View File

@ -208,7 +208,8 @@ nsVolumeService::CreateOrGetVolumeByPath(const nsAString& aPath, nsIVolume** aRe
true /* isMediaPresent*/,
false /* isSharing */,
false /* isFormatting */,
true /* isFake */);
true /* isFake */,
false /* isUnmounting*/);
vol.forget(aResult);
return NS_OK;
}
@ -269,6 +270,7 @@ nsVolumeService::GetVolumesForIPC(nsTArray<VolumeInfo>* aResult)
volInfo->isSharing() = vol->mIsSharing;
volInfo->isFormatting() = vol->mIsFormatting;
volInfo->isFake() = vol->mIsFake;
volInfo->isUnmounting() = vol->mIsUnmounting;
}
}
@ -296,7 +298,8 @@ nsVolumeService::GetVolumesFromParent()
volInfo.isMediaPresent(),
volInfo.isSharing(),
volInfo.isFormatting(),
volInfo.isFake());
volInfo.isFake(),
volInfo.isUnmounting());
UpdateVolume(vol, false);
}
}
@ -420,7 +423,8 @@ nsVolumeService::CreateFakeVolume(const nsAString& name, const nsAString& path)
true /* isMediaPresent */,
false /* isSharing */,
false /* isFormatting */,
true /* isFake */);
true /* isFake */,
false /* isUnmounting */);
vol->LogState();
UpdateVolume(vol.get());
return NS_OK;
@ -470,11 +474,11 @@ public:
{
MOZ_ASSERT(NS_IsMainThread());
DBG("UpdateVolumeRunnable::Run '%s' state %s gen %d locked %d "
"media %d sharing %d formatting %d",
"media %d sharing %d formatting %d unmounting %d",
mVolume->NameStr().get(), mVolume->StateStr(),
mVolume->MountGeneration(), (int)mVolume->IsMountLocked(),
(int)mVolume->IsMediaPresent(), mVolume->IsSharing(),
mVolume->IsFormatting());
mVolume->IsFormatting(), mVolume->IsUnmounting());
mVolumeService->UpdateVolume(mVolume);
mVolumeService = nullptr;
@ -491,11 +495,11 @@ void
nsVolumeService::UpdateVolumeIOThread(const Volume* aVolume)
{
DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s' gen %d locked %d "
"media %d sharing %d formatting %d",
"media %d sharing %d formatting %d unmounting %d",
aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get(),
aVolume->MountGeneration(), (int)aVolume->IsMountLocked(),
(int)aVolume->MediaPresent(), (int)aVolume->IsSharing(),
(int)aVolume->IsFormatting());
(int)aVolume->IsFormatting(), (int)mVolume->IsUnmounting());
MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
NS_DispatchToMainThread(new UpdateVolumeRunnable(this, aVolume));
}

View File

@ -57,20 +57,6 @@ const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms.
const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe;
// MMI match groups
const MMI_MATCH_GROUP_FULL_MMI = 1;
const MMI_MATCH_GROUP_PROCEDURE = 2;
const MMI_MATCH_GROUP_SERVICE_CODE = 3;
const MMI_MATCH_GROUP_SIA = 4;
const MMI_MATCH_GROUP_SIB = 5;
const MMI_MATCH_GROUP_SIC = 6;
const MMI_MATCH_GROUP_PWD_CONFIRM = 7;
const MMI_MATCH_GROUP_DIALING_NUMBER = 8;
const MMI_MAX_LENGTH_SHORT_CODE = 2;
const MMI_END_OF_USSD = "#";
const GET_CURRENT_CALLS_RETRY_MAX = 3;
let RILQUIRKS_CALLSTATE_EXTRA_UINT32;
@ -2402,208 +2388,11 @@ RilObject.prototype = {
{callback: callback});
},
/**
* Parse the dial number to extract its mmi code part.
*
* @param number
* Phone number to be parsed
*/
parseMMIFromDialNumber: function(options) {
// We don't have to parse mmi in cdma.
if (!this._isCdma) {
options.mmi = this._parseMMI(options.number);
}
this.sendChromeMessage(options);
},
/**
* Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
*/
_parseMMI: function(mmiString) {
if (!mmiString || !mmiString.length) {
return null;
}
let matches = this._getMMIRegExp().exec(mmiString);
if (matches) {
return {
fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI],
procedure: matches[MMI_MATCH_GROUP_PROCEDURE],
serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE],
sia: matches[MMI_MATCH_GROUP_SIA],
sib: matches[MMI_MATCH_GROUP_SIB],
sic: matches[MMI_MATCH_GROUP_SIC],
pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM],
dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER]
};
}
if (this._isPoundString(mmiString) || this._isMMIShortString(mmiString)) {
return {
fullMMI: mmiString
};
}
return null;
},
/**
* Build the regex to parse MMI string.
*
* The resulting groups after matching will be:
* 1 = full MMI string that might be used as a USSD request.
* 2 = MMI procedure.
* 3 = Service code.
* 4 = SIA.
* 5 = SIB.
* 6 = SIC.
* 7 = Password registration.
* 8 = Dialing number.
*
* @see TS.22.030 Figure 3.5.3.2.
*/
_buildMMIRegExp: function() {
// The general structure of the codes is as follows:
// - Activation (*SC*SI#).
// - Deactivation (#SC*SI#).
// - Interrogation (*#SC*SI#).
// - Registration (**SC*SI#).
// - Erasure (##SC*SI#).
//
// where SC = Service Code (2 or 3 digits) and SI = Supplementary Info
// (variable length).
// MMI procedure, which could be *, #, *#, **, ##
let procedure = "(\\*[*#]?|##?)";
// MMI Service code, which is a 2 or 3 digits that uniquely specifies the
// Supplementary Service associated with the MMI code.
let serviceCode = "(\\d{2,3})";
// MMI Supplementary Information SIA, SIB and SIC. SIA may comprise e.g. a
// PIN code or Directory Number, SIB may be used to specify the tele or
// bearer service and SIC to specify the value of the "No Reply Condition
// Timer". Where a particular service request does not require any SI,
// "*SI" is not entered. The use of SIA, SIB and SIC is optional and shall
// be entered in any of the following formats:
// - *SIA*SIB*SIC#
// - *SIA*SIB#
// - *SIA**SIC#
// - *SIA#
// - **SIB*SIC#
// - ***SIC#
//
// Also catch the additional NEW_PASSWORD for the case of a password
// registration procedure. Ex:
// - * 03 * ZZ * OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - ** 03 * ZZ * OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - * 03 ** OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - ** 03 ** OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
let si = "\\*([^*#]*)";
let allSi = "";
for (let i = 0; i < 4; ++i) {
allSi = "(?:" + si + allSi + ")?";
}
let fullmmi = "(" + procedure + serviceCode + allSi + "#)";
// dial string after the #.
let dialString = "([^#]*)";
return new RegExp(fullmmi + dialString);
},
/**
* Provide the regex to parse MMI string.
*/
_getMMIRegExp: function() {
if (!this._mmiRegExp) {
this._mmiRegExp = this._buildMMIRegExp();
}
return this._mmiRegExp;
},
/**
* Helper to parse # string. TS.22.030 Figure 3.5.3.2.
*/
_isPoundString: function(mmiString) {
return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD);
},
/**
* Helper to parse short string. TS.22.030 Figure 3.5.3.2.
*/
_isMMIShortString: function(mmiString) {
if (mmiString.length > 2) {
return false;
}
// TODO: Should take care of checking if the string is an emergency number
// in Bug 889737. See Bug 1023141 for more background.
// In a call case.
if (Object.getOwnPropertyNames(this.currentCalls).length > 0) {
return true;
}
// Input string is 2 digits starting with a "1"
if ((mmiString.length == 2) && (mmiString.charAt(0) === '1')) {
return false;
}
return true;
},
_serviceCodeToKeyString: function(serviceCode) {
switch (serviceCode) {
case MMI_SC_CFU:
case MMI_SC_CF_BUSY:
case MMI_SC_CF_NO_REPLY:
case MMI_SC_CF_NOT_REACHABLE:
case MMI_SC_CF_ALL:
case MMI_SC_CF_ALL_CONDITIONAL:
return MMI_KS_SC_CALL_FORWARDING;
case MMI_SC_PIN:
return MMI_KS_SC_PIN;
case MMI_SC_PIN2:
return MMI_KS_SC_PIN2;
case MMI_SC_PUK:
return MMI_KS_SC_PUK;
case MMI_SC_PUK2:
return MMI_KS_SC_PUK2;
case MMI_SC_IMEI:
return MMI_KS_SC_IMEI;
case MMI_SC_CLIP:
return MMI_KS_SC_CLIP;
case MMI_SC_CLIR:
return MMI_KS_SC_CLIR;
case MMI_SC_BAOC:
case MMI_SC_BAOIC:
case MMI_SC_BAOICxH:
case MMI_SC_BAIC:
case MMI_SC_BAICr:
case MMI_SC_BA_ALL:
case MMI_SC_BA_MO:
case MMI_SC_BA_MT:
return MMI_KS_SC_CALL_BARRING;
case MMI_SC_CALL_WAITING:
return MMI_KS_SC_CALL_WAITING;
default:
return MMI_KS_SC_USSD;
}
},
sendMMI: function(options) {
if (DEBUG) {
this.context.debug("SendMMI " + JSON.stringify(options));
}
let mmi = this._parseMMI(options.mmi);
if (DEBUG) {
this.context.debug("MMI " + JSON.stringify(mmi));
}
let _sendMMIError = (function(errorMsg) {
options.success = false;
options.errorMsg = errorMsg;
@ -2611,14 +2400,12 @@ RilObject.prototype = {
}).bind(this);
// It's neither a valid mmi code nor an ongoing ussd.
let mmi = options.mmi;
if (!mmi && !this._ussdSession) {
_sendMMIError(MMI_ERROR_KS_ERROR);
return;
}
options.mmiServiceCode = mmi ?
this._serviceCodeToKeyString(mmi.serviceCode) : MMI_KS_SC_USSD;
function _isValidPINPUKRequest() {
// The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling
// is "Registration" (**).
@ -3576,36 +3363,34 @@ RilObject.prototype = {
return;
}
let mmiServiceCode = options.mmiServiceCode;
let serviceCode = options.mmi.serviceCode;
if (options.success) {
switch (mmiServiceCode) {
case MMI_KS_SC_PIN:
switch (serviceCode) {
case MMI_SC_PIN:
options.statusMessage = MMI_SM_KS_PIN_CHANGED;
break;
case MMI_KS_SC_PIN2:
case MMI_SC_PIN2:
options.statusMessage = MMI_SM_KS_PIN2_CHANGED;
break;
case MMI_KS_SC_PUK:
case MMI_SC_PUK:
options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED;
break;
case MMI_KS_SC_PUK2:
case MMI_SC_PUK2:
options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED;
break;
}
} else {
if (options.retryCount <= 0) {
if (mmiServiceCode === MMI_KS_SC_PUK) {
if (serviceCode === MMI_SC_PUK) {
options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED;
} else if (mmiServiceCode === MMI_KS_SC_PIN) {
} else if (serviceCode === MMI_SC_PIN) {
options.errorMsg = MMI_ERROR_KS_NEEDS_PUK;
}
} else {
if (mmiServiceCode === MMI_KS_SC_PIN ||
mmiServiceCode === MMI_KS_SC_PIN2) {
if (serviceCode === MMI_SC_PIN || serviceCode === MMI_SC_PIN2) {
options.errorMsg = MMI_ERROR_KS_BAD_PIN;
} else if (mmiServiceCode === MMI_KS_SC_PUK ||
mmiServiceCode === MMI_KS_SC_PUK2) {
} else if (serviceCode === MMI_SC_PUK || serviceCode === MMI_SC_PUK2) {
options.errorMsg = MMI_ERROR_KS_BAD_PUK;
}
if (options.retryCount !== undefined) {
@ -6030,7 +5815,7 @@ RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] =
this.sendChromeMessage(options);
};
RilObject.prototype[REQUEST_SET_CALL_FORWARD] =
function REQUEST_SET_CALL_FORWARD(length, options) {
function REQUEST_SET_CALL_FORWARD(length, options) {
options.success = (options.rilRequestError === 0);
if (!options.success) {
options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];

View File

@ -7,6 +7,19 @@ function run_test() {
run_next_test();
}
function createMMIOptions(procedure, serviceCode, sia, sib, sic) {
let mmi = {
fullMMI: Array.slice(arguments).join("*") + "#",
procedure: procedure,
serviceCode: serviceCode,
sia: sia,
sib: sib,
sic: sic
};
return mmi;
}
function testSendMMI(mmi, error) {
let workerhelper = newInterceptWorker();
let worker = workerhelper.worker;
@ -27,20 +40,8 @@ function testSendMMI(mmi, error) {
* sendMMI tests.
*/
add_test(function test_sendMMI_empty() {
testSendMMI("", MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_undefined() {
testSendMMI({}, MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_invalid() {
testSendMMI("11", MMI_ERROR_KS_ERROR);
add_test(function test_sendMMI_null() {
testSendMMI(null, MMI_ERROR_KS_ERROR);
run_next_test();
});
@ -61,7 +62,7 @@ add_test(function test_sendMMI_short_code() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "**"});
context.RIL.sendMMI({mmi: {fullMMI: "**"}});
let postedMessage = workerhelper.postedMessage;
do_check_eq(ussdOptions.ussd, "**");
@ -72,12 +73,6 @@ add_test(function test_sendMMI_short_code() {
run_next_test();
});
add_test(function test_sendMMI_dial_string() {
testSendMMI("123", MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_change_PIN() {
let workerhelper = newInterceptWorker();
let worker = workerhelper.worker;
@ -90,7 +85,8 @@ add_test(function test_sendMMI_change_PIN() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "**04*1234*4567*4567#"});
context.RIL.sendMMI({mmi: createMMIOptions("**", "04", "1234", "4567",
"4567")});
let postedMessage = workerhelper.postedMessage;
@ -101,25 +97,29 @@ add_test(function test_sendMMI_change_PIN() {
});
add_test(function test_sendMMI_change_PIN_no_new_PIN() {
testSendMMI("**04*1234**4567#", MMI_ERROR_KS_ERROR);
testSendMMI(createMMIOptions("**", "04", "1234", "", "4567"),
MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_change_PIN_no_old_PIN() {
testSendMMI("**04**1234*4567#", MMI_ERROR_KS_ERROR);
testSendMMI(createMMIOptions("**", "04", "", "1234", "4567"),
MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_change_PIN_wrong_procedure() {
testSendMMI("*04*1234*4567*4567#", MMI_ERROR_KS_INVALID_ACTION);
testSendMMI(createMMIOptions("*", "04", "1234", "4567", "4567"),
MMI_ERROR_KS_INVALID_ACTION);
run_next_test();
});
add_test(function test_sendMMI_change_PIN_new_PIN_mismatch() {
testSendMMI("**04*4567*1234*4567#", MMI_ERROR_KS_MISMATCH_PIN);
testSendMMI(createMMIOptions("**", "04", "4567", "1234", "4567"),
MMI_ERROR_KS_MISMATCH_PIN);
run_next_test();
});
@ -136,7 +136,8 @@ add_test(function test_sendMMI_change_PIN2() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "**042*1234*4567*4567#"});
context.RIL.sendMMI({mmi: createMMIOptions("**", "042", "1234", "4567",
"4567")});
let postedMessage = workerhelper.postedMessage;
@ -147,25 +148,29 @@ add_test(function test_sendMMI_change_PIN2() {
});
add_test(function test_sendMMI_change_PIN2_no_new_PIN2() {
testSendMMI("**042*1234**4567#", MMI_ERROR_KS_ERROR);
testSendMMI(createMMIOptions("**", "042", "1234", "", "4567"),
MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_change_PIN2_no_old_PIN2() {
testSendMMI("**042**1234*4567#", MMI_ERROR_KS_ERROR);
testSendMMI(createMMIOptions("**", "042", "", "1234", "4567"),
MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_change_PIN2_wrong_procedure() {
testSendMMI("*042*1234*4567*4567#", MMI_ERROR_KS_INVALID_ACTION);
testSendMMI(createMMIOptions("*", "042", "1234", "4567", "4567"),
MMI_ERROR_KS_INVALID_ACTION);
run_next_test();
});
add_test(function test_sendMMI_change_PIN2_new_PIN2_mismatch() {
testSendMMI("**042*4567*1234*4567#", MMI_ERROR_KS_MISMATCH_PIN);
testSendMMI(createMMIOptions("**", "042", "4567", "1234", "4567"),
MMI_ERROR_KS_MISMATCH_PIN);
run_next_test();
});
@ -182,7 +187,8 @@ add_test(function test_sendMMI_unblock_PIN() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "**05*1234*4567*4567#"});
context.RIL.sendMMI({mmi: createMMIOptions("**", "05", "1234", "4567",
"4567")});
let postedMessage = workerhelper.postedMessage;
@ -193,25 +199,29 @@ add_test(function test_sendMMI_unblock_PIN() {
});
add_test(function test_sendMMI_unblock_PIN_no_new_PIN() {
testSendMMI("**05*1234**4567#", MMI_ERROR_KS_ERROR);
testSendMMI(createMMIOptions("**", "05", "1234", "", "4567"),
MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_unblock_PIN_no_PUK() {
testSendMMI("**05**1234*4567#", MMI_ERROR_KS_ERROR);
testSendMMI(createMMIOptions("**", "05", "", "1234", "4567"),
MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_unblock_PIN_wrong_procedure() {
testSendMMI("*05*1234*4567*4567#", MMI_ERROR_KS_INVALID_ACTION);
testSendMMI(createMMIOptions("*", "05", "1234", "4567", "4567"),
MMI_ERROR_KS_INVALID_ACTION);
run_next_test();
});
add_test(function test_sendMMI_unblock_PIN_new_PIN_mismatch() {
testSendMMI("**05*4567*1234*4567#", MMI_ERROR_KS_MISMATCH_PIN);
testSendMMI(createMMIOptions("**", "05", "4567", "1234", "4567"),
MMI_ERROR_KS_MISMATCH_PIN);
run_next_test();
});
@ -228,7 +238,8 @@ add_test(function test_sendMMI_unblock_PIN2() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "**052*1234*4567*4567#"});
context.RIL.sendMMI({mmi: createMMIOptions("**", "052", "1234", "4567",
"4567")});
let postedMessage = workerhelper.postedMessage;
@ -239,25 +250,29 @@ add_test(function test_sendMMI_unblock_PIN2() {
});
add_test(function test_sendMMI_unblock_PIN2_no_new_PIN2() {
testSendMMI("**052*1234**4567#", MMI_ERROR_KS_ERROR);
testSendMMI(createMMIOptions("**", "052", "1234", "", "4567"),
MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_unblock_PIN2_no_PUK2() {
testSendMMI("**052**1234*4567#", MMI_ERROR_KS_ERROR);
testSendMMI(createMMIOptions("**", "052", "", "1234", "4567"),
MMI_ERROR_KS_ERROR);
run_next_test();
});
add_test(function test_sendMMI_unblock_PIN2_wrong_procedure() {
testSendMMI("*052*1234*4567*4567#", MMI_ERROR_KS_INVALID_ACTION);
testSendMMI(createMMIOptions("*", "052", "1234", "4567", "4567"),
MMI_ERROR_KS_INVALID_ACTION);
run_next_test();
});
add_test(function test_sendMMI_unblock_PIN2_new_PIN_mismatch() {
testSendMMI("**052*4567*1234*4567#", MMI_ERROR_KS_MISMATCH_PIN);
testSendMMI(createMMIOptions("**", "052", "4567", "1234", "4567"),
MMI_ERROR_KS_MISMATCH_PIN);
run_next_test();
});
@ -275,7 +290,7 @@ add_test(function test_sendMMI_get_IMEI() {
});
};
context.RIL.sendMMI({mmi: "*#06#"});
context.RIL.sendMMI({mmi: createMMIOptions("*#", "06")});
let postedMessage = workerhelper.postedMessage;
@ -299,7 +314,7 @@ add_test(function test_sendMMI_get_IMEI_error() {
});
};
context.RIL.sendMMI({mmi: "*#06#"});
context.RIL.sendMMI({mmi: createMMIOptions("*#", "06")});
let postedMessage = workerhelper.postedMessage;
@ -328,7 +343,7 @@ add_test(function test_sendMMI_call_barring_BAIC_interrogation_voice() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "*#33#"});
context.RIL.sendMMI({mmi: createMMIOptions("*#", "33")});
let postedMessage = workerhelper.postedMessage;
@ -358,7 +373,7 @@ add_test(function test_sendMMI_call_barring_BAIC_activation() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "*33#"});
context.RIL.sendMMI({mmi: createMMIOptions("*", "33")});
let postedMessage = workerhelper.postedMessage;
@ -386,7 +401,7 @@ add_test(function test_sendMMI_call_barring_BAIC_deactivation() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "#33#"});
context.RIL.sendMMI({mmi: createMMIOptions("#", "33")});
let postedMessage = workerhelper.postedMessage;
@ -398,7 +413,7 @@ add_test(function test_sendMMI_call_barring_BAIC_deactivation() {
});
add_test(function test_sendMMI_call_barring_BAIC_procedure_not_supported() {
testSendMMI("**33*0000#", MMI_ERROR_KS_NOT_SUPPORTED);
testSendMMI(createMMIOptions("**", "33", "0000"), MMI_ERROR_KS_NOT_SUPPORTED);
run_next_test();
});
@ -417,11 +432,11 @@ add_test(function test_sendMMI_USSD() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "*123#"});
context.RIL.sendMMI({mmi: createMMIOptions("*", "123")});
let postedMessage = workerhelper.postedMessage;
do_check_eq(ussdOptions.ussd, "*123#");
do_check_eq(ussdOptions.ussd, "**123#");
do_check_eq (postedMessage.errorMsg, GECKO_ERROR_SUCCESS);
do_check_true(postedMessage.success);
do_check_true(context.RIL._ussdSession);
@ -443,11 +458,11 @@ add_test(function test_sendMMI_USSD_error() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "*123#"});
context.RIL.sendMMI({mmi: createMMIOptions("*", "123")});
let postedMessage = workerhelper.postedMessage;
do_check_eq(ussdOptions.ussd, "*123#");
do_check_eq(ussdOptions.ussd, "**123#");
do_check_eq (postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE);
do_check_false(postedMessage.success);
do_check_false(context.RIL._ussdSession);
@ -476,25 +491,25 @@ function setCallWaitingSuccess(mmi) {
}
add_test(function test_sendMMI_call_waiting_activation() {
setCallWaitingSuccess("*43*10#");
setCallWaitingSuccess(createMMIOptions("*", "43", "10"));
run_next_test();
});
add_test(function test_sendMMI_call_waiting_deactivation() {
setCallWaitingSuccess("#43#");
setCallWaitingSuccess(createMMIOptions("#", "43"));
run_next_test();
});
add_test(function test_sendMMI_call_waiting_registration() {
testSendMMI("**43#", MMI_ERROR_KS_NOT_SUPPORTED);
testSendMMI(createMMIOptions("**", "43"), MMI_ERROR_KS_NOT_SUPPORTED);
run_next_test();
});
add_test(function test_sendMMI_call_waiting_erasure() {
testSendMMI("##43#", MMI_ERROR_KS_NOT_SUPPORTED);
testSendMMI(createMMIOptions("##", "43"), MMI_ERROR_KS_NOT_SUPPORTED);
run_next_test();
});
@ -520,7 +535,7 @@ add_test(function test_sendMMI_call_waiting_interrogation() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "*#43#"});
context.RIL.sendMMI({mmi: createMMIOptions("*#", "43")});
let postedMessage = workerhelper.postedMessage;

View File

@ -7,7 +7,20 @@ function run_test() {
run_next_test();
}
function setCallForwardSuccess(mmi) {
function createMMIOptions(procedure, serviceCode, sia, sib, sic) {
let mmi = {
fullMMI: Array.slice(arguments).join("*") + "#",
procedure: procedure,
serviceCode: serviceCode,
sia: sia,
sib: sib,
sic: sic
};
return mmi;
}
function setCallForwardSuccess(procedure, serviceCode, sia, sib, sic) {
let workerhelper = newInterceptWorker();
let worker = workerhelper.worker;
let context = worker.ContextPool._contexts[0];
@ -19,22 +32,23 @@ function setCallForwardSuccess(mmi) {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: mmi});
context.RIL.sendMMI({mmi: createMMIOptions(procedure, serviceCode, sia, sib,
sic)});
let postedMessage = workerhelper.postedMessage;
do_check_eq(postedMessage.errorMsg, GECKO_ERROR_SUCCESS);
do_check_eq(postedMessage.errorMsg, undefined);
do_check_true(postedMessage.success);
}
add_test(function test_sendMMI_call_forwarding_activation() {
setCallForwardSuccess("*21*12345*99*10#");
setCallForwardSuccess("*", "21", "12345", "99", "10");
run_next_test();
});
add_test(function test_sendMMI_call_forwarding_deactivation() {
setCallForwardSuccess("#21*12345*99*10#");
setCallForwardSuccess("#", "21", "12345", "99", "10");
run_next_test();
});
@ -67,11 +81,11 @@ add_test(function test_sendMMI_call_forwarding_interrogation() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "*#21#"});
context.RIL.sendMMI({mmi: createMMIOptions("*#", "21")});
let postedMessage = workerhelper.postedMessage;
do_check_eq(postedMessage.errorMsg, GECKO_ERROR_SUCCESS);
do_check_eq(postedMessage.errorMsg, undefined);
do_check_true(postedMessage.success);
do_check_true(Array.isArray(postedMessage.rules));
do_check_eq(postedMessage.rules.length, 1);
@ -97,7 +111,7 @@ add_test(function test_sendMMI_call_forwarding_interrogation_no_rules() {
};
context.RIL.radioState = GECKO_RADIOSTATE_ENABLED;
context.RIL.sendMMI({mmi: "*#21#"});
context.RIL.sendMMI({mmi: createMMIOptions("*#", "21")});
let postedMessage = workerhelper.postedMessage;
@ -109,43 +123,43 @@ add_test(function test_sendMMI_call_forwarding_interrogation_no_rules() {
add_test(function test_sendMMI_call_forwarding_registration() {
setCallForwardSuccess("**21*12345*99*10#");
setCallForwardSuccess("**", "21", "12345", "99", "10");
run_next_test();
});
add_test(function test_sendMMI_call_forwarding_erasure() {
setCallForwardSuccess("##21*12345*99#");
setCallForwardSuccess("##", "21", "12345", "99");
run_next_test();
});
add_test(function test_sendMMI_call_forwarding_CFB() {
setCallForwardSuccess("*67*12345*99*10#");
setCallForwardSuccess("*", "67", "12345", "99", "10");
run_next_test();
});
add_test(function test_sendMMI_call_forwarding_CFNRy() {
setCallForwardSuccess("*61*12345*99*10#");
setCallForwardSuccess("*", "61", "12345", "99", "10");
run_next_test();
});
add_test(function test_sendMMI_call_forwarding_CFNRc() {
setCallForwardSuccess("*62*12345*99*10#");
setCallForwardSuccess("*", "62", "12345", "99", "10");
run_next_test();
});
add_test(function test_sendMMI_call_forwarding_CFAll() {
setCallForwardSuccess("*004*12345*99*10#");
setCallForwardSuccess("*", "004", "12345", "99", "10");
run_next_test();
});
add_test(function test_sendMMI_call_forwarding_CFAllConditional() {
setCallForwardSuccess("*002*12345*99*10#");
setCallForwardSuccess("*", "002", "12345", "99", "10");
run_next_test();
});

View File

@ -1,317 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
function run_test() {
run_next_test();
}
let worker;
function parseMMI(mmi) {
if (!worker) {
worker = newWorker({
postRILMessage: function(data) {
// Do nothing
},
postMessage: function(message) {
// Do nothing
}
});
}
let context = worker.ContextPool._contexts[0];
return context.RIL._parseMMI(mmi);
}
add_test(function test_parseMMI_empty() {
let mmi = parseMMI("");
do_check_null(mmi);
run_next_test();
});
add_test(function test_parseMMI_undefined() {
let mmi = parseMMI();
do_check_null(mmi);
run_next_test();
});
add_test(function test_parseMMI_one_digit_short_code() {
let mmi = parseMMI("1");
do_check_eq(mmi.fullMMI, "1");
do_check_eq(mmi.procedure, undefined);
do_check_eq(mmi.serviceCode, undefined);
do_check_eq(mmi.sia, undefined);
do_check_eq(mmi.sib, undefined);
do_check_eq(mmi.sic, undefined);
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, undefined);
run_next_test();
});
add_test(function test_parseMMI_invalid_short_code() {
let mmi = parseMMI("11");
do_check_null(mmi);
run_next_test();
});
add_test(function test_parseMMI_short_code() {
let mmi = parseMMI("21");
do_check_eq(mmi.fullMMI, "21");
do_check_eq(mmi.procedure, undefined);
do_check_eq(mmi.serviceCode, undefined);
do_check_eq(mmi.sia, undefined);
do_check_eq(mmi.sib, undefined);
do_check_eq(mmi.sic, undefined);
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, undefined);
run_next_test();
});
add_test(function test_parseMMI_dial_string() {
let mmi = parseMMI("12345");
do_check_null(mmi);
run_next_test();
});
add_test(function test_parseMMI_USSD_without_asterisk_prefix() {
let mmi = parseMMI("123#");
do_check_eq(mmi.fullMMI, "123#");
do_check_eq(mmi.procedure, undefined);
do_check_eq(mmi.serviceCode, undefined);
do_check_eq(mmi.sia, undefined);
do_check_eq(mmi.sib, undefined);
do_check_eq(mmi.sic, undefined);
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, undefined);
run_next_test();
});
add_test(function test_parseMMI_USSD() {
let mmi = parseMMI("*123#");
do_check_eq(mmi.fullMMI, "*123#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, undefined);
do_check_eq(mmi.sib, undefined);
do_check_eq(mmi.sic, undefined);
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sia() {
let mmi = parseMMI("*123*1#");
do_check_eq(mmi.fullMMI, "*123*1#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, "1");
do_check_eq(mmi.sib, undefined);
do_check_eq(mmi.sic, undefined);
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sib() {
let mmi = parseMMI("*123**1#");
do_check_eq(mmi.fullMMI, "*123**1#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, "");
do_check_eq(mmi.sib, "1");
do_check_eq(mmi.sic, undefined);
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sic() {
let mmi = parseMMI("*123***1#");
do_check_eq(mmi.fullMMI, "*123***1#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, "");
do_check_eq(mmi.sib, "");
do_check_eq(mmi.sic, "1");
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sia_sib() {
let mmi = parseMMI("*123*1*1#");
do_check_eq(mmi.fullMMI, "*123*1*1#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, "1");
do_check_eq(mmi.sib, "1");
do_check_eq(mmi.sic, undefined);
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sia_sic() {
let mmi = parseMMI("*123*1**1#");
do_check_eq(mmi.fullMMI, "*123*1**1#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, "1");
do_check_eq(mmi.sib, "");
do_check_eq(mmi.sic, "1");
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sib_sic() {
let mmi = parseMMI("*123**1*1#");
do_check_eq(mmi.fullMMI, "*123**1*1#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, "");
do_check_eq(mmi.sib, "1");
do_check_eq(mmi.sic, "1");
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_pwd() {
let mmi = parseMMI("*123****1#");
do_check_eq(mmi.fullMMI, "*123****1#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, "");
do_check_eq(mmi.sib, "");
do_check_eq(mmi.sic, "");
do_check_eq(mmi.pwd, "1");
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_dial_number() {
let mmi = parseMMI("*123#345");
do_check_eq(mmi.fullMMI, "*123#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "123");
do_check_eq(mmi.sia, undefined);
do_check_eq(mmi.sib, undefined);
do_check_eq(mmi.sic, undefined);
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "345");
run_next_test();
});
/**
* MMI procedures tests
*/
add_test(function test_parseMMI_activation() {
let mmi = parseMMI("*00*12*34*56#");
do_check_eq(mmi.fullMMI, "*00*12*34*56#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
do_check_eq(mmi.serviceCode, "00");
do_check_eq(mmi.sia, "12");
do_check_eq(mmi.sib, "34");
do_check_eq(mmi.sic, "56");
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_deactivation() {
let mmi = parseMMI("#00*12*34*56#");
do_check_eq(mmi.fullMMI, "#00*12*34*56#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_DEACTIVATION);
do_check_eq(mmi.serviceCode, "00");
do_check_eq(mmi.sia, "12");
do_check_eq(mmi.sib, "34");
do_check_eq(mmi.sic, "56");
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_interrogation() {
let mmi = parseMMI("*#00*12*34*56#");
do_check_eq(mmi.fullMMI, "*#00*12*34*56#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_INTERROGATION);
do_check_eq(mmi.serviceCode, "00");
do_check_eq(mmi.sia, "12");
do_check_eq(mmi.sib, "34");
do_check_eq(mmi.sic, "56");
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_registration() {
let mmi = parseMMI("**00*12*34*56#");
do_check_eq(mmi.fullMMI, "**00*12*34*56#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_REGISTRATION);
do_check_eq(mmi.serviceCode, "00");
do_check_eq(mmi.sia, "12");
do_check_eq(mmi.sib, "34");
do_check_eq(mmi.sic, "56");
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_erasure() {
let mmi = parseMMI("##00*12*34*56#");
do_check_eq(mmi.fullMMI, "##00*12*34*56#");
do_check_eq(mmi.procedure, MMI_PROCEDURE_ERASURE);
do_check_eq(mmi.serviceCode, "00");
do_check_eq(mmi.sia, "12");
do_check_eq(mmi.sib, "34");
do_check_eq(mmi.sic, "56");
do_check_eq(mmi.pwd, undefined);
do_check_eq(mmi.dialNumber, "");
run_next_test();
});

View File

@ -24,7 +24,6 @@ skip-if = true
[test_ril_worker_sms_segment_info.js]
[test_ril_worker_mmi.js]
[test_ril_worker_mmi_cf.js]
[test_ril_worker_mmi_parseMMI.js]
[test_ril_worker_cf.js]
[test_ril_worker_cellbroadcast_config.js]
[test_ril_worker_cellbroadcast.js]

View File

@ -5,19 +5,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Telephony.h"
#include "mozilla/dom/CallEvent.h"
#include "mozilla/dom/TelephonyBinding.h"
#include "mozilla/dom/Promise.h"
#include "nsIURI.h"
#include "nsPIDOMWindow.h"
#include "nsIPermissionManager.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/CallEvent.h"
#include "mozilla/dom/MozMobileConnectionBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/TelephonyBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsContentUtils.h"
#include "nsIPermissionManager.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
@ -25,6 +26,7 @@
#include "TelephonyCall.h"
#include "TelephonyCallGroup.h"
#include "TelephonyCallId.h"
#include "TelephonyCallback.h"
// Service instantiation
#include "ipc/TelephonyIPCService.h"
@ -34,6 +36,7 @@
#include "nsXULAppAPI.h" // For XRE_GetProcessType()
using namespace mozilla::dom;
using namespace mozilla::dom::telephony;
using mozilla::ErrorResult;
class Telephony::Listener : public nsITelephonyListener
@ -60,43 +63,6 @@ public:
}
};
class Telephony::Callback : public nsITelephonyCallback
{
nsRefPtr<Telephony> mTelephony;
nsRefPtr<Promise> mPromise;
uint32_t mServiceId;
virtual ~Callback() {}
public:
NS_DECL_ISUPPORTS
Callback(Telephony* aTelephony, Promise* aPromise, uint32_t aServiceId)
: mTelephony(aTelephony), mPromise(aPromise), mServiceId(aServiceId)
{
MOZ_ASSERT(mTelephony);
}
NS_IMETHODIMP
NotifyDialError(const nsAString& aError)
{
mPromise->MaybeRejectBrokenly(aError);
return NS_OK;
}
NS_IMETHODIMP
NotifyDialSuccess(uint32_t aCallIndex, const nsAString& aNumber)
{
nsRefPtr<TelephonyCallId> id = mTelephony->CreateCallId(aNumber);
nsRefPtr<TelephonyCall> call =
mTelephony->CreateCall(id, mServiceId, aCallIndex,
nsITelephonyService::CALL_STATE_DIALING);
mPromise->MaybeResolve(call);
return NS_OK;
}
};
class Telephony::EnumerationAck : public nsRunnable
{
nsRefPtr<Telephony> mTelephony;
@ -270,7 +236,8 @@ Telephony::DialInternal(uint32_t aServiceId, const nsAString& aNumber,
}
nsCOMPtr<nsITelephonyCallback> callback =
new Callback(this, promise, aServiceId);
new TelephonyCallback(GetOwner(), this, promise, aServiceId);
nsresult rv = mService->Dial(aServiceId, aNumber, aEmergency, callback);
if (NS_FAILED(rv)) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
@ -381,7 +348,6 @@ NS_IMPL_ADDREF_INHERITED(Telephony, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(Telephony, DOMEventTargetHelper)
NS_IMPL_ISUPPORTS(Telephony::Listener, nsITelephonyListener)
NS_IMPL_ISUPPORTS(Telephony::Callback, nsITelephonyCallback)
// Telephony WebIDL

View File

@ -21,6 +21,11 @@ class nsPIDOMWindow;
namespace mozilla {
namespace dom {
namespace telephony {
class TelephonyCallback;
} // namespace telephony
class OwningTelephonyCallOrTelephonyCallGroup;
@ -35,12 +40,10 @@ class Telephony MOZ_FINAL : public DOMEventTargetHelper,
* also bug 775997 comment #51.
*/
class Listener;
class Callback;
friend class Callback;
class EnumerationAck;
friend class EnumerationAck;
friend class telephony::TelephonyCallback;
nsCOMPtr<nsITelephonyService> mService;
nsRefPtr<Listener> mListener;

View File

@ -0,0 +1,139 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TelephonyCallback.h"
#include "mozilla/dom/DOMMMIError.h"
#include "nsServiceManagerUtils.h"
using namespace mozilla::dom;
using namespace mozilla::dom::telephony;
NS_IMPL_ISUPPORTS(TelephonyCallback, nsITelephonyCallback)
TelephonyCallback::TelephonyCallback(nsPIDOMWindow* aWindow,
Telephony* aTelephony,
Promise* aPromise,
uint32_t aServiceId)
: mWindow(aWindow), mTelephony(aTelephony), mPromise(aPromise),
mServiceId(aServiceId)
{
MOZ_ASSERT(mTelephony);
}
nsresult
TelephonyCallback::NotifyDialMMISuccess(const nsAString& aStatusMessage)
{
AutoJSAPI jsapi;
if (!NS_WARN_IF(jsapi.Init(mWindow))) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
MozMMIResult result;
result.mServiceCode.Assign(mServiceCode);
result.mStatusMessage.Assign(aStatusMessage);
return NotifyDialMMISuccess(cx, result);
}
nsresult
TelephonyCallback::NotifyDialMMISuccess(JSContext* aCx,
const nsAString& aStatusMessage,
JS::Handle<JS::Value> aInfo)
{
RootedDictionary<MozMMIResult> result(aCx);
result.mServiceCode.Assign(mServiceCode);
result.mStatusMessage.Assign(aStatusMessage);
result.mAdditionalInformation.Construct().SetAsObject() = &aInfo.toObject();
return NotifyDialMMISuccess(aCx, result);
}
nsresult
TelephonyCallback::NotifyDialMMISuccess(JSContext* aCx,
const MozMMIResult& aResult)
{
JS::Rooted<JS::Value> jsResult(aCx);
if (!ToJSValue(aCx, aResult, &jsResult)) {
JS_ClearPendingException(aCx);
return NS_ERROR_TYPE_ERR;
}
return NotifyDialMMISuccess(jsResult);
}
// nsITelephonyCallback
NS_IMETHODIMP
TelephonyCallback::NotifyDialMMI(const nsAString& aServiceCode)
{
mMMIRequest = new DOMRequest(mWindow);
mServiceCode.Assign(aServiceCode);
mPromise->MaybeResolve(mMMIRequest);
return NS_OK;
}
NS_IMETHODIMP
TelephonyCallback::NotifyDialError(const nsAString& aError)
{
mPromise->MaybeRejectBrokenly(aError);
return NS_OK;
}
NS_IMETHODIMP
TelephonyCallback::NotifyDialCallSuccess(uint32_t aCallIndex,
const nsAString& aNumber)
{
nsRefPtr<TelephonyCallId> id = mTelephony->CreateCallId(aNumber);
nsRefPtr<TelephonyCall> call =
mTelephony->CreateCall(id, mServiceId, aCallIndex,
nsITelephonyService::CALL_STATE_DIALING);
mPromise->MaybeResolve(call);
return NS_OK;
}
NS_IMETHODIMP
TelephonyCallback::NotifyDialMMISuccess(JS::Handle<JS::Value> aResult)
{
nsCOMPtr<nsIDOMRequestService> rs = do_GetService(DOMREQUEST_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(rs, NS_ERROR_FAILURE);
return rs->FireSuccessAsync(mMMIRequest, aResult);
}
NS_IMETHODIMP
TelephonyCallback::NotifyDialMMIError(const nsAString& aError)
{
Nullable<int16_t> info;
nsRefPtr<DOMError> error =
new DOMMMIError(mWindow, aError, EmptyString(), mServiceCode, info);
nsCOMPtr<nsIDOMRequestService> rs = do_GetService(DOMREQUEST_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(rs, NS_ERROR_FAILURE);
return rs->FireDetailedError(mMMIRequest, error);
}
NS_IMETHODIMP
TelephonyCallback::NotifyDialMMIErrorWithInfo(const nsAString& aError,
uint16_t aInfo)
{
Nullable<int16_t> info(aInfo);
nsRefPtr<DOMError> error =
new DOMMMIError(mWindow, aError, EmptyString(), mServiceCode, info);
nsCOMPtr<nsIDOMRequestService> rs = do_GetService(DOMREQUEST_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(rs, NS_ERROR_FAILURE);
return rs->FireDetailedError(mMMIRequest, error);
}

View File

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TelephonyCallback_h
#define mozilla_dom_TelephonyCallback_h
#include "Telephony.h"
#include "mozilla/dom/DOMRequest.h"
#include "mozilla/dom/MozMobileConnectionBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsITelephonyService.h"
#include "nsJSUtils.h"
#include "nsString.h"
class nsPIDOMWindow;
namespace mozilla {
namespace dom {
namespace telephony {
class TelephonyCallback MOZ_FINAL : public nsITelephonyCallback
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSITELEPHONYCALLBACK
TelephonyCallback(nsPIDOMWindow* aWindow, Telephony* aTelephony,
Promise* aPromise, uint32_t aServiceId);
nsresult
NotifyDialMMISuccess(const nsAString& aStatusMessage);
template<typename T>
nsresult
NotifyDialMMISuccess(const nsAString& aStatusMessage, const T& aInfo)
{
AutoJSAPI jsapi;
if (!NS_WARN_IF(jsapi.Init(mWindow))) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> info(cx);
if (!ToJSValue(cx, aInfo, &info)) {
JS_ClearPendingException(cx);
return NS_ERROR_TYPE_ERR;
}
return NotifyDialMMISuccess(cx, aStatusMessage, info);
}
private:
~TelephonyCallback() {}
nsresult
NotifyDialMMISuccess(JSContext* aCx, const nsAString& aStatusMessage,
JS::Handle<JS::Value> aInfo);
nsresult
NotifyDialMMISuccess(JSContext* aCx, const MozMMIResult& aResult);
nsCOMPtr<nsPIDOMWindow> mWindow;
nsRefPtr<Telephony> mTelephony;
nsRefPtr<Promise> mPromise;
uint32_t mServiceId;
nsRefPtr<DOMRequest> mMMIRequest;
nsString mServiceCode;
};
} // namespace telephony
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TelephonyCallback_h

View File

@ -54,6 +54,16 @@ const AUDIO_STATE_NAME = [
const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
// MMI match groups
const MMI_MATCH_GROUP_FULL_MMI = 1;
const MMI_MATCH_GROUP_PROCEDURE = 2;
const MMI_MATCH_GROUP_SERVICE_CODE = 3;
const MMI_MATCH_GROUP_SIA = 4;
const MMI_MATCH_GROUP_SIB = 5;
const MMI_MATCH_GROUP_SIC = 6;
const MMI_MATCH_GROUP_PWD_CONFIRM = 7;
const MMI_MATCH_GROUP_DIALING_NUMBER = 8;
let DEBUG;
function debug(s) {
dump("TelephonyService: " + s + "\n");
@ -99,15 +109,52 @@ XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
XPCOMUtils.defineLazyServiceGetter(this, "gGonkMobileConnectionService",
"@mozilla.org/mobileconnection/mobileconnectionservice;1",
"nsIGonkMobileConnectionService");
XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function() {
let ns = {};
Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns);
return ns.PhoneNumberUtils;
});
function MMIResult(aMmiServiceCode, aOptions) {
this.serviceCode = aMmiServiceCode;
this.statusMessage = aOptions.statusMessage;
this.additionalInformation = aOptions.additionalInformation;
}
MMIResult.prototype = {
__exposedProps__ : {serviceCode: 'r',
statusMessage: 'r',
additionalInformation: 'r'},
};
function CallForwardingOptions(aOptions) {
this.active = aOptions.active;
this.action = aOptions.action;
this.reason = aOptions.reason;
this.number = aOptions.number;
this.timeSeconds = aOptions.timeSeconds;
this.serviceClass = aOptions.serviceClass;
}
CallForwardingOptions.prototype = {
__exposedProps__ : {active: 'r',
action: 'r',
reason: 'r',
number: 'r',
timeSeconds: 'r',
serviceClass: 'r'},
};
function TelephonyService() {
this._numClients = gRadioInterfaceLayer.numRadioInterfaces;
this._listeners = [];
this._mmiRegExp = null;
this._isDialing = false;
this._cachedDialRequest = null;
this._currentCalls = {};
this._cdmaCallWaitingNumber = null;
@ -300,6 +347,10 @@ TelephonyService.prototype = {
}
},
_rulesToCallForwardingOptions: function(aRules) {
return aRules.map(rule => new CallForwardingOptions(rule));
},
_updateDebugFlag: function() {
try {
DEBUG = RIL.DEBUG_RIL ||
@ -430,12 +481,13 @@ TelephonyService.prototype = {
aListener.enumerateCallStateComplete();
},
_hasCalls: function(aClientId) {
return Object.keys(this._currentCalls[aClientId]).length !== 0;
},
_hasCallsOnOtherClient: function(aClientId) {
for (let cid = 0; cid < this._numClients; ++cid) {
if (cid === aClientId) {
continue;
}
if (Object.keys(this._currentCalls[cid]).length !== 0) {
if (cid !== aClientId && this._hasCalls(cid)) {
return true;
}
}
@ -499,36 +551,51 @@ TelephonyService.prototype = {
this.notifyCallStateChanged(aClientId, parentCall);
},
_composeDialRequest: function(aClientId, aNumber) {
return new Promise((resolve, reject) => {
this._sendToRilWorker(aClientId, "parseMMIFromDialNumber",
{number: aNumber}, response => {
let options = {};
let mmi = response.mmi;
if (!mmi) {
resolve({
number: aNumber
});
} else if (this._isTemporaryCLIR(mmi)) {
resolve({
number: mmi.dialNumber,
clirMode: this._getTemporaryCLIRMode(mmi.procedure)
});
} else {
reject(DIAL_ERROR_BAD_NUMBER);
}
});
});
},
cachedDialRequest: null,
isDialing: false,
dial: function(aClientId, aNumber, aIsDialEmergency, aCallback) {
if (DEBUG) debug("Dialing " + (aIsDialEmergency ? "emergency " : "") + aNumber);
if (this.isDialing) {
// We don't try to be too clever here, as the phone is probably in the
// locked state. Let's just check if it's a number without normalizing
if (!aIsDialEmergency) {
aNumber = gPhoneNumberUtils.normalize(aNumber);
}
// Validate the number.
// Note: isPlainPhoneNumber also accepts USSD and SS numbers
if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
if (DEBUG) debug("Error: Number '" + aNumber + "' is not viable. Drop.");
aCallback.notifyDialError(DIAL_ERROR_BAD_NUMBER);
return;
}
let mmi = this._parseMMI(aNumber, this._hasCalls(aClientId));
if (!mmi) {
this._dialCall(aClientId,
{ number: aNumber,
isDialEmergency: aIsDialEmergency }, aCallback);
} else if (this._isTemporaryCLIR(mmi)) {
this._dialCall(aClientId,
{ number: mmi.dialNumber,
clirMode: this._getTemporaryCLIRMode(mmi.procedure),
isDialEmergency: aIsDialEmergency }, aCallback);
} else {
// Reject MMI code from dialEmergency api.
if (aIsDialEmergency) {
aCallback.notifyDialError(DIAL_ERROR_BAD_NUMBER);
return;
}
this._dialMMI(aClientId, mmi, aCallback);
}
},
/**
* @param aOptions.number
* @param aOptions.clirMode (optional)
* @param aOptions.isDialEmergency
*/
_dialCall: function(aClientId, aOptions, aCallback) {
if (this._isDialing) {
if (DEBUG) debug("Error: Already has a dialing call.");
aCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR);
return;
@ -549,63 +616,43 @@ TelephonyService.prototype = {
return;
}
// We don't try to be too clever here, as the phone is probably in the
// locked state. Let's just check if it's a number without normalizing
if (!aIsDialEmergency) {
aNumber = gPhoneNumberUtils.normalize(aNumber);
}
// Validate the number.
// Note: isPlainPhoneNumber also accepts USSD and SS numbers
if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
if (DEBUG) debug("Error: Number '" + aNumber + "' is not viable. Drop.");
aCallback.notifyDialError(DIAL_ERROR_BAD_NUMBER);
return;
}
this._composeDialRequest(aClientId, aNumber).then(options => {
options.isEmergency = this._isEmergencyNumber(options.number);
options.isDialEmergency = aIsDialEmergency;
if (options.isEmergency) {
// Automatically select a proper clientId for emergency call.
aClientId = gRadioInterfaceLayer.getClientIdForEmergencyCall() ;
if (aClientId === -1) {
if (DEBUG) debug("Error: No client is avaialble for emergency call.");
aCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR);
return;
}
aOptions.isEmergency = this._isEmergencyNumber(aOptions.number);
if (aOptions.isEmergency) {
// Automatically select a proper clientId for emergency call.
aClientId = gRadioInterfaceLayer.getClientIdForEmergencyCall() ;
if (aClientId === -1) {
if (DEBUG) debug("Error: No client is avaialble for emergency call.");
aCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR);
return;
}
}
// Before we dial, we have to hold the active call first.
let activeCall = this._getOneActiveCall(aClientId);
if (!activeCall) {
this._dialInternal(aClientId, options, aCallback);
// Before we dial, we have to hold the active call first.
let activeCall = this._getOneActiveCall(aClientId);
if (!activeCall) {
this._sendDialCallRequest(aClientId, aOptions, aCallback);
} else {
if (DEBUG) debug("There is an active call. Hold it first before dial.");
this._cachedDialRequest = {
clientId: aClientId,
options: aOptions,
callback: aCallback
};
if (activeCall.isConference) {
this.holdConference(aClientId);
} else {
if (DEBUG) debug("There is an active call. Hold it first before dial.");
this.cachedDialRequest = {
clientId: aClientId,
options: options,
callback: aCallback
};
if (activeCall.isConference) {
this.holdConference(aClientId);
} else {
this.holdCall(aClientId, activeCall.callIndex);
}
this.holdCall(aClientId, activeCall.callIndex);
}
}, cause => {
aCallback.notifyDialError(DIAL_ERROR_BAD_NUMBER);
});
}
},
_dialInternal: function(aClientId, aOptions, aCallback) {
this.isDialing = true;
_sendDialCallRequest: function(aClientId, aOptions, aCallback) {
this._isDialing = true;
this._sendToRilWorker(aClientId, "dial", aOptions, response => {
this.isDialing = false;
this._isDialing = false;
if (!response.success) {
aCallback.notifyDialError(response.errorMsg);
@ -616,15 +663,247 @@ TelephonyService.prototype = {
Object.keys(this._currentCalls[aClientId])[0];
if (currentCdmaCallIndex == null) {
aCallback.notifyDialSuccess(response.callIndex, response.number);
aCallback.notifyDialCallSuccess(response.callIndex, response.number);
} else {
// RIL doesn't hold the 2nd call. We create one by ourselves.
aCallback.notifyDialSuccess(CDMA_SECOND_CALL_INDEX, response.number);
aCallback.notifyDialCallSuccess(CDMA_SECOND_CALL_INDEX, response.number);
this._addCdmaChildCall(aClientId, response.number, currentCdmaCallIndex);
}
});
},
/**
* @param aMmi
* Parsed MMI structure.
*/
_dialMMI: function(aClientId, aMmi, aCallback) {
let mmiServiceCode = aMmi ?
this._serviceCodeToKeyString(aMmi.serviceCode) : RIL.MMI_KS_SC_USSD;
aCallback.notifyDialMMI(mmiServiceCode);
this._sendToRilWorker(aClientId, "sendMMI", { mmi: aMmi }, response => {
if (DEBUG) debug("MMI response: " + JSON.stringify(response));
if (!response.success) {
if (response.additionalInformation != null) {
aCallback.notifyDialMMIErrorWithInfo(response.errorMsg,
response.additionalInformation);
} else {
aCallback.notifyDialMMIError(response.errorMsg);
}
return;
}
// We expect to have an IMEI at this point if the request was supposed
// to query for the IMEI, so getting a successful reply from the RIL
// without containing an actual IMEI number is considered an error.
if (mmiServiceCode === RIL.MMI_KS_SC_IMEI &&
!response.statusMessage) {
aCallback.notifyDialMMIError(RIL.GECKO_ERROR_GENERIC_FAILURE);
return;
}
// MMI query call forwarding options request returns a set of rules that
// will be exposed in the form of an array of MozCallForwardingOptions
// instances.
if (mmiServiceCode === RIL.MMI_KS_SC_CALL_FORWARDING) {
if (response.isSetCallForward) {
gGonkMobileConnectionService.notifyCFStateChanged(aClientId,
response.action,
response.reason,
response.number,
response.timeSeconds,
response.serviceClass);
}
if (response.additionalInformation != null) {
response.additionalInformation =
this._rulesToCallForwardingOptions(response.additionalInformation);
}
}
let result = new MMIResult(mmiServiceCode, response);
aCallback.notifyDialMMISuccess(result);
});
},
/**
* Build the regex to parse MMI string. TS.22.030
*
* The resulting groups after matching will be:
* 1 = full MMI string that might be used as a USSD request.
* 2 = MMI procedure.
* 3 = Service code.
* 4 = SIA.
* 5 = SIB.
* 6 = SIC.
* 7 = Password registration.
* 8 = Dialing number.
*/
_buildMMIRegExp: function() {
// The general structure of the codes is as follows:
// - Activation (*SC*SI#).
// - Deactivation (#SC*SI#).
// - Interrogation (*#SC*SI#).
// - Registration (**SC*SI#).
// - Erasure (##SC*SI#).
//
// where SC = Service Code (2 or 3 digits) and SI = Supplementary Info
// (variable length).
// Procedure, which could be *, #, *#, **, ##
let procedure = "(\\*[*#]?|##?)";
// Service code, which is a 2 or 3 digits that uniquely specifies the
// Supplementary Service associated with the MMI code.
let serviceCode = "(\\d{2,3})";
// Supplementary Information SIA, SIB and SIC. SIA may comprise e.g. a PIN
// code or Directory Number, SIB may be used to specify the tele or bearer
// service and SIC to specify the value of the "No Reply Condition Timer".
// Where a particular service request does not require any SI, "*SI" is
// not entered. The use of SIA, SIB and SIC is optional and shall be
// entered in any of the following formats:
// - *SIA*SIB*SIC#
// - *SIA*SIB#
// - *SIA**SIC#
// - *SIA#
// - **SIB*SIC#
// - ***SIC#
//
// Also catch the additional NEW_PASSWORD for the case of a password
// registration procedure. Ex:
// - * 03 * ZZ * OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - ** 03 * ZZ * OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - * 03 ** OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - ** 03 ** OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
let si = "\\*([^*#]*)";
let allSi = "";
for (let i = 0; i < 4; ++i) {
allSi = "(?:" + si + allSi + ")?";
}
let fullmmi = "(" + procedure + serviceCode + allSi + "#)";
// Dial string after the #.
let dialString = "([^#]*)";
return new RegExp(fullmmi + dialString);
},
/**
* Provide the regex to parse MMI string.
*/
_getMMIRegExp: function() {
if (!this._mmiRegExp) {
this._mmiRegExp = this._buildMMIRegExp();
}
return this._mmiRegExp;
},
/**
* Helper to parse # string. TS.22.030 Figure 3.5.3.2.
*/
_isPoundString: function(aMmiString) {
return (aMmiString.charAt(aMmiString.length - 1) === "#");
},
/**
* Helper to parse short string. TS.22.030 Figure 3.5.3.2.
*/
_isShortString: function(aMmiString, hasCalls) {
if (aMmiString.length > 2) {
return false;
}
if (hasCalls) {
return true;
}
// Input string is
// - emergency number or
// - 2 digits starting with a "1"
if (this._isEmergencyNumber(aMmiString) ||
(aMmiString.length == 2) && (aMmiString.charAt(0) === '1')) {
return false;
}
return true;
},
/**
* Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
*/
_parseMMI: function(aMmiString, hasCalls) {
if (!aMmiString) {
return null;
}
let matches = this._getMMIRegExp().exec(aMmiString);
if (matches) {
return {
fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI],
procedure: matches[MMI_MATCH_GROUP_PROCEDURE],
serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE],
sia: matches[MMI_MATCH_GROUP_SIA],
sib: matches[MMI_MATCH_GROUP_SIB],
sic: matches[MMI_MATCH_GROUP_SIC],
pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM],
dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER]
};
}
if (this._isPoundString(aMmiString) ||
this._isShortString(aMmiString, hasCalls)) {
return {
fullMMI: aMmiString
};
}
return null;
},
_serviceCodeToKeyString: function(aServiceCode) {
switch (aServiceCode) {
case RIL.MMI_SC_CFU:
case RIL.MMI_SC_CF_BUSY:
case RIL.MMI_SC_CF_NO_REPLY:
case RIL.MMI_SC_CF_NOT_REACHABLE:
case RIL.MMI_SC_CF_ALL:
case RIL.MMI_SC_CF_ALL_CONDITIONAL:
return RIL.MMI_KS_SC_CALL_FORWARDING;
case RIL.MMI_SC_PIN:
return RIL.MMI_KS_SC_PIN;
case RIL.MMI_SC_PIN2:
return RIL.MMI_KS_SC_PIN2;
case RIL.MMI_SC_PUK:
return RIL.MMI_KS_SC_PUK;
case RIL.MMI_SC_PUK2:
return RIL.MMI_KS_SC_PUK2;
case RIL.MMI_SC_IMEI:
return RIL.MMI_KS_SC_IMEI;
case RIL.MMI_SC_CLIP:
return RIL.MMI_KS_SC_CLIP;
case RIL.MMI_SC_CLIR:
return RIL.MMI_KS_SC_CLIR;
case RIL.MMI_SC_BAOC:
case RIL.MMI_SC_BAOIC:
case RIL.MMI_SC_BAOICxH:
case RIL.MMI_SC_BAIC:
case RIL.MMI_SC_BAICr:
case RIL.MMI_SC_BA_ALL:
case RIL.MMI_SC_BA_MO:
case RIL.MMI_SC_BA_MT:
return RIL.MMI_KS_SC_CALL_BARRING;
case RIL.MMI_SC_CALL_WAITING:
return RIL.MMI_KS_SC_CALL_WAITING;
default:
return RIL.MMI_KS_SC_USSD;
}
},
hangUp: function(aClientId, aCallIndex) {
let parentId = this._currentCalls[aClientId][aCallIndex].parentId;
if (parentId) {
@ -932,12 +1211,12 @@ TelephonyService.prototype = {
}
// Handle cached dial request.
if (this.cachedDialRequest && !this._getOneActiveCall()) {
if (this._cachedDialRequest && !this._getOneActiveCall()) {
if (DEBUG) debug("All calls held. Perform the cached dial request.");
let request = this.cachedDialRequest;
this._dialInternal(request.clientId, request.options, request.callback);
this.cachedDialRequest = null;
let request = this._cachedDialRequest;
this._sendDialCallRequest(request.clientId, request.options, request.callback);
this._cachedDialRequest = null;
}
this._notifyAllListeners("callStateChanged", [aClientId,
@ -987,6 +1266,11 @@ TelephonyService.prototype = {
this._notifyAllListeners("conferenceCallStateChanged", [aState]);
},
dialMMI: function(aClientId, aMmiString, aCallback) {
let mmi = this._parseMMI(aMmiString, this._hasCalls(aClientId));
this._dialMMI(aClientId, mmi, aCallback);
},
/**
* nsIObserver interface.
*/

View File

@ -16,15 +16,37 @@ struct EnumerateCallsResponse
// empty.
};
struct DialResponse
struct DialResponseError
{
// empty.
nsString name;
};
struct DialResponseCallSuccess
{
uint32_t callIndex;
nsString number;
};
struct DialResponseMMISuccess
{
nsString statusMessage;
AdditionalInformation additionalInformation;
};
struct DialResponseMMIError
{
nsString name;
AdditionalInformation additionalInformation;
};
union IPCTelephonyResponse
{
EnumerateCallsResponse;
DialResponse;
// dial
DialResponseError;
DialResponseCallSuccess;
DialResponseMMISuccess;
DialResponseMMIError;
};
protocol PTelephonyRequest
@ -34,9 +56,7 @@ protocol PTelephonyRequest
child:
NotifyEnumerateCallState(uint32_t aClientId, IPCCallStateData aData);
NotifyDialError(nsString aError);
NotifyDialSuccess(uint32_t aCallIndex, nsString aNumber);
NotifyDialMMI(nsString aServiceCode);
/**
* Sent when the asynchronous request has completed.

View File

@ -4,6 +4,8 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TelephonyChild.h"
#include "mozilla/dom/telephony/TelephonyCallback.h"
#include "TelephonyIPCService.h"
USING_TELEPHONY_NAMESPACE
@ -145,9 +147,14 @@ TelephonyRequestChild::Recv__delete__(const IPCTelephonyResponse& aResponse)
case IPCTelephonyResponse::TEnumerateCallsResponse:
mListener->EnumerateCallStateComplete();
break;
case IPCTelephonyResponse::TDialResponse:
// Do nothing.
break;
case IPCTelephonyResponse::TDialResponseError:
return DoResponse(aResponse.get_DialResponseError());
case IPCTelephonyResponse::TDialResponseCallSuccess:
return DoResponse(aResponse.get_DialResponseCallSuccess());
case IPCTelephonyResponse::TDialResponseMMISuccess:
return DoResponse(aResponse.get_DialResponseMMISuccess());
case IPCTelephonyResponse::TDialResponseMMIError:
return DoResponse(aResponse.get_DialResponseMMIError());
default:
MOZ_CRASH("Unknown type!");
}
@ -177,20 +184,79 @@ TelephonyRequestChild::RecvNotifyEnumerateCallState(const uint32_t& aClientId,
}
bool
TelephonyRequestChild::RecvNotifyDialError(const nsString& aError)
TelephonyRequestChild::RecvNotifyDialMMI(const nsString& aServiceCode)
{
MOZ_ASSERT(mCallback);
mCallback->NotifyDialError(aError);
mCallback->NotifyDialMMI(aServiceCode);
return true;
}
bool
TelephonyRequestChild::RecvNotifyDialSuccess(const uint32_t& aCallIndex,
const nsString& aNumber)
TelephonyRequestChild::DoResponse(const DialResponseError& aResponse)
{
MOZ_ASSERT(mCallback);
mCallback->NotifyDialError(aResponse.name());
return true;
}
bool
TelephonyRequestChild::DoResponse(const DialResponseCallSuccess& aResponse)
{
MOZ_ASSERT(mCallback);
mCallback->NotifyDialCallSuccess(aResponse.callIndex(), aResponse.number());
return true;
}
bool
TelephonyRequestChild::DoResponse(const DialResponseMMISuccess& aResponse)
{
MOZ_ASSERT(mCallback);
mCallback->NotifyDialSuccess(aCallIndex, aNumber);
// FIXME: Need to overload NotifyDialMMISuccess in the IDL. mCallback is not
// necessarily an instance of TelephonyCallback.
nsRefPtr<TelephonyCallback> callback = static_cast<TelephonyCallback*>(mCallback.get());
nsAutoString statusMessage(aResponse.statusMessage());
AdditionalInformation info(aResponse.additionalInformation());
switch (info.type()) {
case AdditionalInformation::Tvoid_t:
callback->NotifyDialMMISuccess(statusMessage);
break;
case AdditionalInformation::TArrayOfnsString:
callback->NotifyDialMMISuccess(statusMessage, info.get_ArrayOfnsString());
break;
case AdditionalInformation::TArrayOfMozCallForwardingOptions:
callback->NotifyDialMMISuccess(statusMessage, info.get_ArrayOfMozCallForwardingOptions());
break;
default:
MOZ_CRASH("Received invalid type!");
break;
}
return true;
}
bool
TelephonyRequestChild::DoResponse(const DialResponseMMIError& aResponse)
{
MOZ_ASSERT(mCallback);
nsAutoString name(aResponse.name());
AdditionalInformation info(aResponse.additionalInformation());
switch (info.type()) {
case AdditionalInformation::Tvoid_t:
mCallback->NotifyDialMMIError(name);
break;
case AdditionalInformation::Tuint16_t:
mCallback->NotifyDialMMIErrorWithInfo(name, info.get_uint16_t());
break;
default:
MOZ_CRASH("Received invalid type!");
break;
}
return true;
}

View File

@ -80,13 +80,21 @@ protected:
const IPCCallStateData& aData) MOZ_OVERRIDE;
virtual bool
RecvNotifyDialError(const nsString& aError) MOZ_OVERRIDE;
virtual bool
RecvNotifyDialSuccess(const uint32_t& aCallIndex,
const nsString& aNumber) MOZ_OVERRIDE;
RecvNotifyDialMMI(const nsString& aServiceCode) MOZ_OVERRIDE;
private:
bool
DoResponse(const DialResponseError& aResponse);
bool
DoResponse(const DialResponseCallSuccess& aResponse);
bool
DoResponse(const DialResponseMMISuccess& aResponse);
bool
DoResponse(const DialResponseMMIError& aResponse);
nsCOMPtr<nsITelephonyListener> mListener;
nsCOMPtr<nsITelephonyCallback> mCallback;
};

View File

@ -434,6 +434,14 @@ TelephonyRequestParent::DoRequest(const DialRequest& aRequest)
return true;
}
nsresult
TelephonyRequestParent::SendResponse(const IPCTelephonyResponse& aResponse)
{
NS_ENSURE_TRUE(!mActorDestroyed, NS_ERROR_FAILURE);
return Send__delete__(this, aResponse) ? NS_OK : NS_ERROR_FAILURE;
}
// nsITelephonyListener
NS_IMETHODIMP
@ -527,20 +535,105 @@ TelephonyRequestParent::SupplementaryServiceNotification(uint32_t aClientId,
// nsITelephonyCallback
NS_IMETHODIMP
TelephonyRequestParent::NotifyDialError(const nsAString& aError)
TelephonyRequestParent::NotifyDialMMI(const nsAString& aServiceCode)
{
NS_ENSURE_TRUE(!mActorDestroyed, NS_ERROR_FAILURE);
return (SendNotifyDialError(nsString(aError)) &&
Send__delete__(this, DialResponse())) ? NS_OK : NS_ERROR_FAILURE;
return SendNotifyDialMMI(nsAutoString(aServiceCode)) ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
TelephonyRequestParent::NotifyDialSuccess(uint32_t aCallIndex,
const nsAString& aNumber)
TelephonyRequestParent::NotifyDialError(const nsAString& aError)
{
NS_ENSURE_TRUE(!mActorDestroyed, NS_ERROR_FAILURE);
return (SendNotifyDialSuccess(aCallIndex, nsString(aNumber)) &&
Send__delete__(this, DialResponse())) ? NS_OK : NS_ERROR_FAILURE;
return SendResponse(DialResponseError(nsAutoString(aError)));
}
NS_IMETHODIMP
TelephonyRequestParent::NotifyDialCallSuccess(uint32_t aCallIndex,
const nsAString& aNumber)
{
return SendResponse(DialResponseCallSuccess(aCallIndex, nsAutoString(aNumber)));
}
NS_IMETHODIMP
TelephonyRequestParent::NotifyDialMMISuccess(JS::Handle<JS::Value> aResult)
{
AutoSafeJSContext cx;
RootedDictionary<MozMMIResult> result(cx);
if (!result.Init(cx, aResult)) {
return NS_ERROR_TYPE_ERR;
}
// No additionInformation passed
if (!result.mAdditionalInformation.WasPassed()) {
return SendResponse(DialResponseMMISuccess(result.mStatusMessage,
AdditionalInformation(mozilla::void_t())));
}
OwningUnsignedShortOrObject& info = result.mAdditionalInformation.Value();
// Currently, we could only accept the following values for |info|:
// 1. array of string
// 2. array of MozCallForwardingOptions
if (!info.IsObject()) {
return NS_ERROR_TYPE_ERR;
}
JS::Rooted<JSObject*> object(cx, info.GetAsObject());
JS::Rooted<JS::Value> value(cx);
uint32_t length;
if (!JS_IsArrayObject(cx, object) ||
!JS_GetArrayLength(cx, object, &length) || length <= 0 ||
// Check first element to decide the format of array.
!JS_GetElement(cx, object, 0, &value)) {
return NS_ERROR_TYPE_ERR;
}
if (value.isString()) {
// String[]
nsTArray<nsString> infos;
for (uint32_t i = 0; i < length; i++) {
nsAutoJSString str;
if (!JS_GetElement(cx, object, i, &value) || !value.isString() ||
!str.init(cx, value.toString())) {
return NS_ERROR_TYPE_ERR;
}
infos.AppendElement(str);
}
return SendResponse(DialResponseMMISuccess(result.mStatusMessage,
AdditionalInformation(infos)));
} else {
// IPC::MozCallForwardingOptions[]
nsTArray<IPC::MozCallForwardingOptions> infos;
for (uint32_t i = 0; i < length; i++) {
IPC::MozCallForwardingOptions info;
if (!JS_GetElement(cx, object, i, &value) || !info.Init(cx, value)) {
return NS_ERROR_TYPE_ERR;
}
infos.AppendElement(info);
}
return SendResponse(DialResponseMMISuccess(result.mStatusMessage,
AdditionalInformation(infos)));
}
}
NS_IMETHODIMP
TelephonyRequestParent::NotifyDialMMIError(const nsAString& aError)
{
return SendResponse(DialResponseMMIError(nsAutoString(aError),
AdditionalInformation(mozilla::void_t())));
}
NS_IMETHODIMP
TelephonyRequestParent::NotifyDialMMIErrorWithInfo(const nsAString& aError,
uint16_t aInfo)
{
return SendResponse(DialResponseMMIError(nsAutoString(aError),
AdditionalInformation(aInfo)));
}

View File

@ -114,6 +114,9 @@ protected:
virtual void
ActorDestroy(ActorDestroyReason why);
nsresult
SendResponse(const IPCTelephonyResponse& aResponse);
private:
bool mActorDestroyed;

View File

@ -4,6 +4,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
using struct IPC::MozCallForwardingOptions from "mozilla/dom/mobileconnection/MobileConnectionIPCSerializer.h";
namespace mozilla {
namespace dom {
namespace telephony {
@ -31,6 +34,13 @@ struct IPCCdmaWaitingCallData
uint16_t namePresentation;
};
union AdditionalInformation {
void_t;
uint16_t;
nsString[];
MozCallForwardingOptions[];
};
} /* namespace telephony */
} /* namespace dom */
} /* namespace mozilla */

View File

@ -21,6 +21,7 @@ EXPORTS.mozilla.dom += [
EXPORTS.mozilla.dom.telephony += [
'ipc/TelephonyChild.h',
'ipc/TelephonyParent.h',
'TelephonyCallback.h',
'TelephonyCommon.h',
]
@ -31,6 +32,7 @@ UNIFIED_SOURCES += [
'ipc/TelephonyParent.cpp',
'Telephony.cpp',
'TelephonyCall.cpp',
'TelephonyCallback.cpp',
'TelephonyCallGroup.cpp',
'TelephonyCallId.cpp',
]
@ -42,6 +44,7 @@ IPDL_SOURCES += [
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
XPIDL_SOURCES += [
'nsIGonkTelephonyService.idl',
]

View File

@ -26,4 +26,7 @@ interface nsIGonkTelephonyService : nsITelephonyService
in AString notification);
void notifyConferenceCallStateChanged(in short state);
void dialMMI(in unsigned long clientId, in AString mmiString,
in nsITelephonyCallback callback);
};

View File

@ -176,20 +176,55 @@ interface nsITelephonyListener : nsISupports
in AString message);
};
[scriptable, uuid(b3b2b0b0-357f-4efb-bc9f-ca2b2d5686a1)]
/**
* A callback interface for handling asynchronous response of Telephony.dial().
*/
[scriptable, uuid(67533db3-cd38-475c-a774-8d0bbf9169fb)]
interface nsITelephonyCallback : nsISupports
{
/**
* Called when a dial request is treated as an MMI code and it is about to
* process the request.
*
* @param serviceCode
* MMI service code key string that defined in MMI_KS_SC_*
*/
void notifyDialMMI(in AString serviceCode);
/**
* Called when a dial request fails.
*
* @param error
* Error from RIL.
*/
void notifyDialError(in AString error);
/**
* Called when a dial request succeeds.
* Called when a dial request is treated as a call setup and the result
* succeeds.
*
* @param callIndex
* Call index from RIL.
* @param number
* Dialed out phone number (ex: Temporary CLIR prefix will be removed)
*/
void notifyDialSuccess(in unsigned long callIndex, in AString number);
void notifyDialCallSuccess(in unsigned long callIndex, in AString number);
/**
* Called when a MMI code request succeeds.
* The function should only be called after notifyDialMMI.
*
* @param result
* Result of the request. See MozMMIResult.
*/
void notifyDialMMISuccess(in jsval result);
/**
* Called when a MMI code request fails.
* The function should only be called after notifyDialMMI.
*/
void notifyDialMMIError(in AString error);
void notifyDialMMIErrorWithInfo(in AString error, in unsigned short info);
};
%{C++

View File

@ -1302,3 +1302,25 @@ function startDSDSTest(test) {
finish();
}
}
function sendMMI(aMmi) {
let deferred = Promise.defer();
telephony.dial(aMmi)
.then(request => {
ok(request instanceof DOMRequest,
"request is instanceof " + request.constructor);
request.addEventListener("success", function(event) {
deferred.resolve(request.result);
});
request.addEventListener("error", function(event) {
deferred.reject(request.error);
});
}, cause => {
deferred.reject(cause);
});
return deferred.promise;
}

View File

@ -63,5 +63,7 @@ disabled = Bug 821958
[test_incomingcall_phonestate_speaker.js]
[test_temporary_clir.js]
[test_outgoing_error_state.js]
[test_mmi_code.js]
[test_outgoing_auto_hold.js]
[test_mmi.js]
[test_mmi_change_pin.js]
[test_mmi_call_forwarding.js]

View File

@ -0,0 +1,30 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
function testGettingIMEI() {
log("Test *#06# ...");
let MMI_CODE = "*#06#";
return sendMMI(MMI_CODE)
.then(function resolve(aResult) {
ok(true, MMI_CODE + " success");
is(aResult.serviceCode, "scImei", "Service code IMEI");
// IMEI is hardcoded as "000000000000000".
// See it here {B2G_HOME}/external/qemu/telephony/android_modem.c
// (The result of +CGSN).
is(aResult.statusMessage, "000000000000000", "Emulator IMEI");
is(aResult.additionalInformation, undefined, "No additional information");
}, function reject() {
ok(false, MMI_CODE + " should not fail");
});
}
// Start test
startTest(function() {
Promise.resolve()
.then(() => testGettingIMEI())
.then(finish);
});

View File

@ -0,0 +1,221 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
let connection;
/**
* Wait for one named MobileConnection event.
*
* Resolve if that named event occurs. Never reject.
*
* Fulfill params: the DOMEvent passed.
*
* @param aEventName
* A string event name.
*
* @return A deferred promise.
*/
function waitForManagerEvent(aEventName) {
let deferred = Promise.defer();
connection.addEventListener(aEventName, function onevent(aEvent) {
connection.removeEventListener(aEventName, onevent);
ok(true, "MobileConnection event '" + aEventName + "' got.");
deferred.resolve(aEvent);
});
return deferred.promise;
}
/**
* Wrap DOMRequest onsuccess/onerror events to Promise resolve/reject.
*
* Fulfill params: A DOMEvent.
* Reject params: A DOMEvent.
*
* @param aRequest
* A DOMRequest instance.
*
* @return A deferred promise.
*/
function wrapDomRequestAsPromise(aRequest) {
let deferred = Promise.defer();
ok(aRequest instanceof DOMRequest,
"aRequest is instanceof " + aRequest.constructor);
aRequest.addEventListener("success", function(aEvent) {
deferred.resolve(aEvent);
});
aRequest.addEventListener("error", function(aEvent) {
deferred.reject(aEvent);
});
return deferred.promise;
}
/**
* Configures call forward options.
*
* Fulfill params: (none)
* Reject params:
* 'RadioNotAvailable', 'RequestNotSupported', 'InvalidParameter', or
* 'GenericFailure'.
*
* @param aOptions
* A MozCallForwardingOptions.
*
* @return A deferred promise.
*/
function setCallForwardingOption(aOptions) {
let request = connection.setCallForwardingOption(aOptions);
return wrapDomRequestAsPromise(request)
.then(null, () => { throw request.error; });
}
const TEST_DATA = [
{
reason: MozMobileConnection.CALL_FORWARD_REASON_UNCONDITIONAL,
number: "+886912345678",
serviceClass: MozMobileConnection.ICC_SERVICE_CLASS_VOICE,
timeSeconds: 5
}, {
reason: MozMobileConnection.CALL_FORWARD_REASON_MOBILE_BUSY,
number: "0912345678",
serviceClass: MozMobileConnection.ICC_SERVICE_CLASS_VOICE,
timeSeconds: 10
}, {
reason: MozMobileConnection.CALL_FORWARD_REASON_NO_REPLY,
number: "+886987654321",
serviceClass: MozMobileConnection.ICC_SERVICE_CLASS_VOICE,
timeSeconds: 15
}, {
reason: MozMobileConnection.CALL_FORWARD_REASON_NOT_REACHABLE,
number: "+0987654321",
serviceClass: MozMobileConnection.ICC_SERVICE_CLASS_VOICE,
timeSeconds: 20
}
];
// Please see TS 22.030 Annex B
const CF_REASON_TO_MMI = {
/* CALL_FORWARD_REASON_UNCONDITIONAL */
0: "21",
/* CALL_FORWARD_REASON_MOBILE_BUSY */
1: "67",
/* CALL_FORWARD_REASON_NO_REPLY */
2: "61",
/* CALL_FORWARD_REASON_NOT_REACHABLE */
3: "62",
/* CALL_FORWARD_REASON_ALL_CALL_FORWARDING */
4: "002",
/* CALL_FORWARD_REASON_ALL_CONDITIONAL_CALL_FORWARDING */
5: "004"
};
// Please see TS 22.030 Annex C
const SERVICE_CLASS_TO_MMI = {
/* ICC_SERVICE_CLASS_VOICE */
1: "11"
};
function testSetCallForwarding(aData) {
// Registration: **SC*SIA*SIB*SIC#
let MMI_CODE = "**" + CF_REASON_TO_MMI[aData.reason] + "*" + aData.number +
"*" + SERVICE_CLASS_TO_MMI[aData.serviceClass] +
"*" + aData.timeSeconds + "#";
log("Test " + MMI_CODE);
let promises = [];
// Check cfstatechange event.
promises.push(waitForManagerEvent("cfstatechange").then(function(aEvent) {
is(aEvent.success, true, "check success");
is(aEvent.action, MozMobileConnection.CALL_FORWARD_ACTION_REGISTRATION,
"check action");
is(aEvent.reason, aData.reason, "check reason");
is(aEvent.number, aData.number, "check number");
is(aEvent.timeSeconds, aData.timeSeconds, "check timeSeconds");
is(aEvent.serviceClass, aData.serviceClass, "check serviceClass");
}));
// Check DOMRequest's result.
promises.push(sendMMI(MMI_CODE)
.then(function resolve(aResult) {
is(aResult.serviceCode, "scCallForwarding", "Check service code");
is(aResult.statusMessage, "smServiceRegistered", "Check status message");
is(aResult.additionalInformation, undefined, "Check additional information");
}, function reject(aError) {
ok(false, "got '" + aError.name + "' error");
}));
return Promise.all(promises);
}
function testGetCallForwarding(aExpectedData) {
// Interrogation: *#SC#
let MMI_CODE = "*#" + CF_REASON_TO_MMI[aExpectedData.reason] + "#";
log("Test " + MMI_CODE);
return sendMMI(MMI_CODE)
.then(function resolve(aResult) {
is(aResult.serviceCode, "scCallForwarding", "Check service code");
is(aResult.statusMessage, "smServiceInterrogated", "Check status message");
is(Array.isArray(aResult.additionalInformation), true,
"additionalInformation should be an array");
for (let i = 0; i < aResult.additionalInformation.length; i++) {
let result = aResult.additionalInformation[i];
// Only need to check the result containing the serviceClass that we are
// interested in.
if (!(result.serviceClass & aExpectedData.serviceClass)) {
continue;
}
is(result.active, true, "check active");
is(result.reason, aExpectedData.reason, "check reason");
is(result.number, aExpectedData.number, "check number");
is(result.timeSeconds, aExpectedData.timeSeconds, "check timeSeconds");
}
}, function reject(aError) {
ok(false, MMI_CODE + " got error: " + aError.name);
});
}
function clearAllCallForwardingSettings() {
log("Clear all call forwarding settings");
let promise = Promise.resolve();
for (let reason = MozMobileConnection.CALL_FORWARD_REASON_UNCONDITIONAL;
reason <= MozMobileConnection.CALL_FORWARD_REASON_ALL_CONDITIONAL_CALL_FORWARDING;
reason++) {
let options = {
reason: reason,
action: MozMobileConnection.CALL_FORWARD_ACTION_ERASURE
};
// Emulator doesn't support CALL_FORWARD_REASON_ALL_* yet, we catch the
// reject here in order to avoid impact the test result.
promise =
promise.then(() => setCallForwardingOption(options).then(null, () => {}));
}
return promise;
}
// Start tests
startTestWithPermissions(['mobileconnection'], function() {
connection = navigator.mozMobileConnections[0];
let promise = Promise.resolve();
for (let i = 0; i < TEST_DATA.length; i++) {
let data = TEST_DATA[i];
promise = promise.then(() => testSetCallForwarding(data))
.then(() => testGetCallForwarding(data));
}
// reset call forwarding settings.
return promise.then(null, () => { ok(false, "promise reject during test"); })
.then(() => clearAllCallForwardingSettings())
.then(finish);
});

View File

@ -0,0 +1,111 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
// PIN is hardcoded as "0000" by default.
// See it here {B2G_HOME}/external/qemu/telephony/sim_card.c,
// in asimcard_create().
const TEST_DATA = [
// Test passing no pin.
{
pin: "",
newPin: "0000",
newPinAgain: "1111",
expectedError: {
name: "emMmiError",
additionalInformation: null
}
},
// Test passing no newPin.
{
pin: "0000",
newPin: "",
newPinAgain: "",
expectedError: {
name: "emMmiError",
additionalInformation: null
}
},
// Test passing mismatched newPin.
{
pin: "0000",
newPin: "0000",
newPinAgain: "1111",
expectedError: {
name: "emMmiErrorMismatchPin",
additionalInformation: null
}
},
// Test passing invalid pin (< 4 digit).
{
pin: "000",
newPin: "0000",
newPinAgain: "0000",
expectedError: {
name: "emMmiErrorInvalidPin",
additionalInformation: null
}
},
// Test passing invalid newPin (> 8 digit).
{
pin: "0000",
newPin: "000000000",
newPinAgain: "000000000",
expectedError: {
name: "emMmiErrorInvalidPin",
additionalInformation: null
}
},
// Test passing incorrect pin.
{
pin: "1234",
newPin: "0000",
newPinAgain: "0000",
expectedError: {
name: "emMmiErrorBadPin",
// The default pin retries is 3, failed once becomes to 2
additionalInformation: 2
}
},
// Test changing pin successfully (Reset the retries).
{
pin: "0000",
newPin: "0000",
newPinAgain: "0000"
}
];
function testChangePin(aPin, aNewPin, aNewPinAgain, aExpectedError) {
let MMI_CODE = "**04*" + aPin + "*" + aNewPin + "*" + aNewPinAgain + "#";
log("Test " + MMI_CODE);
return sendMMI(MMI_CODE)
.then(function resolve(aResult) {
ok(!aExpectedError, MMI_CODE + " success");
is(aResult.serviceCode, "scPin", "Check service code");
is(aResult.statusMessage, "smPinChanged", "Check status message");
is(aResult.additionalInformation, undefined, "Check additional information");
}, function reject(aError) {
ok(aExpectedError, MMI_CODE + " fail");
is(aError.name, aExpectedError.name, "Check name");
is(aError.message, "", "Check message");
is(aError.serviceCode, "scPin", "Check service code");
is(aError.additionalInformation, aExpectedError.additionalInformation,
"Check additional information");
});
}
// Start test
startTest(function() {
let promise = Promise.resolve();
for (let i = 0; i < TEST_DATA.length; i++) {
let data = TEST_DATA[i];
promise = promise.then(() => testChangePin(data.pin,
data.newPin,
data.newPinAgain,
data.expectedError));
}
return promise.then(finish);
});

View File

@ -1,23 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
let number = "";
function dialMMI() {
telephony.dial("*#06#").then(null, cause => {
log("Received promise 'reject'");
is(telephony.active, null);
is(telephony.calls.length, 0);
is(cause, "BadNumberError");
finish();
});
}
startTest(function() {
dialMMI();
});

View File

@ -0,0 +1,9 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
let subscriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);

View File

@ -0,0 +1,308 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
let NS = {};
subscriptLoader.loadSubScript("resource://gre/components/TelephonyService.js",
NS);
function run_test() {
run_next_test();
}
function parseMMI(mmiString) {
return NS.TelephonyService.prototype._parseMMI(mmiString, false);
}
add_test(function test_parseMMI_empty() {
let mmi = parseMMI("");
equal(mmi, null);
run_next_test();
});
add_test(function test_parseMMI_undefined() {
let mmi = parseMMI();
equal(mmi, null);
run_next_test();
});
add_test(function test_parseMMI_one_digit_short_code() {
let mmi = parseMMI("1");
equal(mmi.fullMMI, "1");
equal(mmi.procedure, undefined);
equal(mmi.serviceCode, undefined);
equal(mmi.sia, undefined);
equal(mmi.sib, undefined);
equal(mmi.sic, undefined);
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, undefined);
run_next_test();
});
add_test(function test_parseMMI_invalid_short_code() {
let mmi = parseMMI("11");
equal(mmi, null);
run_next_test();
});
add_test(function test_parseMMI_short_code() {
let mmi = parseMMI("21");
equal(mmi.fullMMI, "21");
equal(mmi.procedure, undefined);
equal(mmi.serviceCode, undefined);
equal(mmi.sia, undefined);
equal(mmi.sib, undefined);
equal(mmi.sic, undefined);
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, undefined);
run_next_test();
});
add_test(function test_parseMMI_dial_string() {
let mmi = parseMMI("12345");
equal(mmi, null);
run_next_test();
});
add_test(function test_parseMMI_USSD_without_asterisk_prefix() {
let mmi = parseMMI("123#");
equal(mmi.fullMMI, "123#");
equal(mmi.procedure, undefined);
equal(mmi.serviceCode, undefined);
equal(mmi.sia, undefined);
equal(mmi.sib, undefined);
equal(mmi.sic, undefined);
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, undefined);
run_next_test();
});
add_test(function test_parseMMI_USSD() {
let mmi = parseMMI("*123#");
equal(mmi.fullMMI, "*123#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, undefined);
equal(mmi.sib, undefined);
equal(mmi.sic, undefined);
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sia() {
let mmi = parseMMI("*123*1#");
equal(mmi.fullMMI, "*123*1#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, "1");
equal(mmi.sib, undefined);
equal(mmi.sic, undefined);
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sib() {
let mmi = parseMMI("*123**1#");
equal(mmi.fullMMI, "*123**1#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, "");
equal(mmi.sib, "1");
equal(mmi.sic, undefined);
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sic() {
let mmi = parseMMI("*123***1#");
equal(mmi.fullMMI, "*123***1#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, "");
equal(mmi.sib, "");
equal(mmi.sic, "1");
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sia_sib() {
let mmi = parseMMI("*123*1*1#");
equal(mmi.fullMMI, "*123*1*1#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, "1");
equal(mmi.sib, "1");
equal(mmi.sic, undefined);
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sia_sic() {
let mmi = parseMMI("*123*1**1#");
equal(mmi.fullMMI, "*123*1**1#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, "1");
equal(mmi.sib, "");
equal(mmi.sic, "1");
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_sib_sic() {
let mmi = parseMMI("*123**1*1#");
equal(mmi.fullMMI, "*123**1*1#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, "");
equal(mmi.sib, "1");
equal(mmi.sic, "1");
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_pwd() {
let mmi = parseMMI("*123****1#");
equal(mmi.fullMMI, "*123****1#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, "");
equal(mmi.sib, "");
equal(mmi.sic, "");
equal(mmi.pwd, "1");
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_dial_number() {
let mmi = parseMMI("*123#345");
equal(mmi.fullMMI, "*123#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "123");
equal(mmi.sia, undefined);
equal(mmi.sib, undefined);
equal(mmi.sic, undefined);
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "345");
run_next_test();
});
/**
* MMI procedures tests
*/
add_test(function test_parseMMI_activation() {
let mmi = parseMMI("*00*12*34*56#");
equal(mmi.fullMMI, "*00*12*34*56#");
equal(mmi.procedure, MMI_PROCEDURE_ACTIVATION);
equal(mmi.serviceCode, "00");
equal(mmi.sia, "12");
equal(mmi.sib, "34");
equal(mmi.sic, "56");
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_deactivation() {
let mmi = parseMMI("#00*12*34*56#");
equal(mmi.fullMMI, "#00*12*34*56#");
equal(mmi.procedure, MMI_PROCEDURE_DEACTIVATION);
equal(mmi.serviceCode, "00");
equal(mmi.sia, "12");
equal(mmi.sib, "34");
equal(mmi.sic, "56");
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_interrogation() {
let mmi = parseMMI("*#00*12*34*56#");
equal(mmi.fullMMI, "*#00*12*34*56#");
equal(mmi.procedure, MMI_PROCEDURE_INTERROGATION);
equal(mmi.serviceCode, "00");
equal(mmi.sia, "12");
equal(mmi.sib, "34");
equal(mmi.sic, "56");
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_registration() {
let mmi = parseMMI("**00*12*34*56#");
equal(mmi.fullMMI, "**00*12*34*56#");
equal(mmi.procedure, MMI_PROCEDURE_REGISTRATION);
equal(mmi.serviceCode, "00");
equal(mmi.sia, "12");
equal(mmi.sib, "34");
equal(mmi.sic, "56");
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});
add_test(function test_parseMMI_erasure() {
let mmi = parseMMI("##00*12*34*56#");
equal(mmi.fullMMI, "##00*12*34*56#");
equal(mmi.procedure, MMI_PROCEDURE_ERASURE);
equal(mmi.serviceCode, "00");
equal(mmi.sia, "12");
equal(mmi.sib, "34");
equal(mmi.sic, "56");
equal(mmi.pwd, undefined);
equal(mmi.dialNumber, "");
run_next_test();
});

View File

@ -0,0 +1,5 @@
[DEFAULT]
head = header_helpers.js
tail =
[test_parseMMI.js]

Some files were not shown because too many files have changed in this diff Show More