Bumping manifests a=b2g-bump
[gecko.git] / dom / telephony / Telephony.cpp
blob3602f093da1cc081ae7799ccb74f9e14be7bec4e
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/. */
7 #include "Telephony.h"
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"
17 #include "nsIURI.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() {}
39 public:
40 NS_DECL_ISUPPORTS
41 NS_FORWARD_SAFE_NSITELEPHONYLISTENER(mTelephony)
43 Listener(Telephony* aTelephony)
44 : mTelephony(aTelephony)
46 MOZ_ASSERT(mTelephony);
49 void
50 Disconnect()
52 MOZ_ASSERT(mTelephony);
53 mTelephony = nullptr;
57 class Telephony::EnumerationAck : public nsRunnable
59 nsRefPtr<Telephony> mTelephony;
60 nsString mType;
62 public:
63 EnumerationAck(Telephony* aTelephony, const nsAString& aType)
64 : mTelephony(aTelephony), mType(aType)
66 MOZ_ASSERT(mTelephony);
69 NS_IMETHOD Run()
71 mTelephony->NotifyEvent(mType);
72 return NS_OK;
76 Telephony::Telephony(nsPIDOMWindow* aOwner)
77 : DOMEventTargetHelper(aOwner), mEnumerated(false)
81 Telephony::~Telephony()
83 Shutdown();
86 void
87 Telephony::Shutdown()
89 if (mListener) {
90 mListener->Disconnect();
92 if (mService) {
93 mService->UnregisterListener(mListener);
94 mService = nullptr;
97 mListener = nullptr;
101 JSObject*
102 Telephony::WrapObject(JSContext* aCx)
104 return TelephonyBinding::Wrap(aCx, this);
107 // static
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);
115 if (!ril) {
116 aRv.Throw(NS_ERROR_UNEXPECTED);
117 return nullptr;
120 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
121 if (!sgo) {
122 aRv.Throw(NS_ERROR_UNEXPECTED);
123 return nullptr;
126 nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
127 if (!scriptContext) {
128 aRv.Throw(NS_ERROR_UNEXPECTED);
129 return nullptr;
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);
140 if (NS_FAILED(rv)) {
141 aRv.Throw(rv);
142 return nullptr;
145 return telephony.forget();
148 // static
149 bool
150 Telephony::IsValidNumber(const nsAString& aNumber)
152 return !aNumber.IsEmpty();
155 // static
156 uint32_t
157 Telephony::GetNumServices() {
158 return mozilla::Preferences::GetInt("ril.numRadioInterfaces", 1);
161 // static
162 bool
163 Telephony::IsValidServiceId(uint32_t aServiceId)
165 return aServiceId < GetNumServices();
168 // static
169 bool
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;
178 uint32_t
179 Telephony::ProvidedOrDefaultServiceId(const Optional<uint32_t>& aServiceId)
181 if (aServiceId.WasPassed()) {
182 return aServiceId.Value();
183 } else {
184 uint32_t serviceId = 0;
185 mService->GetDefaultServiceId(&serviceId);
186 return serviceId;
190 bool
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) {
197 return true;
201 return false;
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());
209 if (!global) {
210 return nullptr;
213 nsRefPtr<Promise> promise = Promise::Create(global, aRv);
214 if (aRv.Failed()) {
215 return nullptr;
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);
233 if (NS_FAILED(rv)) {
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);
249 return id.forget();
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) {
260 return nullptr;
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();
275 nsresult
276 Telephony::NotifyEvent(const nsAString& aType)
278 return DispatchCallEvent(aType, nullptr);
281 nsresult
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) {
296 call = tempCall;
297 break;
301 return call.forget();
304 already_AddRefed<TelephonyCall>
305 Telephony::GetCallFromEverywhere(uint32_t aServiceId, uint32_t aCallIndex)
307 nsRefPtr<TelephonyCall> call = GetCall(aServiceId, aCallIndex);
309 if (!call) {
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)
327 tmp->Shutdown();
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)
343 // Telephony WebIDL
345 already_AddRefed<Promise>
346 Telephony::Dial(const nsAString& aNumber, const Optional<uint32_t>& aServiceId,
347 ErrorResult& aRv)
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,
357 ErrorResult& aRv)
359 uint32_t serviceId = ProvidedOrDefaultServiceId(aServiceId);
360 nsRefPtr<Promise> promise = DialInternal(serviceId, aNumber, true, aRv);
361 return promise.forget();
364 void
365 Telephony::StartTone(const nsAString& aDTMFChar,
366 const Optional<uint32_t>& aServiceId,
367 ErrorResult& aRv)
369 uint32_t serviceId = ProvidedOrDefaultServiceId(aServiceId);
371 if (aDTMFChar.IsEmpty()) {
372 NS_WARNING("Empty tone string will be ignored");
373 return;
376 if (aDTMFChar.Length() > 1 || !IsValidServiceId(serviceId)) {
377 aRv.Throw(NS_ERROR_INVALID_ARG);
378 return;
381 aRv = mService->StartTone(serviceId, aDTMFChar);
384 void
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);
391 return;
394 aRv = mService->StopTone(serviceId);
397 bool
398 Telephony::GetMuted(ErrorResult& aRv) const
400 bool muted = false;
401 aRv = mService->GetMicrophoneMuted(&muted);
403 return muted;
406 void
407 Telephony::SetMuted(bool aMuted, ErrorResult& aRv)
409 aRv = mService->SetMicrophoneMuted(aMuted);
412 bool
413 Telephony::GetSpeakerEnabled(ErrorResult& aRv) const
415 bool enabled = false;
416 aRv = mService->GetSpeakerEnabled(&enabled);
418 return enabled;
421 void
422 Telephony::SetSpeakerEnabled(bool aEnabled, ErrorResult& aRv)
424 aRv = mService->SetSpeakerEnabled(aEnabled);
427 void
428 Telephony::GetActive(Nullable<OwningTelephonyCallOrTelephonyCallGroup>& aValue)
430 if (mGroup->CallState() == nsITelephonyService::CALL_STATE_CONNECTED) {
431 aValue.SetValue().SetAsTelephonyCallGroup() = mGroup;
432 } else {
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];
437 return;
440 aValue.SetNull();
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();
458 // EventTarget
460 void
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
473 NS_IMETHODIMP
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);
484 if (modifiedCall) {
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);
515 return NS_OK;
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);
529 return NS_OK;
532 NS_IMETHODIMP
533 Telephony::ConferenceCallStateChanged(uint16_t aCallState)
535 mGroup->ChangeState(aCallState);
536 return NS_OK;
539 NS_IMETHODIMP
540 Telephony::EnumerateCallStateComplete()
542 MOZ_ASSERT(!mEnumerated);
544 mEnumerated = true;
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!");
557 return NS_OK;
560 NS_IMETHODIMP
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);
574 if (call) {
575 return NS_OK;
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);
584 return NS_OK;
587 NS_IMETHODIMP
588 Telephony::SupplementaryServiceNotification(uint32_t aServiceId,
589 int32_t aCallIndex,
590 uint16_t aNotification)
592 nsRefPtr<TelephonyCall> associatedCall;
593 if (!mCalls.IsEmpty()) {
594 associatedCall = GetCall(aServiceId, aCallIndex);
597 nsresult rv;
598 switch (aNotification) {
599 case nsITelephonyService::NOTIFICATION_REMOTE_HELD:
600 rv = DispatchCallEvent(NS_LITERAL_STRING("remoteheld"), associatedCall);
601 break;
602 case nsITelephonyService::NOTIFICATION_REMOTE_RESUMED:
603 rv = DispatchCallEvent(NS_LITERAL_STRING("remoteresumed"), associatedCall);
604 break;
605 default:
606 NS_ERROR("Got a bad notification!");
607 return NS_ERROR_UNEXPECTED;
610 NS_ENSURE_SUCCESS(rv, rv);
611 return NS_OK;
614 NS_IMETHODIMP
615 Telephony::NotifyError(uint32_t aServiceId,
616 int32_t aCallIndex,
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);
625 if (!callToNotify) {
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);
633 return NS_OK;
636 NS_IMETHODIMP
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,
649 aNamePresentation);
650 callToNotify->UpdateSecondId(id);
651 DispatchCallEvent(NS_LITERAL_STRING("callschanged"), callToNotify);
652 return NS_OK;
655 NS_IMETHODIMP
656 Telephony::NotifyConferenceError(const nsAString& aName,
657 const nsAString& aMessage)
659 mGroup->NotifyError(aName, aMessage);
660 return NS_OK;
663 nsresult
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);
670 CallEventInit init;
671 init.mBubbles = false;
672 init.mCancelable = false;
673 init.mCall = aCall;
675 nsRefPtr<CallEvent> event = CallEvent::Constructor(this, aType, init);
677 return DispatchTrustedEvent(event);
680 void
681 Telephony::EnqueueEnumerationAck(const nsAString& aType)
683 if (!mEnumerated) {
684 return;
687 nsCOMPtr<nsIRunnable> task = new EnumerationAck(this, aType);
688 if (NS_FAILED(NS_DispatchToCurrentThread(task))) {
689 NS_WARNING("Failed to dispatch to current thread!");