Bug 1058397 - Part 3: USSDSession implement. r=hsinyi
authorSzu-Yu Chen [:aknow] <szchen@mozilla.com>
Wed, 15 Oct 2014 02:51:00 -0400
changeset 210716 b93c5e78c526d4d1caa93fb9664adeb1a96f33bf
parent 210715 8ec09ed512d3830642fe2c3859d55ca3135e26b6
child 210717 1b5f60a8cbac6583f796915cd442f7e2ec94e28e
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewershsinyi
bugs1058397
milestone36.0a1
Bug 1058397 - Part 3: USSDSession implement. r=hsinyi
dom/system/gonk/ril_worker.js
dom/telephony/USSDSession.cpp
dom/telephony/USSDSession.h
dom/telephony/gonk/TelephonyService.js
dom/telephony/ipc/PTelephony.ipdl
dom/telephony/ipc/TelephonyIPCService.cpp
dom/telephony/ipc/TelephonyParent.cpp
dom/telephony/ipc/TelephonyParent.h
dom/telephony/moz.build
dom/telephony/nsITelephonyService.idl
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -2636,32 +2636,58 @@ RilObject.prototype = {
     }
 
     // If the MMI code is not a known code, it is treated as an ussd.
     if (!_isRadioAvailable()) {
       return;
     }
 
     options.ussd = mmi.fullMMI;
+
+    if (options.startNewSession && this._ussdSession) {
+      if (DEBUG) this.context.debug("Cancel existing ussd session.");
+      this.cachedUSSDRequest = options;
+      this.cancelUSSD({});
+      return;
+    }
+
     this.sendUSSD(options);
   },
 
   /**
+   * Cache the request for send out a new ussd when there is an existing
+   * session. We should do cancelUSSD first.
+   */
+  cachedUSSDRequest : null,
+
+  /**
    * Send USSD.
    *
    * @param ussd
    *        String containing the USSD code.
-   *
-   */
-   sendUSSD: function(options) {
-     let Buf = this.context.Buf;
-     Buf.newParcel(REQUEST_SEND_USSD, options);
-     Buf.writeString(options.ussd);
-     Buf.sendParcel();
-   },
+   * @param checkSession
+   *        True if an existing session should be there.
+   */
+  sendUSSD: function(options) {
+    if (options.checkSession && !this._ussdSession) {
+      options.success = false;
+      options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+      this.sendChromeMessage(options);
+      return;
+    }
+
+    this.sendRilRequestSendUSSD(options);
+  },
+
+  sendRilRequestSendUSSD: function(options) {
+    let Buf = this.context.Buf;
+    Buf.newParcel(REQUEST_SEND_USSD, options);
+    Buf.writeString(options.ussd);
+    Buf.sendParcel();
+  },
 
   /**
    * Cancel pending USSD.
    */
    cancelUSSD: function(options) {
      this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options);
    },
 
@@ -5674,19 +5700,29 @@ RilObject.prototype[REQUEST_SEND_USSD] =
   options.success = (this._ussdSession = options.rilRequestError === 0);
   options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
   this.sendChromeMessage(options);
 };
 RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) {
   if (DEBUG) {
     this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options));
   }
+
   options.success = (options.rilRequestError === 0);
   this._ussdSession = !options.success;
   options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
