Bug 765231 - Telephony: Implement dialEmergency() to limit call to emergency numbers. r=philikon
authorHsin-Yi Tsai <htsai@mozilla.com>
Fri, 20 Jul 2012 20:08:24 +0800
changeset 100264 c500850abf3f4881204dfbe65de03608553e0555
parent 100263 991e039721e3fc16d14e744552f1db47b79e5aae
child 100265 b5054c63c6e37a8466da0066ec8e97145f5216cf
push id12408
push userhtsai@mozilla.com
push dateTue, 24 Jul 2012 02:50:30 +0000
treeherdermozilla-inbound@c500850abf3f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilikon
bugs765231
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 765231 - Telephony: Implement dialEmergency() to limit call to emergency numbers. r=philikon
dom/system/gonk/RILContentHelper.js
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/nsIRadioInterfaceLayer.idl
dom/system/gonk/ril_worker.js
dom/telephony/Telephony.cpp
dom/telephony/Telephony.h
dom/telephony/nsIDOMTelephony.idl
dom/telephony/test/marionette/test_emergency.js
dom/telephony/test/marionette/test_emergency_badNumber.js
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -415,16 +415,21 @@ RILContentHelper.prototype = {
     cpmm.sendAsyncMessage("RIL:StopTone");
   },
 
   dial: function dial(number) {
     debug("Dialing " + number);
     cpmm.sendAsyncMessage("RIL:Dial", number);
   },
 
