Bug 707629 - Part 2: Complete most of the mozTelephony API. r=bent
--- a/dom/telephony/Telephony.js
+++ b/dom/telephony/Telephony.js
@@ -41,16 +41,41 @@ Cu.import("resource://gre/modules/XPCOMU
const TELEPHONY_CID = Components.ID("{37e248d2-02ff-469b-bb31-eef5a4a4bee3}");
const TELEPHONY_CONTRACTID = "@mozilla.org/telephony;1";
const TELEPHONY_CALL_CID = Components.ID("{6b9b3daf-e5ea-460b-89a5-641ee20dd577}");
const TELEPHONY_CALL_CONTRACTID = "@mozilla.org/telephony-call;1";
+const DOM_RADIOSTATE_UNAVAILABLE = "unavailable";
+const DOM_RADIOSTATE_OFF = "off";
+const DOM_RADIOSTATE_READY = "ready";
+
+const DOM_CARDSTATE_UNAVAILABLE = "unavailable";
+const DOM_CARDSTATE_ABSENT = "absent";
+const DOM_CARDSTATE_PIN_REQUIRED = "pin_required";
+const DOM_CARDSTATE_PUK_REQUIRED = "puk_required";
+const DOM_CARDSTATE_NETWORK_LOCKED = "network_locked";
+const DOM_CARDSTATE_NOT_READY = "not_ready";
+const DOM_CARDSTATE_READY = "ready";
+
+const DOM_CALL_READYSTATE_DIALING = "dialing";
+const DOM_CALL_READYSTATE_RINGING = "ringing";
+const DOM_CALL_READYSTATE_BUSY = "busy";
+const DOM_CALL_READYSTATE_CONNECTING = "connecting";
+const DOM_CALL_READYSTATE_CONNECTED = "connected";
+const DOM_CALL_READYSTATE_DISCONNECTING = "disconnecting";
+const DOM_CALL_READYSTATE_DISCONNECTED = "disconnected";
+const DOM_CALL_READYSTATE_INCOMING = "incoming";
+const DOM_CALL_READYSTATE_HOLDING = "holding";
+const DOM_CALL_READYSTATE_HELD = "held";
+
+const CALLINDEX_TEMPORARY_DIALING = -1;
+
/**
* Define an event listener slot on an object, e.g.
*
* obj.onerror = function () {...}
*
* will register the function as an event handler for the "error" event
* if the "error" slot was defined on 'obj' or its prototype.
*/
@@ -105,93 +130,84 @@ EventTarget.prototype = {
return;
}
},
dispatchEvent: function dispatchEvent(event) {
//TODO this does not deal with bubbling, defaultPrevented, canceling, etc.
//TODO disallow re-dispatch of the same event if it's already being
// dispatched (recursion).
+ if (!this._listeners) {
+ return;
+ }
let handlerList = this._listeners[event.type];
if (!handlerList) {
return;
}
event.target = this;
// We need to worry about event handler mutations during the event firing.
// The correct behaviour is to *not* call any listeners that are added
// during the firing and to *not* call any listeners that are removed
// during the firing. To address this, we make a copy of the listener list
// before dispatching and then double-check that each handler is still
// registered before firing it.
let handlers = handlerList.slice();
handlers.forEach(function (handler) {
- if (handerList.indexOf(handler) == -1) {
+ if (handlerList.indexOf(handler) == -1) {
return;
}
switch (typeof handler) {
case "function":
handler(event);
break;
case "object":
handler.handleEvent(event);
break;
}
});
}
};
-const DOM_RADIOSTATE_UNAVAILABLE = "unavailable";
-const DOM_RADIOSTATE_OFF = "off";
-const DOM_RADIOSTATE_READY = "ready";
-
-const DOM_CARDSTATE_UNAVAILABLE = "unavailable";
-const DOM_CARDSTATE_ABSENT = "absent";
-const DOM_CARDSTATE_PIN_REQUIRED = "pin_required";
-const DOM_CARDSTATE_PUK_REQUIRED = "puk_required";
-const DOM_CARDSTATE_NETWORK_LOCKED = "network_locked";
-const DOM_CARDSTATE_NOT_READY = "not_ready";
-const DOM_CARDSTATE_READY = "ready";
-
/**
* Callback object that Telephony registers with nsITelephone.
* Telephony can't use itself because that might overload event handler
* attributes ('onfoobar').
*/
-function TelephonyRadioCallback(telephony) {
+function TelephoneCallback(telephony) {
this.telephony = telephony;
}
-TelephonyRadioCallback.prototype = {
+TelephoneCallback.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephoneCallback]),
// nsITelephoneCallback
- onsignalstrengthchange: function onsignalstrengthchange(signalStrength) {
- this.telephony.signalStrength = signalStrength;
+ onsignalstrengthchange: function onsignalstrengthchange(event) {
+ this.telephony.signalStrength = event.signalStrength;
this.telephony._dispatchEventByType("signalstrengthchange");
},
- onoperatorchange: function onoperatorchange(operator) {
- this.telephony.operator = operator;
+ onoperatorchange: function onoperatorchange(event) {
+ this.telephony.operator = event.operator;
this.telephony._dispatchEventByType("operatorchange");
},
- onradiostatechange: function onradiostatechange(radioState) {
- this.telephony.radioState = radioState;
+ onradiostatechange: function onradiostatechange(event) {
+ this.telephony.radioState = event.radioState;
this.telephony._dispatchEventByType("radiostatechange");
},
- oncardstatechange: function oncardstatechange(cardState) {
- this.telephony.cardState = cardState;
+ oncardstatechange: function oncardstatechange(event) {
+ this.telephony.cardState = event.cardState;
this.telephony._dispatchEventByType("cardstatechange");
},
- oncallstatechange: function oncallstatechange(callState) {
- this.telephony._processCallState(callState);
+ oncallstatechange: function oncallstatechange(event) {
+ this.telephony._processCallState(event);
},
};
/**
* The navigator.mozTelephony object.
*/
function Telephony() {}
@@ -209,105 +225,202 @@ Telephony.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMTelephony,
Ci.nsIDOMEventTarget,
Ci.nsIDOMGlobalPropertyInitializer]),
// nsIDOMGlobalPropertyInitializer
init: function init(window) {
this.window = window;
- this.radioInterface = Cc["@mozilla.org/telephony/radio-interface;1"]
- .createInstance(Ci.nsITelephone);
- this.radioCallback = new TelephonyRadioCallback(this);
+ this.telephone = Cc["@mozilla.org/telephony/radio-interface;1"]
+ .createInstance(Ci.nsITelephone);
+ this.telephoneCallback = new TelephoneCallback(this);
+ //TODO switch to method suggested by bz in bug 707507
window.addEventListener("unload", function onunload(event) {
- this.radioInterface.unregisterCallback(this.radioCallback);
- this.radioCallback = null;
+ this.telephone.unregisterCallback(this.telephoneCallback);
+ this.telephoneCallback = null;
this.window = null;
}.bind(this));
- this.radioInterface.registerCallback(this.radioCallback);
+ this.telephone.registerCallback(this.telephoneCallback);
+ this.callsByIndex = {};
this.liveCalls = [];
- let initialState = this.radioInterface.initialState;
- this.operator = initialState.operator;
- this.radioState = initialState.radioState;
- this.cardState = initialState.cardState;
- this.signalStrength = initialState.signalStrength;
- this._processCallState(initialState.callState);
+ // Populate existing state.
+ let currentState = this.telephone.currentState;
+ let states = currentState.currentCalls;
+ for (let i = 0; i < states.length; i++) {
+ let state = states[i];
+ let call = new TelephonyCall(this.telephone, state.callIndex);
+ call.readyState = state.callState;
+ call.number = state.number;
+ this.liveCalls.push(call);
+ this.callsByIndex[state.callIndex] = call;
+ }
+
+ this.operator = currentState.operator;
+ this.radioState = currentState.radioState;
+ this.cardState = currentState.cardState;
+ this.signalStrength = currentState.signalStrength;
},
_dispatchEventByType: function _dispatchEventByType(type) {
let event = this.window.document.createEvent("Event");
event.initEvent(type, false, false);
//event.isTrusted = true;
this.dispatchEvent(event);
},
- _processCallState: function _processCallState(callState) {
- //TODO
+ _dispatchCallEvent: function _dispatchCallEvent(call, type, target) {
+ let event = this.window.document.createEvent("Event");
+ event.initEvent(type, false, false);
+ event.call = call; //XXX this is probably not going to work
+ //event.isTrusted = true;
+ target = target || call;
+ target.dispatchEvent(event);
},
+ _processCallState: function _processCallState(state) {
+ // If the call is dialing, chances are good that we just kicked that off
+ // so there's a call object without a callIndex. Let's fix that.
+ if (state.callState == DOM_CALL_READYSTATE_DIALING) {
+ let call = this.callsByIndex[CALLINDEX_TEMPORARY_DIALING];
+ if (call) {
+ call.callIndex = state.callIndex;
+ delete this.callsByIndex[CALLINDEX_TEMPORARY_DIALING];
+ this.callsByIndex[call.callIndex] = call;
+ // Nothing else to do, since the initial call state will already be
+ // DOM_CALL_READYSTATE_DIALING, so there's no event to dispatch.
+ return;
+ }
+ }
+
+ // If there is an existing call object, update state and dispatch event
+ // on it.
+ let call = this.callsByIndex[state.callIndex];
+ if (call) {
+ if (call.readyState == state.callState) {
+ // No change in ready state, don't dispatch an event.
+ return;
+ }
+ if (state.readyState == DOM_CALL_READYSTATE_DISCONNECTED) {
+ let index = this.liveCalls.indexOf(call);
+ if (index != -1) {
+ this.liveCalls.splice(index, 1);
+ }
+ delete this.callsByIndex[call.callIndex];
+ }
+ call.readyState = state.callState;
+ this._dispatchCallEvent(call, "readystatechange");
+ this._dispatchCallEvent(call, state.callState);
+ return;
+ }
+
+ // There's no call object yet, so let's create a new one, except when
+ // the state notified means that the call is over.
+ if (state.readyState == DOM_CALL_READYSTATE_DISCONNECTED) {
+ return;
+ }
+ call = new TelephonyCall(this.telephone, state.callIndex);
+ call.number = state.number;
+ call.readyState = state.callState;
+ this.callsByIndex[state.callIndex] = call;
+ this.liveCalls.push(call);
+
+ let target;
+ if (call.readyState == DOM_CALL_READYSTATE_INCOMING) {
+ target = this;
+ } else {
+ target = call;
+ this._dispatchCallEvent(call, "readystatechange");
+ }
+ this._dispatchCallEvent(call, state.callState, target);
+ },
+
+ callsByIndex: null,
+
// mozIDOMTelephony
liveCalls: null,
dial: function dial(number) {
- this.radioInterface.dial(number);
- return new TelephonyCall(number, DOM_CALL_READYSTATE_DIALING);
+ this.telephone.dial(number);
+
+ // We don't know ahead of time what callIndex the call is going to have
+ // so let's assign a temp value for now and sort it out on the first
+ // 'callstatechange' event.
+ //TODO ensure there isn't already an outgoing call
+ let callIndex = CALLINDEX_TEMPORARY_DIALING;
+ let call = new TelephonyCall(this.telephone, callIndex);
+ call.readyState = DOM_CALL_READYSTATE_DIALING;
+ call.number = number;
+ this.callsByIndex[callIndex] = call;
+ this.liveCalls.push(call);
+ return call;
},
// Additional stuff that's useful.
signalStrength: null,
operator: null,
radioState: DOM_RADIOSTATE_UNAVAILABLE,
cardState: DOM_CARDSTATE_UNAVAILABLE,
};
+defineEventListenerSlot(Telephony.prototype, DOM_CALL_READYSTATE_INCOMING);
+//XXX philikon's additions
defineEventListenerSlot(Telephony.prototype, "radiostatechange");
defineEventListenerSlot(Telephony.prototype, "cardstatechange");
defineEventListenerSlot(Telephony.prototype, "signalstrengthchange");
defineEventListenerSlot(Telephony.prototype, "operatorchange");
-defineEventListenerSlot(Telephony.prototype, "incoming");
-const DOM_CALL_READYSTATE_DIALING = "dialing";
-const DOM_CALL_READYSTATE_DOM_CALLING = "calling";
-const DOM_CALL_READYSTATE_INCOMING = "incoming";
-const DOM_CALL_READYSTATE_CONNECTED = "connected";
-const DOM_CALL_READYSTATE_CLOSED = "closed";
-const DOM_CALL_READYSTATE_BUSY = "busy";
-
-function TelephonyCall(number, initialState) {
- this.number = number;
- this.readyState = initialState;
+function TelephonyCall(telephone, callIndex) {
+ this.telephone = telephone;
+ this.callIndex = callIndex;
}
TelephonyCall.prototype = {
__proto__: EventTarget.prototype,
classID: TELEPHONY_CALL_CID,
classInfo: XPCOMUtils.generateCI({classID: TELEPHONY_CALL_CID,
contractID: TELEPHONY_CALL_CONTRACTID,
interfaces: [Ci.mozIDOMTelephonyCall,
Ci.nsIDOMEventTarget],
flags: Ci.nsIClassInfo.DOM_OBJECT,
classDescription: "TelephonyCall"}),
QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMTelephonyCall,
Ci.nsIDOMEventTarget]),
+
+ callIndex: null,
+
+ // mozIDOMTelephonyCall
+
number: null,
readyState: null,
answer: function answer() {
- //TODO
+ if (this.readyState != DOM_CALL_READYSTATE_INCOMING) {
+ throw "Can only answer an incoming call!";
+ }
+ this.telephone.answerCall();
},
disconnect: function disconnect() {
- //TODO
+ if (this.readyState == DOM_CALL_READYSTATE_INCOMING) {
+ this.telephone.rejectCall();
+ } else {
+ this.telephone.hangUp(this.callIndex);
+ }
},
};
-defineEventListenerSlot(TelephonyCall.prototype, "connect");
-defineEventListenerSlot(TelephonyCall.prototype, "disconnect");
-defineEventListenerSlot(TelephonyCall.prototype, "busy");
+defineEventListenerSlot(TelephonyCall.prototype, "readystatechange");
+defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_RINGING);
+defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_BUSY);
+defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_CONNECTING);
+defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_CONNECTED);
+defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_DISCONNECTING);
+defineEventListenerSlot(TelephonyCall.prototype, DOM_CALL_READYSTATE_DISCONNECTED);
const NSGetFactory = XPCOMUtils.generateNSGetFactory([Telephony]);
--- a/dom/telephony/mozIDOMTelephony.idl
+++ b/dom/telephony/mozIDOMTelephony.idl
@@ -31,16 +31,17 @@
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIDOMEventTarget.idl"
+#include "nsIDOMEvent.idl"
interface nsIDOMEventListener;
interface mozIDOMTelephonyCall;
[scriptable, uuid(24371c72-1631-477d-b26e-f14db369bc22)]
interface mozIDOMTelephony : nsIDOMEventTarget {
readonly attribute jsval liveCalls;
mozIDOMTelephonyCall dial(in DOMString number);
@@ -69,8 +70,13 @@ interface mozIDOMTelephonyCall : nsIDOME
attribute nsIDOMEventListener onreadystatechange;
attribute nsIDOMEventListener onringing;
attribute nsIDOMEventListener onbusy;
attribute nsIDOMEventListener onconnecting;
attribute nsIDOMEventListener onconnected;
attribute nsIDOMEventListener ondisconnecting;
attribute nsIDOMEventListener ondisconnected;
};
+
+[scriptable, uuid(c8c42b0c-a0dd-4702-9425-a7a80b2075c3)]
+interface mozIDOMTelephonyCallEvent : nsIDOMEvent {
+ readonly attribute mozIDOMTelephonyCall call;
+};
--- a/dom/telephony/nsITelephone.idl
+++ b/dom/telephony/nsITelephone.idl
@@ -32,30 +32,32 @@
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
-[scriptable, uuid(e21cdf40-c511-442b-8c7d-aa75471b0423)]
+[scriptable, uuid(9b7e3a01-9c45-4af3-81bb-1bf08a842226)]
interface nsITelephoneCallback : nsISupports {
- void oncallstatechange(in jsval callState);
+ void oncallstatechange(in jsval event);
//XXX philikon's additions
- void onoperatorchange(in jsval operatorInfo);
- void onradiostatechange(in jsval radioState);
- void oncardstatechange(in jsval cardState);
- void onsignalstrengthchange(in jsval signalStrength);
+ void onoperatorchange(in jsval event);
+ void onradiostatechange(in jsval event);
+ void oncardstatechange(in jsval event);
+ void onsignalstrengthchange(in jsval event);
};
-[scriptable, uuid(dda49485-5887-49bb-ba0b-10c5d116eb64)]
+[scriptable, uuid(3d3deb80-fa5e-4e05-9153-91ee614f67d5)]
interface nsITelephone : nsISupports {
- readonly attribute jsval initialState;
+ readonly attribute jsval currentState;
void dial(in DOMString number);
- void hangup(in long callIndex);
+ void hangUp(in long callIndex);
+ void answerCall();
+ void rejectCall();
void registerCallback(in nsITelephoneCallback callback);
void unregisterCallback(in nsITelephoneCallback callback);
};
--- a/dom/telephony/nsTelephonyWorker.js
+++ b/dom/telephony/nsTelephonyWorker.js
@@ -40,24 +40,31 @@ const {classes: Cc, interfaces: Ci, util
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const DEBUG = true; // set to false to suppress debug messages
const TELEPHONYWORKER_CONTRACTID = "@mozilla.org/telephony/worker;1";
const TELEPHONYWORKER_CID = Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
+const DOM_CALL_READYSTATE_DISCONNECTED = "disconnected";
function nsTelephonyWorker() {
this.worker = new ChromeWorker("resource://gre/modules/ril_worker.js");
this.worker.onerror = this.onerror.bind(this);
this.worker.onmessage = this.onmessage.bind(this);
this._callbacks = [];
- this.initialState = {};
+ this.currentState = {
+ signalStrength: null,
+ operator: null,
+ radioState: null,
+ cardState: null,
+ currentCalls: {}
+ };
}
nsTelephonyWorker.prototype = {
classID: TELEPHONYWORKER_CID,
classInfo: XPCOMUtils.generateCI({classID: TELEPHONYWORKER_CID,
contractID: TELEPHONYWORKER_CONTRACTID,
classDescription: "TelephonyWorker",
interfaces: [Ci.nsIRadioWorker,
@@ -81,61 +88,78 @@ nsTelephonyWorker.prototype = {
event.lineno + ": " + event.message + "\n");
},
onmessage: function onmessage(event) {
let message = event.data;
debug("Received message: " + JSON.stringify(message));
let value;
switch (message.type) {
+ case "callstatechange":
case "signalstrengthchange":
- this.initialState.signalStrength = message.signalStrength;
- value = message.signalStrength;
+ this.currentState.signalStrength = message.signalStrength;
break;
case "operatorchange":
- this.initialState.operator = message.operator;
- value = message.operator;
+ this.currentState.operator = message.operator;
break;
- case "onradiostatechange":
- this.initialState.radioState = message.radioState;
- value = message.radioState;
+ case "radiostatechange":
+ this.currentState.radioState = message.radioState;
break;
case "cardstatechange":
- this.initialState.cardState = message.cardState;
- value = message.cardState;
+ this.currentState.cardState = message.cardState;
break;
case "callstatechange":
- this.initialState.callState = message.callState;
- value = message.callState;
+ // Reuse the message object as the value here since there's more to
+ // the call state than just the callState integer.
+ if (message.callState == DOM_CALL_READYSTATE_DISCONNECTED) {
+ delete this.currentState.callState[message.callIndex];
+ } else {
+ this.currentState.callState[value.callIndex] = message;
+ }
break;
default:
// Got some message from the RIL worker that we don't know about.
+ return;
}
+ let methodname = "on" + message.type;
this._callbacks.forEach(function (callback) {
let method = callback[methodname];
if (typeof method != "function") {
return;
}
- method.call(callback, value);
+ method.call(callback, message);
});
},
// nsIRadioWorker
worker: null,
// nsITelephone
- initialState: null,
+ currentState: null,
dial: function dial(number) {
debug("Dialing " + number);
this.worker.postMessage({type: "dial", number: number});
},
+ hangUp: function hangUp(callIndex) {
+ debug("Hanging up call no. " + callIndex);
+ this.worker.postMessage({type: "hangUp", callIndex: callIndex});
+ },
+
+ answerCall: function answerCall() {
+ this.worker.postMessage({type: "answerCall"});
+ },
+
+ rejectCall: function rejectCall() {
+ this.worker.postMessage({type: "rejectCall"});
+ },
+
_callbacks: null,
registerCallback: function registerCallback(callback) {
this._callbacks.push(callback);
},
unregisterCallback: function unregisterCallback(callback) {
let index = this._callbacks.indexOf(callback);