/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* vim: set ts=2 et sw=2 tw=40: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Telephony. * * The Initial Developer of the Original Code is * The Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ben Turner (Original Author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "Telephony.h" #include "nsIDocument.h" #include "nsIURI.h" #include "nsIURL.h" #include "nsPIDOMWindow.h" #include "jsapi.h" #include "mozilla/Preferences.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" #include "nsIInterfaceRequestorUtils.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "SystemWorkerManager.h" #include "CallEvent.h" #include "TelephonyCall.h" USING_TELEPHONY_NAMESPACE using mozilla::Preferences; #define DOM_TELEPHONY_APP_PHONE_URL_PREF "dom.telephony.app.phone.url" namespace { typedef nsAutoTArray TelephonyList; TelephonyList* gTelephonyList; template inline nsresult nsTArrayToJSArray(JSContext* aCx, JSObject* aGlobal, const nsTArray >& aSourceArray, JSObject** aResultArray) { NS_ASSERTION(aCx, "Null context!"); NS_ASSERTION(aGlobal, "Null global!"); JSAutoRequest ar(aCx); JSAutoEnterCompartment ac; if (!ac.enter(aCx, aGlobal)) { NS_WARNING("Failed to enter compartment!"); return NS_ERROR_FAILURE; } JSObject* arrayObj; if (aSourceArray.IsEmpty()) { arrayObj = JS_NewArrayObject(aCx, 0, nsnull); } else { nsTArray valArray; valArray.SetLength(aSourceArray.Length()); for (PRUint32 index = 0; index < valArray.Length(); index++) { nsISupports* obj = aSourceArray[index]->ToISupports(); nsresult rv = nsContentUtils::WrapNative(aCx, aGlobal, obj, &valArray[index]); NS_ENSURE_SUCCESS(rv, rv); } arrayObj = JS_NewArrayObject(aCx, valArray.Length(), valArray.Elements()); } if (!arrayObj) { return NS_ERROR_OUT_OF_MEMORY; } // XXX This is not what Jonas wants. He wants it to be live. if (!JS_FreezeObject(aCx, arrayObj)) { return NS_ERROR_FAILURE; } *aResultArray = arrayObj; return NS_OK; } } // anonymous namespace Telephony::Telephony() : mActiveCall(nsnull), mCallsArray(nsnull), mRooted(false) { if (!gTelephonyList) { gTelephonyList = new TelephonyList(); } gTelephonyList->AppendElement(this); } Telephony::~Telephony() { if (mRIL && mRILTelephonyCallback) { mRIL->UnregisterCallback(mRILTelephonyCallback); } if (mRooted) { NS_DROP_JS_OBJECTS(this, Telephony); } NS_ASSERTION(gTelephonyList, "This should never be null!"); NS_ASSERTION(gTelephonyList->Contains(this), "Should be in the list!"); if (gTelephonyList->Length() == 1) { delete gTelephonyList; gTelephonyList = nsnull; } else { gTelephonyList->RemoveElement(this); } } // static already_AddRefed Telephony::Create(nsPIDOMWindow* aOwner, nsIRadioInterfaceLayer* aRIL) { NS_ASSERTION(aOwner, "Null owner!"); NS_ASSERTION(aRIL, "Null RIL!"); nsCOMPtr sgo = do_QueryInterface(aOwner); NS_ENSURE_TRUE(sgo, nsnull); nsCOMPtr scriptContext = sgo->GetContext(); NS_ENSURE_TRUE(scriptContext, nsnull); nsRefPtr telephony = new Telephony(); telephony->mOwner = aOwner; telephony->mScriptContext.swap(scriptContext); telephony->mRIL = aRIL; telephony->mRILTelephonyCallback = new RILTelephonyCallback(telephony); nsresult rv = aRIL->EnumerateCalls(telephony->mRILTelephonyCallback); NS_ENSURE_SUCCESS(rv, nsnull); rv = aRIL->RegisterCallback(telephony->mRILTelephonyCallback); NS_ENSURE_SUCCESS(rv, nsnull); return telephony.forget(); } already_AddRefed Telephony::CreateNewDialingCall(const nsAString& aNumber) { nsRefPtr call = TelephonyCall::Create(this, aNumber, nsIRadioInterfaceLayer::CALL_STATE_DIALING); NS_ASSERTION(call, "This should never fail!"); NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!"); return call.forget(); } void Telephony::NoteDialedCallFromOtherInstance(const nsAString& aNumber) { // We don't need to hang on to this call object, it is held alive by mCalls. nsRefPtr call = CreateNewDialingCall(aNumber); } nsresult Telephony::NotifyCallsChanged(TelephonyCall* aCall) { nsRefPtr event = CallEvent::Create(aCall); NS_ASSERTION(event, "This should never fail!"); nsresult rv = event->Dispatch(ToIDOMEventTarget(), NS_LITERAL_STRING("callschanged")); NS_ENSURE_SUCCESS(rv, rv); 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++) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCalls[i]"); cb.NoteXPCOMChild(tmp->mCalls[index]->ToISupports()); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Telephony, nsDOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_CALLBACK(tmp->mCallsArray, "mCallsArray") NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Telephony, nsDOMEventTargetHelper) NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(incoming) NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(callschanged) tmp->mCalls.Clear(); tmp->mActiveCall = nsnull; tmp->mCallsArray = nsnull; NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Telephony) NS_INTERFACE_MAP_ENTRY(nsIDOMTelephony) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Telephony) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(Telephony, nsDOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(Telephony, nsDOMEventTargetHelper) DOMCI_DATA(Telephony, Telephony) NS_IMPL_ISUPPORTS1(Telephony::RILTelephonyCallback, nsIRILTelephonyCallback) NS_IMETHODIMP Telephony::Dial(const nsAString& aNumber, nsIDOMTelephonyCall** aResult) { NS_ENSURE_ARG(!aNumber.IsEmpty()); for (PRUint32 index = 0; index < mCalls.Length(); index++) { const nsRefPtr& 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 = mRIL->Dial(aNumber); NS_ENSURE_SUCCESS(rv, rv); nsRefPtr 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 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); return NS_OK; } NS_IMETHODIMP Telephony::SetMuted(bool aMuted) { nsresult rv = mRIL->SetMicrophoneMuted(aMuted); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP Telephony::GetSpeakerEnabled(bool* aSpeakerEnabled) { nsresult rv = mRIL->GetSpeakerEnabled(aSpeakerEnabled); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP Telephony::SetSpeakerEnabled(bool aSpeakerEnabled) { nsresult rv = mRIL->SetSpeakerEnabled(aSpeakerEnabled); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP Telephony::GetActive(jsval* aActive) { if (!mActiveCall) { aActive->setNull(); return NS_OK; } nsresult rv = nsContentUtils::WrapNative(mScriptContext->GetNativeContext(), mScriptContext->GetNativeGlobal(), mActiveCall->ToISupports(), aActive); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP Telephony::GetCalls(jsval* aCalls) { JSObject* calls = mCallsArray; if (!calls) { nsresult rv = nsTArrayToJSArray(mScriptContext->GetNativeContext(), mScriptContext->GetNativeGlobal(), mCalls, &calls); NS_ENSURE_SUCCESS(rv, rv); if (!mRooted) { NS_HOLD_JS_OBJECTS(this, Telephony); mRooted = true; } mCallsArray = calls; } aCalls->setObject(*calls); return NS_OK; } NS_IMETHODIMP Telephony::StartTone(const nsAString& aDTMFChar) { if (aDTMFChar.IsEmpty()) { NS_WARNING("Empty tone string will be ignored"); return NS_OK; } if (aDTMFChar.Length() > 1) { return NS_ERROR_INVALID_ARG; } nsresult rv = mRIL->StartTone(aDTMFChar); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP Telephony::StopTone() { nsresult rv = mRIL->StopTone(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMPL_EVENT_HANDLER(Telephony, incoming) NS_IMPL_EVENT_HANDLER(Telephony, callschanged) NS_IMETHODIMP Telephony::CallStateChanged(PRUint32 aCallIndex, PRUint16 aCallState, const nsAString& aNumber) { NS_ASSERTION(aCallIndex != kOutgoingPlaceholderCallIndex, "This should never happen!"); nsRefPtr modifiedCall; nsRefPtr outgoingCall; for (PRUint32 index = 0; index < mCalls.Length(); index++) { nsRefPtr& tempCall = mCalls[index]; if (tempCall->CallIndex() == kOutgoingPlaceholderCallIndex) { NS_ASSERTION(!outgoingCall, "More than one outgoing call not supported!"); NS_ASSERTION(tempCall->CallState() == nsIRadioInterfaceLayer::CALL_STATE_DIALING, "Something really wrong here!"); // Stash this for later, we may need it if aCallIndex doesn't match one of // our other calls. outgoingCall = tempCall; } else if (tempCall->CallIndex() == aCallIndex) { // We already know about this call so just update its state. modifiedCall = tempCall; outgoingCall = nsnull; break; } } // If nothing matched above and the call state isn't incoming but we do have // an outgoing call then we must be seeing a status update for our outgoing // call. if (!modifiedCall && aCallState != nsIRadioInterfaceLayer::CALL_STATE_INCOMING && outgoingCall) { outgoingCall->UpdateCallIndex(aCallIndex); modifiedCall.swap(outgoingCall); } if (modifiedCall) { // Change state. modifiedCall->ChangeState(aCallState); // See if this should replace our current active call. if (aCallState == nsIRadioInterfaceLayer::CALL_STATE_CONNECTED) { mActiveCall = modifiedCall; } return NS_OK; } // Didn't know anything about this call before now, must be incoming. NS_ASSERTION(aCallState == nsIRadioInterfaceLayer::CALL_STATE_INCOMING, "Serious logic problem here!"); nsRefPtr call = TelephonyCall::Create(this, aNumber, aCallState, aCallIndex); NS_ASSERTION(call, "This should never fail!"); NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!"); // Dispatch incoming event. nsRefPtr event = CallEvent::Create(call); NS_ASSERTION(event, "This should never fail!"); nsresult rv = event->Dispatch(ToIDOMEventTarget(), NS_LITERAL_STRING("incoming")); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP Telephony::EnumerateCallState(PRUint32 aCallIndex, PRUint16 aCallState, const nsAString& aNumber, bool aIsActive, bool* aContinue) { #ifdef DEBUG // Make sure we don't somehow add duplicates. for (PRUint32 index = 0; index < mCalls.Length(); index++) { NS_ASSERTION(mCalls[index]->CallIndex() != aCallIndex, "Something is really wrong here!"); } #endif nsRefPtr call = TelephonyCall::Create(this, aNumber, aCallState, aCallIndex); NS_ASSERTION(call, "This should never fail!"); NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!"); if (aIsActive) { NS_ASSERTION(!mActiveCall, "Already have an active call!"); mActiveCall = call; } *aContinue = true; return NS_OK; } nsresult NS_NewTelephony(nsPIDOMWindow* aWindow, nsIDOMTelephony** aTelephony) { NS_ASSERTION(aWindow, "Null pointer!"); // Make sure we're dealing with an inner window. nsPIDOMWindow* innerWindow = aWindow->IsInnerWindow() ? aWindow : aWindow->GetCurrentInnerWindow(); NS_ENSURE_TRUE(innerWindow, NS_ERROR_FAILURE); // Make sure we're being called from a window that we have permission to // access. if (!nsContentUtils::CanCallerAccess(innerWindow)) { return NS_ERROR_DOM_SECURITY_ERR; } // Need the document in order to make security decisions. nsCOMPtr document = do_QueryInterface(innerWindow->GetExtantDocument()); NS_ENSURE_TRUE(document, NS_NOINTERFACE); // Do security checks. We assume that chrome is always allowed and we also // allow a single page specified by preferences. if (!nsContentUtils::IsSystemPrincipal(document->NodePrincipal())) { nsCOMPtr originalURI; nsresult rv = document->NodePrincipal()->GetURI(getter_AddRefs(originalURI)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr documentURI; rv = originalURI->Clone(getter_AddRefs(documentURI)); NS_ENSURE_SUCCESS(rv, rv); // Strip the query string (if there is one) before comparing. nsCOMPtr documentURL = do_QueryInterface(documentURI); if (documentURL) { rv = documentURL->SetQuery(EmptyCString()); NS_ENSURE_SUCCESS(rv, rv); } bool allowed = false; // The pref may not exist but in that case we deny access just as we do if // the url doesn't match. nsCString whitelist; if (NS_SUCCEEDED(Preferences::GetCString(DOM_TELEPHONY_APP_PHONE_URL_PREF, &whitelist))) { nsCOMPtr ios = do_GetIOService(); NS_ENSURE_TRUE(ios, NS_ERROR_FAILURE); nsCCharSeparatedTokenizer tokenizer(whitelist, ','); while (tokenizer.hasMoreTokens()) { nsCOMPtr uri; if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), tokenizer.nextToken(), nsnull, nsnull, ios))) { rv = documentURI->EqualsExceptRef(uri, &allowed); NS_ENSURE_SUCCESS(rv, rv); if (allowed) { break; } } } } if (!allowed) { *aTelephony = nsnull; return NS_OK; } } // Security checks passed, make a telephony object. nsIInterfaceRequestor* ireq = SystemWorkerManager::GetInterfaceRequestor(); NS_ENSURE_TRUE(ireq, NS_ERROR_UNEXPECTED); nsCOMPtr ril = do_GetInterface(ireq); NS_ENSURE_TRUE(ril, NS_ERROR_UNEXPECTED); nsRefPtr telephony = Telephony::Create(innerWindow, ril); NS_ENSURE_TRUE(telephony, NS_ERROR_UNEXPECTED); telephony.forget(aTelephony); return NS_OK; }