+
+  // The cancelUSSD is triggered by ril_worker itself.
+  if (this.cachedUSSDRequest) {
+    if (DEBUG) this.context.debug("Send out the cached ussd request");
+    this.sendUSSD(this.cachedUSSDRequest);
+    this.cachedUSSDRequest = null;
+    return;
+  }
+
   this.sendChromeMessage(options);
 };
 RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) {
   options.success = (options.rilRequestError === 0);
   if (!options.success) {
     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
     this.sendChromeMessage(options);
     return;
new file mode 100644
--- /dev/null
+++ b/dom/telephony/USSDSession.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/USSDSession.h"
+
+#include "mozilla/dom/USSDSessionBinding.h"
+#include "mozilla/dom/telephony/TelephonyCallback.h"
+#include "nsIGlobalObject.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::telephony;
+using mozilla::ErrorResult;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(USSDSession, mWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(USSDSession)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(USSDSession)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USSDSession)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+USSDSession::USSDSession(nsPIDOMWindow* aWindow, nsITelephonyService* aService,
+                         uint32_t aServiceId)
+  : mWindow(aWindow), mService(aService), mServiceId(aServiceId)
+{
+}
+
+USSDSession::~USSDSession()
+{
+}
+
+nsPIDOMWindow*
+USSDSession::GetParentObject() const
+{
+  return mWindow;
+}
+
+JSObject*
+USSDSession::WrapObject(JSContext* aCx)
+{
+  return USSDSessionBinding::Wrap(aCx, this);
+}
+
+// WebIDL
+
+already_AddRefed<USSDSession>
+USSDSession::Constructor(const GlobalObject& aGlobal, uint32_t aServiceId,
+                         ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!window) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsITelephonyService> ril =
+    do_GetService(TELEPHONY_SERVICE_CONTRACTID);
+  if (!ril) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsRefPtr<USSDSession> session = new USSDSession(window, ril, aServiceId);
+  return session.forget();
+}
+
+already_AddRefed<Promise>
+USSDSession::Send(const nsAString& aUssd, ErrorResult& aRv)
+{
+  if (!mService) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+  if (!global) {
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
+
+  nsresult rv = mService->SendUSSD(mServiceId, aUssd, callback);
+  if (NS_FAILED(rv)) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+
+  return promise.forget();
+}
new file mode 100644
--- /dev/null
+++ b/dom/telephony/USSDSession.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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_USSDSession_h
+#define mozilla_dom_USSDSession_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Promise.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsITelephonyService.h"
+#include "nsPIDOMWindow.h"
+#include "nsWrapperCache.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+
+class USSDSession MOZ_FINAL : public nsISupports,
+                              public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(USSDSession)
+
+  USSDSession(nsPIDOMWindow* aWindow, nsITelephonyService* aService,
+              uint32_t aServiceId);
+
+  nsPIDOMWindow*
+  GetParentObject() const;
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  // WebIDL
+  static already_AddRefed<USSDSession>
+  Constructor(const GlobalObject& aGlobal, uint32_t aServiceId,
+              ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Send(const nsAString& aUssd, ErrorResult& aRv);
+
+private:
+  ~USSDSession();
+
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsCOMPtr<nsITelephonyService> mService;
+  uint32_t mServiceId;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_USSDSession_h
--- a/dom/telephony/gonk/TelephonyService.js
+++ b/dom/telephony/gonk/TelephonyService.js
@@ -580,17 +580,17 @@ TelephonyService.prototype = {
                        isDialEmergency: aIsDialEmergency }, aCallback);
     } else {
       // Reject MMI code from dialEmergency api.
       if (aIsDialEmergency) {
         aCallback.notifyError(DIAL_ERROR_BAD_NUMBER);
         return;
       }
 
-      this._dialMMI(aClientId, mmi, aCallback);
+      this._dialMMI(aClientId, mmi, aCallback, true);
     }
   },
 
   /**
    * @param aOptions.number
    * @param aOptions.clirMode (optional)
    * @param aOptions.isDialEmergency
    */
@@ -668,26 +668,34 @@ TelephonyService.prototype = {
         // RIL doesn't hold the 2nd call. We create one by ourselves.
         aCallback.notifyDialCallSuccess(CDMA_SECOND_CALL_INDEX, response.number);
         this._addCdmaChildCall(aClientId, response.number, currentCdmaCallIndex);
       }
     });
   },
 
   /**
+   * @param aClientId
+   *        Client id.
    * @param aMmi
    *        Parsed MMI structure.
+   * @param aCallback
+   *        A nsITelephonyDialCallback object.
+   * @param aStartNewSession
+   *        True to start a new session for ussd request.
    */