+  dialEmergency: function dialEmergency(number) {
+    debug("Dialing emergency " + number);
+    cpmm.sendAsyncMessage("RIL:DialEmergency", number);
+  },
+
   hangUp: function hangUp(callIndex) {
     debug("Hanging up call no. " + callIndex);
     cpmm.sendAsyncMessage("RIL:HangUp", callIndex);
   },
 
   answerCall: function answerCall(callIndex) {
     cpmm.sendAsyncMessage("RIL:AnswerCall", callIndex);
   },
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -44,16 +44,17 @@ const RIL_IPC_MSG_NAMES = [
   "RIL:EnumerateCalls",
   "RIL:GetMicrophoneMuted",
   "RIL:SetMicrophoneMuted",
   "RIL:GetSpeakerEnabled",
   "RIL:SetSpeakerEnabled",
   "RIL:StartTone",
   "RIL:StopTone",
   "RIL:Dial",
+  "RIL:DialEmergency",
   "RIL:HangUp",
   "RIL:AnswerCall",
   "RIL:RejectCall",
   "RIL:HoldCall",
   "RIL:ResumeCall",
   "RIL:GetAvailableNetworks",
   "RIL:SelectNetwork",
   "RIL:SelectNetworkAuto",
@@ -226,16 +227,19 @@ RadioInterfaceLayer.prototype = {
         this.startTone(msg.json);
         break;
       case "RIL:StopTone":
         this.stopTone();
         break;
       case "RIL:Dial":
         this.dial(msg.json);
         break;
+      case "RIL:DialEmergency":
+        this.dialEmergency(msg.json);
+        break;
       case "RIL:HangUp":
         this.hangUp(msg.json);
         break;
       case "RIL:AnswerCall":
         this.answerCall(msg.json);
         break;
       case "RIL:RejectCall":
         this.rejectCall(msg.json);
@@ -1036,17 +1040,22 @@ RadioInterfaceLayer.prototype = {
 
   enumerateCalls: function enumerateCalls() {
     debug("Requesting enumeration of calls for callback");
     this.worker.postMessage({type: "enumerateCalls"});
   },
 
   dial: function dial(number) {
     debug("Dialing " + number);
-    this.worker.postMessage({type: "dial", number: number});
+    this.worker.postMessage({type: "dial", number: number, isDialEmergency: false});
+  },
+
+  dialEmergency: function dialEmergency(number) {
+    debug("Dialing emergency " + number);
+    this.worker.postMessage({type: "dial", number: number, isDialEmergency: true});
   },
 
   hangUp: function hangUp(callIndex) {
     debug("Hanging up call no. " + callIndex);
     this.worker.postMessage({type: "hangUp", callIndex: callIndex});
   },
 
   startTone: function startTone(dtmfChar) {
--- a/dom/system/gonk/nsIRadioInterfaceLayer.idl
+++ b/dom/system/gonk/nsIRadioInterfaceLayer.idl
@@ -125,17 +125,17 @@ interface nsIRILContactCallback : nsISup
    */
   void receiveContactsList(in DOMString type, in jsval contacts);
 };
 
 /**
  * Helper that runs in the content process and exposes information
  * to the DOM.
  */
-[scriptable, uuid(d2ec602f-9746-4ada-b0c6-f1c1a3cf3578)]
+[scriptable, uuid(31ea634b-e710-49ce-bb7d-bfcbaa40fb00)]
 interface nsIRILContentHelper : nsIMobileConnectionProvider
 {
   void registerTelephonyCallback(in nsIRILTelephonyCallback callback);
   void unregisterTelephonyCallback(in nsIRILTelephonyCallback callback);
 
   void registerVoicemailCallback(in nsIRILVoicemailCallback callback);
   void unregisterVoicemailCallback(in nsIRILVoicemailCallback callback);
 
@@ -144,16 +144,17 @@ interface nsIRILContentHelper : nsIMobil
    * returns false.
    */
   void enumerateCalls(in nsIRILTelephonyCallback callback);
 
   /**
    * Functionality for making and managing phone calls.
    */
   void dial(in DOMString number);
+  void dialEmergency(in DOMString number);
   void hangUp(in unsigned long callIndex);
 
   void startTone(in DOMString dtmfChar);
   void stopTone();
 
   void answerCall(in unsigned long callIndex);
   void rejectCall(in unsigned long callIndex);
   void holdCall(in unsigned long callIndex);
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -1538,24 +1538,28 @@ let RIL = {
    *        String containing the number to dial.
    * @param clirMode
    *        Integer doing something XXX TODO
    * @param uusInfo
    *        Integer doing something XXX TODO
    */
   dial: function dial(options) {
     let dial_request_type = REQUEST_DIAL;
-    if (this.voiceRegistrationState.emergencyCallsOnly) {
+    if (this.voiceRegistrationState.emergencyCallsOnly ||
+        options.isDialEmergency) {
       if (!this._isEmergencyNumber(options.number)) {
-        if (DEBUG) {
-          // TODO: Notify an error here so that the DOM will see an error event.
-          debug(options.number + " is not a valid emergency number.");
-        }
+        // Notify error in establishing the call with an invalid number.
+        options.callIndex = -1;
+        options.type = "callError";
+        options.error =
+          RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER];
+        this.sendDOMMessage(options);
         return;
       }
+
       if (RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL) {
         dial_request_type = REQUEST_DIAL_EMERGENCY_CALL;
       }
     } else {
       if (this._isEmergencyNumber(options.number) &&
           RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL) {
         dial_request_type = REQUEST_DIAL_EMERGENCY_CALL;
       }
--- a/dom/telephony/Telephony.cpp
+++ b/dom/telephony/Telephony.cpp
@@ -178,16 +178,57 @@ Telephony::NotifyCallsChanged(TelephonyC
 
   nsresult rv =
     event->Dispatch(ToIDOMEventTarget(), NS_LITERAL_STRING("callschanged"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+Telephony::DialInternal(bool isEmergency,
+                        const nsAString& aNumber,
+                        nsIDOMTelephonyCall** aResult)
+{
+  NS_ENSURE_ARG(!aNumber.IsEmpty());
+
+  for (PRUint32 index = 0; index < mCalls.Length(); index++) {
+    const nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
+    if (tempCall->IsOutgoing() &&
+        tempCall->CallState() < nsIRadioInterfaceLayer::CALL_STATE_CONNECTED) {
+      // One call has been dialed already and we only support one outgoing call
+      // at a time.
+      NS_WARNING("Only permitted to dial one call at a time!");
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+
+  nsresult rv;
+  if (isEmergency) {
+    rv = mRIL->DialEmergency(aNumber);
+  } else {
+    rv = mRIL->Dial(aNumber);
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<TelephonyCall> call = CreateNewDialingCall(aNumber);
+
+  // Notify other telephony objects that we just dialed.
+  for (PRUint32 index = 0; index < gTelephonyList->Length(); index++) {
+    Telephony*& telephony = gTelephonyList->ElementAt(index);
+    if (telephony != this) {
+      nsRefPtr<Telephony> kungFuDeathGrip = telephony;
+      telephony->NoteDialedCallFromOtherInstance(aNumber);
+    }
+  }
+
+  call.forget(aResult);
+  return NS_OK;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(Telephony)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Telephony,
                                                   nsDOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(incoming)
   NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(callschanged)
   for (PRUint32 index = 0; index < tmp->mCalls.Length(); index++) {
@@ -220,44 +261,26 @@ NS_IMPL_RELEASE_INHERITED(Telephony, nsD
 
 DOMCI_DATA(Telephony, Telephony)
 
 NS_IMPL_ISUPPORTS1(Telephony::RILTelephonyCallback, nsIRILTelephonyCallback)
 
 NS_IMETHODIMP
 Telephony::Dial(const nsAString& aNumber, nsIDOMTelephonyCall** aResult)
 {
-  NS_ENSURE_ARG(!aNumber.IsEmpty());
+  DialInternal(false, aNumber, aResult);
 
-  for (PRUint32 index = 0; index < mCalls.Length(); index++) {
-    const nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
-    if (tempCall->IsOutgoing() &&
-        tempCall->CallState() < nsIRadioInterfaceLayer::CALL_STATE_CONNECTED) {
-      // One call has been dialed already and we only support one outgoing call
-      // at a time.
-      NS_WARNING("Only permitted to dial one call at a time!");
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-  }
+  return NS_OK;
+}
 
-  nsresult rv = mRIL->Dial(aNumber);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsRefPtr<TelephonyCall> call = CreateNewDialingCall(aNumber);
+NS_IMETHODIMP
+Telephony::DialEmergency(const nsAString& aNumber, nsIDOMTelephonyCall** aResult)
+{
+  DialInternal(true, aNumber, aResult);
 
-  // Notify other telephony objects that we just dialed.
-  for (PRUint32 index = 0; index < gTelephonyList->Length(); index++) {
-    Telephony*& telephony = gTelephonyList->ElementAt(index);
-    if (telephony != this) {
-      nsRefPtr<Telephony> kungFuDeathGrip = telephony;
-      telephony->NoteDialedCallFromOtherInstance(aNumber);
-    }
-  }
-
-  call.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Telephony::GetMuted(bool* aMuted)
 {
   nsresult rv = mRIL->GetMicrophoneMuted(aMuted);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -500,16 +523,20 @@ Telephony::NotifyError(PRInt32 aCallInde
     }
   }
 
   if (!callToNotify) {
     NS_ERROR("Don't call me with a bad call index!");
     return NS_ERROR_UNEXPECTED;
   }
 
+  if (mActiveCall && mActiveCall->CallIndex() == callToNotify->CallIndex()) {
+    mActiveCall = nsnull;
+  }
+
   // Set the call state to 'disconnected' and remove it from the calls list.
   callToNotify->NotifyError(aError);
 
   return NS_OK;
 }
 
 nsresult
 NS_NewTelephony(nsPIDOMWindow* aWindow, nsIDOMTelephony** aTelephony)
--- a/dom/telephony/Telephony.h
+++ b/dom/telephony/Telephony.h
@@ -93,16 +93,21 @@ private:
   CreateNewDialingCall(const nsAString& aNumber);
 
   void
   NoteDialedCallFromOtherInstance(const nsAString& aNumber);
 
   nsresult
   NotifyCallsChanged(TelephonyCall* aCall);
 
+  nsresult
+  DialInternal(bool isEmergency,
+               const nsAString& aNumber,
+               nsIDOMTelephonyCall** aResult);
+
   class RILTelephonyCallback : public nsIRILTelephonyCallback
   {
     Telephony* mTelephony;
 
   public:
     NS_DECL_ISUPPORTS
     NS_FORWARD_NSIRILTELEPHONYCALLBACK(mTelephony->)
 
--- a/dom/telephony/nsIDOMTelephony.idl
+++ b/dom/telephony/nsIDOMTelephony.idl
@@ -4,20 +4,21 @@
  * 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 "nsIDOMEventTarget.idl"
 
 interface nsIDOMEventListener;
 interface nsIDOMTelephonyCall;
 
-[scriptable, builtinclass, uuid(0de46b73-be83-4970-ad15-45f92cb0902a)]
+[scriptable, builtinclass, uuid(b3875982-7327-478f-98a2-54cc2de36d7d)]
 interface nsIDOMTelephony : nsIDOMEventTarget
 {
   nsIDOMTelephonyCall dial(in DOMString number);
+  nsIDOMTelephonyCall dialEmergency(in DOMString number);
 
   attribute boolean muted;
   attribute boolean speakerEnabled;
 
   // The call that is "active", i.e. receives microphone input and tones
   // generated via startTone.
   readonly attribute jsval active;
 
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_emergency.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 10000;
+
+const WHITELIST_PREF = "dom.telephony.app.phone.url";
+SpecialPowers.setCharPref(WHITELIST_PREF, window.location.href);
+
+let telephony = window.navigator.mozTelephony;
+let number = "911";
+let outgoing;
+let calls;
+
+function verifyInitialState() {
+  log("Verifying initial state.");
+  ok(telephony);
+  is(telephony.active, null);
+  ok(telephony.calls);
+  is(telephony.calls.length, 0);
+  calls = telephony.calls;
+
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    is(result[0], "OK");
+    dial();
+  });
+}
+
+function dial() {
+  log("Make an emergency call.");
+
+  outgoing = telephony.dialEmergency(number);
+  ok(outgoing);
+  is(outgoing.number, number);
+  is(outgoing.state, "dialing");
+
+  is(outgoing, telephony.active);
+  //ok(telephony.calls === calls); // bug 717414
+  is(telephony.calls.length, 1);
+  is(telephony.calls[0], outgoing);
+
+  runEmulatorCmd("gsm list", function(result) {
+    log("Call list is now: " + result);
+    is(result[0], "outbound to  " + number + "        : unknown");
+    is(result[1], "OK");
+    answer();
+  });
+}
+
+function answer() {
+  log("Answering the emergency call.");
+
+  // We get no "connecting" event when the remote party answers the call.
+
+  outgoing.onconnected = function onconnected(event) {
+    log("Received 'connected' call event.");
+    is(outgoing, event.call);
+    is(outgoing.state, "connected");
+
+    is(outgoing, telephony.active);
+
+    runEmulatorCmd("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "outbound to  " + number + "        : active");
+      is(result[1], "OK");
+      hangUp();
+    });
+  };
+  runEmulatorCmd("gsm accept " + number);
+};
+
+function hangUp() {
+  log("Hanging up the emergency call.");
+
+  // We get no "disconnecting" event when the remote party terminates the call.
+
+  outgoing.ondisconnected = function ondisconnected(event) {
+    log("Received 'disconnected' call event.");
+    is(outgoing, event.call);
+    is(outgoing.state, "disconnected");
+
+    is(telephony.active, null);
+    is(telephony.calls.length, 0);
+
+    runEmulatorCmd("gsm list", function(result) {
+      log("Call list is now: " + result);
+      is(result[0], "OK");
+      cleanUp();
+    });
+  };
+  runEmulatorCmd("gsm cancel " + number);
+}
+
+function cleanUp() {
+  SpecialPowers.clearUserPref(WHITELIST_PREF);
+  finish();
+}
+
+verifyInitialState();
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_emergency_badNumber.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 10000;
+
+const WHITELIST_PREF = "dom.telephony.app.phone.url";
+SpecialPowers.setCharPref(WHITELIST_PREF, window.location.href);
+
+let telephony = window.navigator.mozTelephony;
+let number = "not a valid emergency number";
+let outgoing;
+let calls;
+
+function verifyInitialState() {
+  log("Verifying initial state.");
+  ok(telephony);
+  is(telephony.active, null);
+  ok(telephony.calls);
+  is(telephony.calls.length, 0);
+  calls = telephony.calls;
+
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    is(result[0], "OK");
+    dial();
+  });
+}
+
+function dial() {
+  log("Make an outgoing call to an invalid number.");
+
+  outgoing = telephony.dialEmergency(number);
+  ok(outgoing);
+  is(outgoing.number, number);
+  is(outgoing.state, "dialing");
+
+  is(outgoing, telephony.active);
+  //ok(telephony.calls === calls); // bug 717414
+  is(telephony.calls.length, 1);
+  is(telephony.calls[0], outgoing);
+
+  outgoing.onerror = function onerror(event) {
+    log("Received 'error' event.");
+    is(event.call, outgoing);
+    ok(event.call.error);
+    is(event.call.error.name, "BadNumberError");
+    
+    is(telephony.calls.length, 0);
+    is(telephony.active, null);
+    
+    runEmulatorCmd("gsm list", function(result) {
+      log("Initial call list: " + result);
+      is(result[0], "OK");
+      cleanUp();
+    });
+  };
+}
+
+function cleanUp() {
+  SpecialPowers.clearUserPref(WHITELIST_PREF);
+  finish();
+}
+
+verifyInitialState();