1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "mozilla/dom/CallEvent.h"
10 #include "mozilla/dom/Promise.h"
11 #include "mozilla/dom/TelephonyBinding.h"
13 #include "mozilla/Preferences.h"
14 #include "mozilla/dom/UnionTypes.h"
15 #include "nsCharSeparatedTokenizer.h"
16 #include "nsContentUtils.h"
18 #include "nsNetUtil.h"
19 #include "nsPIDOMWindow.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsThreadUtils.h"
23 #include "CallsList.h"
24 #include "TelephonyCall.h"
25 #include "TelephonyCallGroup.h"
26 #include "TelephonyCallId.h"
27 #include "TelephonyDialCallback.h"
29 using namespace mozilla::dom
;
30 using namespace mozilla::dom::telephony
;
31 using mozilla::ErrorResult
;
33 class Telephony::Listener
: public nsITelephonyListener
35 Telephony
* mTelephony
;
37 virtual ~Listener() {}
41 NS_FORWARD_SAFE_NSITELEPHONYLISTENER(mTelephony
)
43 Listener(Telephony
* aTelephony
)
44 : mTelephony(aTelephony
)
46 MOZ_ASSERT(mTelephony
);
52 MOZ_ASSERT(mTelephony
);
57 class Telephony::EnumerationAck
: public nsRunnable
59 nsRefPtr
<Telephony
> mTelephony
;
63 EnumerationAck(Telephony
* aTelephony
, const nsAString
& aType
)
64 : mTelephony(aTelephony
), mType(aType
)
66 MOZ_ASSERT(mTelephony
);
71 mTelephony
->NotifyEvent(mType
);
76 Telephony::Telephony(nsPIDOMWindow
* aOwner
)
77 : DOMEventTargetHelper(aOwner
), mEnumerated(false)
81 Telephony::~Telephony()
90 mListener
->Disconnect();
93 mService
->UnregisterListener(mListener
);
102 Telephony::WrapObject(JSContext
* aCx
)
104 return TelephonyBinding::Wrap(aCx
, this);
108 already_AddRefed
<Telephony
>
109 Telephony::Create(nsPIDOMWindow
* aOwner
, ErrorResult
& aRv
)
111 NS_ASSERTION(aOwner
, "Null owner!");
113 nsCOMPtr
<nsITelephonyService
> ril
=
114 do_GetService(TELEPHONY_SERVICE_CONTRACTID
);
116 aRv
.Throw(NS_ERROR_UNEXPECTED
);
120 nsCOMPtr
<nsIScriptGlobalObject
> sgo
= do_QueryInterface(aOwner
);
122 aRv
.Throw(NS_ERROR_UNEXPECTED
);
126 nsCOMPtr
<nsIScriptContext
> scriptContext
= sgo
->GetContext();
127 if (!scriptContext
) {
128 aRv
.Throw(NS_ERROR_UNEXPECTED
);
132 nsRefPtr
<Telephony
> telephony
= new Telephony(aOwner
);
134 telephony
->mService
= ril
;
135 telephony
->mListener
= new Listener(telephony
);
136 telephony
->mCallsList
= new CallsList(telephony
);
137 telephony
->mGroup
= TelephonyCallGroup::Create(telephony
);
139 nsresult rv
= ril
->EnumerateCalls(telephony
->mListener
);
145 return telephony
.forget();
150 Telephony::IsValidNumber(const nsAString
& aNumber
)
152 return !aNumber
.IsEmpty();
157 Telephony::GetNumServices() {
158 return mozilla::Preferences::GetInt("ril.numRadioInterfaces", 1);
163 Telephony::IsValidServiceId(uint32_t aServiceId
)
165 return aServiceId
< GetNumServices();
170 Telephony::IsActiveState(uint16_t aCallState
) {
171 return aCallState
== nsITelephonyService::CALL_STATE_DIALING
||
172 aCallState
== nsITelephonyService::CALL_STATE_ALERTING
||
173 aCallState
== nsITelephonyService::CALL_STATE_HOLDING
||
174 aCallState
== nsITelephonyService::CALL_STATE_DISCONNECTING
||
175 aCallState
== nsITelephonyService::CALL_STATE_CONNECTED
;
179 Telephony::ProvidedOrDefaultServiceId(const Optional
<uint32_t>& aServiceId
)
181 if (aServiceId
.WasPassed()) {
182 return aServiceId
.Value();
184 uint32_t serviceId
= 0;
185 mService
->GetDefaultServiceId(&serviceId
);
191 Telephony::HasDialingCall()
193 for (uint32_t i
= 0; i
< mCalls
.Length(); i
++) {
194 const nsRefPtr
<TelephonyCall
>& call
= mCalls
[i
];
195 if (call
->CallState() > nsITelephonyService::CALL_STATE_UNKNOWN
&&
196 call
->CallState() < nsITelephonyService::CALL_STATE_CONNECTED
) {
204 already_AddRefed
<Promise
>
205 Telephony::DialInternal(uint32_t aServiceId
, const nsAString
& aNumber
,
206 bool aEmergency
, ErrorResult
& aRv
)
208 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(GetOwner());
213 nsRefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
218 if (!IsValidNumber(aNumber
) || !IsValidServiceId(aServiceId
)) {
219 promise
->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
220 return promise
.forget();
223 // We only support one outgoing call at a time.
224 if (HasDialingCall()) {
225 promise
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
226 return promise
.forget();
229 nsCOMPtr
<nsITelephonyDialCallback
> callback
=
230 new TelephonyDialCallback(GetOwner(), this, promise
);
232 nsresult rv
= mService
->Dial(aServiceId
, aNumber
, aEmergency
, callback
);
234 promise
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
235 return promise
.forget();
238 return promise
.forget();
241 already_AddRefed
<TelephonyCallId
>
242 Telephony::CreateCallId(const nsAString
& aNumber
, uint16_t aNumberPresentation
,
243 const nsAString
& aName
, uint16_t aNamePresentation
)
245 nsRefPtr
<TelephonyCallId
> id
=
246 new TelephonyCallId(GetOwner(), aNumber
, aNumberPresentation
,
247 aName
, aNamePresentation
);
252 already_AddRefed
<TelephonyCall
>
253 Telephony::CreateCall(TelephonyCallId
* aId
, uint32_t aServiceId
,
254 uint32_t aCallIndex
, uint16_t aCallState
,
255 bool aEmergency
, bool aConference
,
256 bool aSwitchable
, bool aMergeable
)
258 // We don't have to create an already ended call.
259 if (aCallState
== nsITelephonyService::CALL_STATE_DISCONNECTED
) {
263 nsRefPtr
<TelephonyCall
> call
=
264 TelephonyCall::Create(this, aId
, aServiceId
, aCallIndex
, aCallState
,
265 aEmergency
, aConference
, aSwitchable
, aMergeable
);
267 NS_ASSERTION(call
, "This should never fail!");
268 NS_ASSERTION(aConference
? mGroup
->CallsArray().Contains(call
)
269 : mCalls
.Contains(call
),
270 "Should have auto-added new call!");
272 return call
.forget();
276 Telephony::NotifyEvent(const nsAString
& aType
)
278 return DispatchCallEvent(aType
, nullptr);
282 Telephony::NotifyCallsChanged(TelephonyCall
* aCall
)
284 return DispatchCallEvent(NS_LITERAL_STRING("callschanged"), aCall
);
287 already_AddRefed
<TelephonyCall
>
288 Telephony::GetCall(uint32_t aServiceId
, uint32_t aCallIndex
)
290 nsRefPtr
<TelephonyCall
> call
;
292 for (uint32_t i
= 0; i
< mCalls
.Length(); i
++) {
293 nsRefPtr
<TelephonyCall
>& tempCall
= mCalls
[i
];
294 if (tempCall
->ServiceId() == aServiceId
&&
295 tempCall
->CallIndex() == aCallIndex
) {
301 return call
.forget();
304 already_AddRefed
<TelephonyCall
>
305 Telephony::GetCallFromEverywhere(uint32_t aServiceId
, uint32_t aCallIndex
)
307 nsRefPtr
<TelephonyCall
> call
= GetCall(aServiceId
, aCallIndex
);
310 call
= mGroup
->GetCall(aServiceId
, aCallIndex
);
313 return call
.forget();
316 NS_IMPL_CYCLE_COLLECTION_CLASS(Telephony
)
318 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Telephony
,
319 DOMEventTargetHelper
)
320 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCalls
)
321 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallsList
)
322 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGroup
)
323 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
325 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Telephony
,
326 DOMEventTargetHelper
)
328 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCalls
)
329 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallsList
)
330 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGroup
)
331 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
333 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Telephony
)
334 // Telephony does not expose nsITelephonyListener. mListener is the exposed
335 // nsITelephonyListener and forwards the calls it receives to us.
336 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
338 NS_IMPL_ADDREF_INHERITED(Telephony
, DOMEventTargetHelper
)
339 NS_IMPL_RELEASE_INHERITED(Telephony
, DOMEventTargetHelper
)
341 NS_IMPL_ISUPPORTS(Telephony::Listener
, nsITelephonyListener
)
345 already_AddRefed
<Promise
>
346 Telephony::Dial(const nsAString
& aNumber
, const Optional
<uint32_t>& aServiceId
,
349 uint32_t serviceId
= ProvidedOrDefaultServiceId(aServiceId
);
350 nsRefPtr
<Promise
> promise
= DialInternal(serviceId
, aNumber
, false, aRv
);
351 return promise
.forget();
354 already_AddRefed
<Promise
>
355 Telephony::DialEmergency(const nsAString
& aNumber
,
356 const Optional
<uint32_t>& aServiceId
,
359 uint32_t serviceId
= ProvidedOrDefaultServiceId(aServiceId
);
360 nsRefPtr
<Promise
> promise
= DialInternal(serviceId
, aNumber
, true, aRv
);
361 return promise
.forget();
365 Telephony::StartTone(const nsAString
& aDTMFChar
,
366 const Optional
<uint32_t>& aServiceId
,
369 uint32_t serviceId
= ProvidedOrDefaultServiceId(aServiceId
);
371 if (aDTMFChar
.IsEmpty()) {
372 NS_WARNING("Empty tone string will be ignored");
376 if (aDTMFChar
.Length() > 1 || !IsValidServiceId(serviceId
)) {
377 aRv
.Throw(NS_ERROR_INVALID_ARG
);
381 aRv
= mService
->StartTone(serviceId
, aDTMFChar
);
385 Telephony::StopTone(const Optional
<uint32_t>& aServiceId
, ErrorResult
& aRv
)
387 uint32_t serviceId
= ProvidedOrDefaultServiceId(aServiceId
);
389 if (!IsValidServiceId(serviceId
)) {
390 aRv
.Throw(NS_ERROR_INVALID_ARG
);
394 aRv
= mService
->StopTone(serviceId
);
398 Telephony::GetMuted(ErrorResult
& aRv
) const
401 aRv
= mService
->GetMicrophoneMuted(&muted
);
407 Telephony::SetMuted(bool aMuted
, ErrorResult
& aRv
)
409 aRv
= mService
->SetMicrophoneMuted(aMuted
);
413 Telephony::GetSpeakerEnabled(ErrorResult
& aRv
) const
415 bool enabled
= false;
416 aRv
= mService
->GetSpeakerEnabled(&enabled
);
422 Telephony::SetSpeakerEnabled(bool aEnabled
, ErrorResult
& aRv
)
424 aRv
= mService
->SetSpeakerEnabled(aEnabled
);
428 Telephony::GetActive(Nullable
<OwningTelephonyCallOrTelephonyCallGroup
>& aValue
)
430 if (mGroup
->CallState() == nsITelephonyService::CALL_STATE_CONNECTED
) {
431 aValue
.SetValue().SetAsTelephonyCallGroup() = mGroup
;
433 // Search the first active call.
434 for (uint32_t i
= 0; i
< mCalls
.Length(); i
++) {
435 if (IsActiveState(mCalls
[i
]->CallState())) {
436 aValue
.SetValue().SetAsTelephonyCall() = mCalls
[i
];
444 already_AddRefed
<CallsList
>
445 Telephony::Calls() const
447 nsRefPtr
<CallsList
> list
= mCallsList
;
448 return list
.forget();
451 already_AddRefed
<TelephonyCallGroup
>
452 Telephony::ConferenceGroup() const
454 nsRefPtr
<TelephonyCallGroup
> group
= mGroup
;
455 return group
.forget();
461 Telephony::EventListenerAdded(nsIAtom
* aType
)
463 if (aType
== nsGkAtoms::oncallschanged
) {
464 // Fire oncallschanged on the next tick if the calls array is ready.
465 EnqueueEnumerationAck(NS_LITERAL_STRING("callschanged"));
466 } else if (aType
== nsGkAtoms::onready
) {
467 EnqueueEnumerationAck(NS_LITERAL_STRING("ready"));
471 // nsITelephonyListener
474 Telephony::CallStateChanged(uint32_t aServiceId
, uint32_t aCallIndex
,
475 uint16_t aCallState
, const nsAString
& aNumber
,
476 uint16_t aNumberPresentation
, const nsAString
& aName
,
477 uint16_t aNamePresentation
, bool aIsOutgoing
,
478 bool aIsEmergency
, bool aIsConference
,
479 bool aIsSwitchable
, bool aIsMergeable
)
481 nsRefPtr
<TelephonyCall
> modifiedCall
482 = GetCallFromEverywhere(aServiceId
, aCallIndex
);
485 modifiedCall
->UpdateEmergency(aIsEmergency
);
486 modifiedCall
->UpdateSwitchable(aIsSwitchable
);
487 modifiedCall
->UpdateMergeable(aIsMergeable
);
488 nsRefPtr
<TelephonyCallId
> id
= modifiedCall
->Id();
489 id
->UpdateNumber(aNumber
);
491 if (modifiedCall
->CallState() != aCallState
) {
492 // We don't fire the statechange event on a call in conference here.
493 // Instead, the event will be fired later in
494 // TelephonyCallGroup::ChangeState(). Thus the sequence of firing the
495 // statechange events is guaranteed: first on TelephonyCallGroup then on
496 // individual TelephonyCall objects.
497 bool fireEvent
= !aIsConference
;
498 modifiedCall
->ChangeStateInternal(aCallState
, fireEvent
);
501 nsRefPtr
<TelephonyCallGroup
> group
= modifiedCall
->GetGroup();
503 if (!group
&& aIsConference
) {
504 // Add to conference.
505 NS_ASSERTION(mCalls
.Contains(modifiedCall
), "Should in mCalls");
506 mGroup
->AddCall(modifiedCall
);
507 RemoveCall(modifiedCall
);
508 } else if (group
&& !aIsConference
) {
509 // Remove from conference.
510 NS_ASSERTION(mGroup
->CallsArray().Contains(modifiedCall
), "Should in mGroup");
511 mGroup
->RemoveCall(modifiedCall
);
512 AddCall(modifiedCall
);
518 nsRefPtr
<TelephonyCallId
> id
= CreateCallId(aNumber
, aNumberPresentation
,
519 aName
, aNamePresentation
);
520 nsRefPtr
<TelephonyCall
> call
=
521 CreateCall(id
, aServiceId
, aCallIndex
, aCallState
,
522 aIsEmergency
, aIsConference
, aIsSwitchable
, aIsMergeable
);
524 if (call
&& aCallState
== nsITelephonyService::CALL_STATE_INCOMING
) {
525 nsresult rv
= DispatchCallEvent(NS_LITERAL_STRING("incoming"), call
);
526 NS_ENSURE_SUCCESS(rv
, rv
);
533 Telephony::ConferenceCallStateChanged(uint16_t aCallState
)
535 mGroup
->ChangeState(aCallState
);
540 Telephony::EnumerateCallStateComplete()
542 MOZ_ASSERT(!mEnumerated
);
546 if (NS_FAILED(NotifyEvent(NS_LITERAL_STRING("ready")))) {
547 NS_WARNING("Failed to notify ready!");
550 if (NS_FAILED(NotifyCallsChanged(nullptr))) {
551 NS_WARNING("Failed to notify calls changed!");
554 if (NS_FAILED(mService
->RegisterListener(mListener
))) {
555 NS_WARNING("Failed to register listener!");
561 Telephony::EnumerateCallState(uint32_t aServiceId
, uint32_t aCallIndex
,
562 uint16_t aCallState
, const nsAString
& aNumber
,
563 uint16_t aNumberPresentation
, const nsAString
& aName
,
564 uint16_t aNamePresentation
, bool aIsOutgoing
,
565 bool aIsEmergency
, bool aIsConference
,
566 bool aIsSwitchable
, bool aIsMergeable
)
568 // We request calls enumeration in constructor, and the asynchronous result
569 // will be sent back through the callback function EnumerateCallState().
570 // However, it is likely to have call state changes, i.e. CallStateChanged()
571 // being called, before the enumeration result comes back. We'd make sure
572 // we don't somehow add duplicates due to the race condition.
573 nsRefPtr
<TelephonyCall
> call
= GetCallFromEverywhere(aServiceId
, aCallIndex
);
578 // Didn't know anything about this call before now.
579 nsRefPtr
<TelephonyCallId
> id
= CreateCallId(aNumber
, aNumberPresentation
,
580 aName
, aNamePresentation
);
581 call
= CreateCall(id
, aServiceId
, aCallIndex
, aCallState
,
582 aIsEmergency
, aIsConference
, aIsSwitchable
, aIsMergeable
);
588 Telephony::SupplementaryServiceNotification(uint32_t aServiceId
,
590 uint16_t aNotification
)
592 nsRefPtr
<TelephonyCall
> associatedCall
;
593 if (!mCalls
.IsEmpty()) {
594 associatedCall
= GetCall(aServiceId
, aCallIndex
);
598 switch (aNotification
) {
599 case nsITelephonyService::NOTIFICATION_REMOTE_HELD
:
600 rv
= DispatchCallEvent(NS_LITERAL_STRING("remoteheld"), associatedCall
);
602 case nsITelephonyService::NOTIFICATION_REMOTE_RESUMED
:
603 rv
= DispatchCallEvent(NS_LITERAL_STRING("remoteresumed"), associatedCall
);
606 NS_ERROR("Got a bad notification!");
607 return NS_ERROR_UNEXPECTED
;
610 NS_ENSURE_SUCCESS(rv
, rv
);
615 Telephony::NotifyError(uint32_t aServiceId
,
617 const nsAString
& aError
)
619 if (mCalls
.IsEmpty()) {
620 NS_ERROR("No existing call!");
621 return NS_ERROR_UNEXPECTED
;
624 nsRefPtr
<TelephonyCall
> callToNotify
= GetCall(aServiceId
, aCallIndex
);
626 NS_ERROR("Don't call me with a bad call index!");
627 return NS_ERROR_UNEXPECTED
;
630 // Set the call state to 'disconnected' and remove it from the calls list.
631 callToNotify
->NotifyError(aError
);
637 Telephony::NotifyCdmaCallWaiting(uint32_t aServiceId
, const nsAString
& aNumber
,
638 uint16_t aNumberPresentation
,
639 const nsAString
& aName
,
640 uint16_t aNamePresentation
)
642 MOZ_ASSERT(mCalls
.Length() == 1);
644 nsRefPtr
<TelephonyCall
> callToNotify
= mCalls
[0];
645 MOZ_ASSERT(callToNotify
&& callToNotify
->ServiceId() == aServiceId
);
647 nsRefPtr
<TelephonyCallId
> id
=
648 new TelephonyCallId(GetOwner(), aNumber
, aNumberPresentation
, aName
,
650 callToNotify
->UpdateSecondId(id
);
651 DispatchCallEvent(NS_LITERAL_STRING("callschanged"), callToNotify
);
656 Telephony::NotifyConferenceError(const nsAString
& aName
,
657 const nsAString
& aMessage
)
659 mGroup
->NotifyError(aName
, aMessage
);
664 Telephony::DispatchCallEvent(const nsAString
& aType
,
665 TelephonyCall
* aCall
)
667 // If it is an incoming event, the call should not be null.
668 MOZ_ASSERT(!aType
.EqualsLiteral("incoming") || aCall
);
671 init
.mBubbles
= false;
672 init
.mCancelable
= false;
675 nsRefPtr
<CallEvent
> event
= CallEvent::Constructor(this, aType
, init
);
677 return DispatchTrustedEvent(event
);
681 Telephony::EnqueueEnumerationAck(const nsAString
& aType
)
687 nsCOMPtr
<nsIRunnable
> task
= new EnumerationAck(this, aType
);
688 if (NS_FAILED(NS_DispatchToCurrentThread(task
))) {
689 NS_WARNING("Failed to dispatch to current thread!");