-  _dialMMI: function(aClientId, aMmi, aCallback) {
+  _dialMMI: function(aClientId, aMmi, aCallback, aStartNewSession) {
     let mmiServiceCode = aMmi ?
       this._serviceCodeToKeyString(aMmi.serviceCode) : RIL.MMI_KS_SC_USSD;
 
     aCallback.notifyDialMMI(mmiServiceCode);
 
-    this._sendToRilWorker(aClientId, "sendMMI", { mmi: aMmi }, response => {
+    this._sendToRilWorker(aClientId, "sendMMI",
+                          { mmi: aMmi,
+                            startNewSession: aStartNewSession }, 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);
@@ -1043,16 +1051,28 @@ TelephonyService.prototype = {
   holdConference: function(aClientId) {
     this._sendToRilWorker(aClientId, "holdConference");
   },
 
   resumeConference: function(aClientId) {
     this._sendToRilWorker(aClientId, "resumeConference");
   },
 
+  sendUSSD: function(aClientId, aUssd, aCallback) {
+    this._sendToRilWorker(aClientId, "sendUSSD",
+                          { ussd: aUssd, checkSession: true },
+                          response => {
+      if (!response.success) {
+        aCallback.notifyError(response.errorMsg);
+      } else {
+        aCallback.notifySuccess();
+      }
+    });
+  },
+
   get microphoneMuted() {
     return gAudioManager.microphoneMuted;
   },
 
   set microphoneMuted(aMuted) {
     if (aMuted == this.microphoneMuted) {
       return;
     }
@@ -1263,17 +1283,17 @@ TelephonyService.prototype = {
   notifyConferenceCallStateChanged: function(aState) {
     if (DEBUG) debug("handleConferenceCallStateChanged: " + aState);
     aState = this._convertRILCallState(aState);
     this._notifyAllListeners("conferenceCallStateChanged", [aState]);
   },
 
   dialMMI: function(aClientId, aMmiString, aCallback) {
     let mmi = this._parseMMI(aMmiString, this._hasCalls(aClientId));
-    this._dialMMI(aClientId, mmi, aCallback);
+    this._dialMMI(aClientId, mmi, aCallback, false);
   },
 
   /**
    * nsIObserver interface.
    */
 
   observe: function(aSubject, aTopic, aData) {
     switch (aTopic) {
--- a/dom/telephony/ipc/PTelephony.ipdl
+++ b/dom/telephony/ipc/PTelephony.ipdl
@@ -19,20 +19,27 @@ struct EnumerateCallsRequest
 
 struct DialRequest
 {
   uint32_t clientId;
   nsString number;
   bool isEmergency;
 };
 
+struct USSDRequest
+{
+  uint32_t clientId;
+  nsString ussd;
+};
+
 union IPCTelephonyRequest
 {
   EnumerateCallsRequest;
   DialRequest;
+  USSDRequest;
 };
 
 sync protocol PTelephony {
   manager PContent;
   manages PTelephonyRequest;
 
 child:
   NotifyCallError(uint32_t aClientId, int32_t aCallIndex, nsString aError);
--- a/dom/telephony/ipc/TelephonyIPCService.cpp
+++ b/dom/telephony/ipc/TelephonyIPCService.cpp
@@ -297,16 +297,24 @@ TelephonyIPCService::StopTone(uint32_t a
     return NS_ERROR_FAILURE;
   }
 
   mPTelephonyChild->SendStopTone(aClientId);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+TelephonyIPCService::SendUSSD(uint32_t aClientId, const nsAString& aUssd,
+                              nsITelephonyCallback *aCallback)
+{
+  return SendRequest(nullptr, aCallback,
+                     USSDRequest(aClientId, nsString(aUssd)));
+}
+
+NS_IMETHODIMP
 TelephonyIPCService::GetMicrophoneMuted(bool* aMuted)
 {
   if (!mPTelephonyChild) {
     NS_WARNING("TelephonyService used after shutdown has begun!");
     return NS_ERROR_FAILURE;
   }
 
   mPTelephonyChild->SendGetMicrophoneMuted(aMuted);
--- a/dom/telephony/ipc/TelephonyParent.cpp
+++ b/dom/telephony/ipc/TelephonyParent.cpp
@@ -38,16 +38,18 @@ TelephonyParent::RecvPTelephonyRequestCo
 {
   TelephonyRequestParent* actor = static_cast<TelephonyRequestParent*>(aActor);
 
   switch (aRequest.type()) {
     case IPCTelephonyRequest::TEnumerateCallsRequest:
       return actor->DoRequest(aRequest.get_EnumerateCallsRequest());
     case IPCTelephonyRequest::TDialRequest:
       return actor->DoRequest(aRequest.get_DialRequest());
+    case IPCTelephonyRequest::TUSSDRequest:
+      return actor->DoRequest(aRequest.get_USSDRequest());
     default:
       MOZ_CRASH("Unknown type!");
   }
 
   return false;
 }
 
 PTelephonyRequestParent*
@@ -430,16 +432,30 @@ TelephonyRequestParent::DoRequest(const 
                   aRequest.isEmergency(), this);
   } else {
     return NS_SUCCEEDED(NotifyError(NS_LITERAL_STRING("InvalidStateError")));
   }
 
   return true;
 }
 
+bool
+TelephonyRequestParent::DoRequest(const USSDRequest& aRequest)
+{
+  nsCOMPtr<nsITelephonyService> service =
+    do_GetService(TELEPHONY_SERVICE_CONTRACTID);
+  if (service) {
+    service->SendUSSD(aRequest.clientId(), aRequest.ussd(), this);
+  } else {
+    return NS_SUCCEEDED(NotifyError(NS_LITERAL_STRING("InvalidStateError")));
+  }
+
+  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;
 }
 
--- a/dom/telephony/ipc/TelephonyParent.h
+++ b/dom/telephony/ipc/TelephonyParent.h
@@ -121,13 +121,16 @@ protected:
 private:
   bool mActorDestroyed;
 
   bool
   DoRequest(const EnumerateCallsRequest& aRequest);
 
   bool
   DoRequest(const DialRequest& aRequest);
+
+  bool
+  DoRequest(const USSDRequest& aRequest);
 };
 
 END_TELEPHONY_NAMESPACE
 
 #endif /* mozilla_dom_telephony_TelephonyParent_h */
--- a/dom/telephony/moz.build
+++ b/dom/telephony/moz.build
@@ -11,16 +11,17 @@ XPIDL_SOURCES += [
 XPIDL_MODULE = 'dom_telephony'
 
 EXPORTS.mozilla.dom += [
     'CallsList.h',
     'Telephony.h',
     'TelephonyCall.h',
     'TelephonyCallGroup.h',
     'TelephonyCallId.h',
+    'USSDSession.h'
 ]
 
 EXPORTS.mozilla.dom.telephony += [
     'ipc/TelephonyChild.h',
     'ipc/TelephonyParent.h',
     'TelephonyCallback.h',
     'TelephonyCommon.h',
     'TelephonyDialCallback.h',
@@ -32,16 +33,17 @@ UNIFIED_SOURCES += [
     'ipc/TelephonyIPCService.cpp',
     'ipc/TelephonyParent.cpp',
     'Telephony.cpp',
     'TelephonyCall.cpp',
     'TelephonyCallback.cpp',
     'TelephonyCallGroup.cpp',
     'TelephonyCallId.cpp',
     'TelephonyDialCallback.cpp',
+    'USSDSession.cpp',
 ]
 
 IPDL_SOURCES += [
     'ipc/PTelephony.ipdl',
     'ipc/PTelephonyRequest.ipdl',
     'ipc/TelephonyTypes.ipdlh'
 ]
 
--- a/dom/telephony/nsITelephonyService.idl
+++ b/dom/telephony/nsITelephonyService.idl
@@ -235,17 +235,17 @@ interface nsITelephonyDialCallback : nsI
 #define TELEPHONY_SERVICE_CONTRACTID \
   "@mozilla.org/telephony/telephonyservice;1"
 %}
 
 /**
  * XPCOM component (in the content process) that provides the telephony
  * information.
  */
-[scriptable, uuid(600929fb-269c-4ae2-a61f-3c8a62cf5456)]
+[scriptable, uuid(79188caa-046a-48e1-b9c5-2e891504dc7a)]
 interface nsITelephonyService : nsISupports
 {
   const unsigned short CALL_STATE_UNKNOWN = 0;
   const unsigned short CALL_STATE_DIALING = 1;
   const unsigned short CALL_STATE_ALERTING = 2;
   const unsigned short CALL_STATE_CONNECTING = 3;
   const unsigned short CALL_STATE_CONNECTED = 4;
   const unsigned short CALL_STATE_HOLDING = 5;
@@ -294,16 +294,26 @@ interface nsITelephonyService : nsISuppo
   void holdCall(in unsigned long clientId, in unsigned long callIndex);
   void resumeCall(in unsigned long clientId, in unsigned long callIndex);
 
   void conferenceCall(in unsigned long clientId);
   void separateCall(in unsigned long clientId, in unsigned long callIndex);
   void holdConference(in unsigned long clientId);
   void resumeConference(in unsigned long clientId);
 
+  /**
+   * Send an USSD on existing session. It results in error if the session is
+   * not existed.
+   *
+   * If successful, callback.notifySuccess() will be called.
+   * Otherwise, callback.notifyError() will be called.
+   */
+  void sendUSSD(in unsigned long clientId, in DOMString ussd,
+                in nsITelephonyCallback callback);
+
   attribute bool microphoneMuted;
   attribute bool speakerEnabled;
 };
 
 %{C++
 template<typename T> struct already_AddRefed;
 
 already_AddRefed<nsITelephonyService>