Bug 1058397 - Part 3: USSDSession implement. r=hsinyi
☠☠ backed out by 104587650717 ☠ ☠
authorSzu-Yu Chen [:aknow] <szchen@mozilla.com>
Wed, 08 Oct 2014 20:01:00 +0200
changeset 232848 774f695d8c0ad10e38f660e80e29b61714920744
parent 232847 c7d7762d5e2735e8747232e7c4c7d2c32fc1b20a
child 232849 3ad7043d211646b2161e448818d4b92d6a900bc2
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershsinyi
bugs1058397
milestone35.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 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);
    },
 
@@ -5676,19 +5702,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,96 @@
+/* -*- 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;
+
+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>