mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 05:35:31 +00:00
427 lines
15 KiB
JavaScript
427 lines
15 KiB
JavaScript
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Telephony.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* the Mozilla Foundation.
|
|
* Portions created by the Initial Developer are Copyright (C) 2011
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Philipp von Weitershausen <philipp@weitershausen.de>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* 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 ***** */
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
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.
|
|
*/
|
|
function defineEventListenerSlot(object, event_type) {
|
|
let property_name = "on" + event_type;
|
|
let hidden_name = "_on" + event_type;
|
|
let bound_name = "_bound_on" + event_type;
|
|
object.__defineGetter__(property_name, function getter() {
|
|
return this[hidden_name];
|
|
});
|
|
object.__defineSetter__(property_name, function setter(handler) {
|
|
let old_handler = this[bound_name];
|
|
if (old_handler) {
|
|
this.removeEventListener(event_type, old_handler);
|
|
}
|
|
// Bind the handler to the object so that its 'this' is correct.
|
|
let bound_handler = handler.bind(this);
|
|
this.addEventListener(event_type, bound_handler);
|
|
this[hidden_name] = handler;
|
|
this[bound_name] = bound_handler;
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Base object for event targets.
|
|
*/
|
|
function EventTarget() {}
|
|
EventTarget.prototype = {
|
|
|
|
addEventListener: function addEventListener(type, handler) {
|
|
//TODO verify that handler is an nsIDOMEventListener (or function)
|
|
if (!this._listeners) {
|
|
this._listeners = {};
|
|
}
|
|
if (!this._listeners[type]) {
|
|
this._listeners[type] = [];
|
|
}
|
|
if (this._listeners[type].indexOf(handler) != -1) {
|
|
// The handler is already registered. Ignore.
|
|
return;
|
|
}
|
|
this._listeners[type].push(handler);
|
|
},
|
|
|
|
removeEventListener: function removeEventListener(type, handler) {
|
|
let list, index;
|
|
if (this._listeners &&
|
|
(list = this._listeners[type]) &&
|
|
(index = list.indexOf(handler) != -1)) {
|
|
list.splice(index, 1);
|
|
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 (handlerList.indexOf(handler) == -1) {
|
|
return;
|
|
}
|
|
switch (typeof handler) {
|
|
case "function":
|
|
handler(event);
|
|
break;
|
|
case "object":
|
|
handler.handleEvent(event);
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Callback object that Telephony registers with nsITelephone.
|
|
* Telephony can't use itself because that might overload event handler
|
|
* attributes ('onfoobar').
|
|
*/
|
|
function TelephoneCallback(telephony) {
|
|
this.telephony = telephony;
|
|
}
|
|
TelephoneCallback.prototype = {
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephoneCallback]),
|
|
|
|
// nsITelephoneCallback
|
|
|
|
onsignalstrengthchange: function onsignalstrengthchange(event) {
|
|
this.telephony.signalStrength = event.signalStrength;
|
|
this.telephony._dispatchEventByType("signalstrengthchange");
|
|
},
|
|
|
|
onoperatorchange: function onoperatorchange(event) {
|
|
this.telephony.operator = event.operator;
|
|
this.telephony._dispatchEventByType("operatorchange");
|
|
},
|
|
|
|
onradiostatechange: function onradiostatechange(event) {
|
|
this.telephony.radioState = event.radioState;
|
|
this.telephony._dispatchEventByType("radiostatechange");
|
|
},
|
|
|
|
oncardstatechange: function oncardstatechange(event) {
|
|
this.telephony.cardState = event.cardState;
|
|
this.telephony._dispatchEventByType("cardstatechange");
|
|
},
|
|
|
|
oncallstatechange: function oncallstatechange(event) {
|
|
this.telephony._processCallState(event);
|
|
},
|
|
|
|
};
|
|
|
|
/**
|
|
* The navigator.mozTelephony object.
|
|
*/
|
|
function Telephony() {}
|
|
Telephony.prototype = {
|
|
|
|
__proto__: EventTarget.prototype,
|
|
|
|
classID: TELEPHONY_CID,
|
|
classInfo: XPCOMUtils.generateCI({classID: TELEPHONY_CID,
|
|
contractID: TELEPHONY_CONTRACTID,
|
|
interfaces: [Ci.mozIDOMTelephony,
|
|
Ci.nsIDOMEventTarget],
|
|
flags: Ci.nsIClassInfo.DOM_OBJECT,
|
|
classDescription: "Telephony"}),
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMTelephony,
|
|
Ci.nsIDOMEventTarget,
|
|
Ci.nsIDOMGlobalPropertyInitializer]),
|
|
|
|
// nsIDOMGlobalPropertyInitializer
|
|
|
|
init: function init(window) {
|
|
this.window = window;
|
|
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.telephone.unregisterCallback(this.telephoneCallback);
|
|
this.telephoneCallback = null;
|
|
this.window = null;
|
|
}.bind(this));
|
|
this.telephone.registerCallback(this.telephoneCallback);
|
|
this.callsByIndex = {};
|
|
this.liveCalls = [];
|
|
|
|
// 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);
|
|
},
|
|
|
|
_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.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");
|
|
|
|
|
|
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() {
|
|
if (this.readyState != DOM_CALL_READYSTATE_INCOMING) {
|
|
throw "Can only answer an incoming call!";
|
|
}
|
|
this.telephone.answerCall();
|
|
},
|
|
|
|
disconnect: function disconnect() {
|
|
if (this.readyState == DOM_CALL_READYSTATE_INCOMING) {
|
|
this.telephone.rejectCall();
|
|
} else {
|
|
this.telephone.hangUp(this.callIndex);
|
|
}
|
|
},
|
|
|
|
};
|
|
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]);
|