From 52d8a15e146e682319380322f94ceb6d93fa1a97 Mon Sep 17 00:00:00 2001 From: Santos Cordon Date: Tue, 17 Jun 2014 19:08:45 -0700 Subject: [PATCH] Remote Connection implementation. API classes for daisy-chaining connection services. Change-Id: I90991697456377b72ec73d2ef835864cb0b7737a --- Android.mk | 1 + api/current.txt | 59 +++- .../java/android/telecomm/CallService.java | 6 +- .../android/telecomm/CallServiceAdapter.java | 201 +++++++++---- .../android/telecomm/ConnectionRequest.java | 11 + .../android/telecomm/ConnectionService.java | 80 +++++ .../android/telecomm/RemoteConnection.java | 227 +++++++++++++++ .../telecomm/RemoteConnectionManager.java | 74 +++++ .../telecomm/RemoteConnectionService.java | 274 ++++++++++++++++++ .../java/android/telecomm/SimpleResponse.java | 38 +++ .../telecomm/ICallServiceAdapter.aidl | 4 + .../telecomm/RemoteServiceCallback.aidl | 27 ++ 12 files changed, 942 insertions(+), 60 deletions(-) create mode 100644 telecomm/java/android/telecomm/RemoteConnection.java create mode 100644 telecomm/java/android/telecomm/RemoteConnectionManager.java create mode 100644 telecomm/java/android/telecomm/RemoteConnectionService.java create mode 100644 telecomm/java/android/telecomm/SimpleResponse.java create mode 100644 telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl diff --git a/Android.mk b/Android.mk index 8ba881532c9b6..cc982e404f07a 100644 --- a/Android.mk +++ b/Android.mk @@ -333,6 +333,7 @@ LOCAL_SRC_FILES += \ telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl \ telecomm/java/com/android/internal/telecomm/IInCallService.aidl \ telecomm/java/com/android/internal/telecomm/ITelecommService.aidl \ + telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ telephony/java/com/android/internal/telephony/ITelephony.aidl \ diff --git a/api/current.txt b/api/current.txt index a678773de8efd..616655c7e4446 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27595,8 +27595,9 @@ package android.telecomm { method public abstract void unhold(java.lang.String); } - public final class CallServiceAdapter { + public final class CallServiceAdapter implements android.os.IBinder.DeathRecipient { method public void addConferenceCall(java.lang.String); + method public void binderDied(); method public void handleFailedOutgoingCall(android.telecomm.ConnectionRequest, int, java.lang.String); method public void handleSuccessfulOutgoingCall(java.lang.String); method public void handoffCall(java.lang.String); @@ -27728,10 +27729,12 @@ package android.telecomm { public final class ConnectionRequest implements android.os.Parcelable { ctor public ConnectionRequest(android.net.Uri, android.os.Bundle); ctor public ConnectionRequest(java.lang.String, android.net.Uri, android.os.Bundle); + ctor public ConnectionRequest(android.telecomm.Subscription, java.lang.String, android.net.Uri, android.os.Bundle); method public int describeContents(); method public java.lang.String getCallId(); method public android.os.Bundle getExtras(); method public android.net.Uri getHandle(); + method public android.telecomm.Subscription getSubscription(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -27741,9 +27744,12 @@ package android.telecomm { method public final void abort(java.lang.String); method public final void answer(java.lang.String); method public final void call(android.telecomm.CallInfo); + method public void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.SimpleResponse); method public final void disconnect(java.lang.String); method public java.util.Collection getAllConnections(); method public final void hold(java.lang.String); + method public void lookupRemoteSubscriptions(android.net.Uri, android.telecomm.SimpleResponse>); + method public void maybeRespondToSubscriptionLookup(); method public final void onAudioStateChanged(java.lang.String, android.telecomm.CallAudioState); method public void onConnectionAdded(android.telecomm.Connection); method public void onConnectionRemoved(android.telecomm.Connection); @@ -27813,11 +27819,46 @@ package android.telecomm { method protected abstract void updateCall(android.telecomm.InCallCall); } + public final class RemoteConnection { + method public void addListener(android.telecomm.RemoteConnection.Listener); + method public void answer(); + method public void disconnect(); + method public int getDisconnectCause(); + method public java.lang.String getDisconnectMessage(); + method public void hold(); + method public void playDtmf(char); + method public void postDialContinue(boolean); + method public void reject(); + method public void removeListener(android.telecomm.RemoteConnection.Listener); + method public void stopDtmf(); + method public void unhold(); + } + + public static abstract interface RemoteConnection.Listener { + method public abstract void onAudioStateChanged(android.telecomm.RemoteConnection, android.telecomm.CallAudioState); + method public abstract void onDestroyed(android.telecomm.RemoteConnection); + method public abstract void onDisconnected(android.telecomm.RemoteConnection, int, java.lang.String); + method public abstract void onPostDialWait(android.telecomm.RemoteConnection, java.lang.String); + method public abstract void onRequestingRingback(android.telecomm.RemoteConnection, boolean); + method public abstract void onStateChanged(android.telecomm.RemoteConnection, int); + } + + public class RemoteConnectionService implements android.os.IBinder.DeathRecipient { + method public void binderDied(); + method public void createOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.SimpleResponse); + method public java.util.List lookupSubscriptions(android.net.Uri); + } + public abstract interface Response { method public abstract void onError(IN, int, java.lang.String); method public abstract void onResult(IN, OUT...); } + public abstract interface SimpleResponse { + method public abstract void onError(IN); + method public abstract void onResult(IN, OUT); + } + public class Subscription implements android.os.Parcelable { ctor public Subscription(android.content.ComponentName, java.lang.String, android.net.Uri, int, int, int, boolean, boolean); method public int describeContents(); @@ -38353,6 +38394,22 @@ package android.widget { } +package com.android.internal.telecomm { + + public abstract interface RemoteServiceCallback implements android.os.IInterface { + method public abstract void onError() throws android.os.RemoteException; + method public abstract void onResult(java.util.List, java.util.List) throws android.os.RemoteException; + } + + public static abstract class RemoteServiceCallback.Stub extends android.os.Binder implements com.android.internal.telecomm.RemoteServiceCallback { + ctor public RemoteServiceCallback.Stub(); + method public android.os.IBinder asBinder(); + method public static com.android.internal.telecomm.RemoteServiceCallback asInterface(android.os.IBinder); + method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException; + } + +} + package com.android.internal.util { public abstract interface Predicate { diff --git a/telecomm/java/android/telecomm/CallService.java b/telecomm/java/android/telecomm/CallService.java index cf7c901010397..12ba1ffd6a0ab 100644 --- a/telecomm/java/android/telecomm/CallService.java +++ b/telecomm/java/android/telecomm/CallService.java @@ -27,8 +27,6 @@ import com.android.internal.os.SomeArgs; import com.android.internal.telecomm.ICallService; import com.android.internal.telecomm.ICallServiceAdapter; -import java.util.List; - /** * Base implementation of CallService which can be used to provide calls for the system * in-call UI. CallService is a one-way service from the framework's CallsManager to any app @@ -72,7 +70,7 @@ public abstract class CallService extends Service { public void handleMessage(Message msg) { switch (msg.what) { case MSG_SET_CALL_SERVICE_ADAPTER: - mAdapter = new CallServiceAdapter((ICallServiceAdapter) msg.obj); + mAdapter.addAdapter((ICallServiceAdapter) msg.obj); onAdapterAttached(mAdapter); break; case MSG_CALL: @@ -259,7 +257,7 @@ public abstract class CallService extends Service { */ private final CallServiceBinder mBinder = new CallServiceBinder(); - private CallServiceAdapter mAdapter = null; + private CallServiceAdapter mAdapter = new CallServiceAdapter(); /** {@inheritDoc} */ @Override diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java index 2c5f0787cd853..30084d04da96c 100644 --- a/telecomm/java/android/telecomm/CallServiceAdapter.java +++ b/telecomm/java/android/telecomm/CallServiceAdapter.java @@ -16,38 +16,86 @@ package android.telecomm; +import android.content.ComponentName; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; import android.os.RemoteException; +import com.android.internal.telecomm.ICallService; import com.android.internal.telecomm.ICallServiceAdapter; +import com.android.internal.telecomm.RemoteServiceCallback; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Provides methods for ICallService implementations to interact with the system phone app. * TODO(santoscordon): Need final public-facing comments in this file. + * TODO(santoscordon): Rename this to CallServiceAdapterDemultiplexer (or something). */ -public final class CallServiceAdapter { - private final ICallServiceAdapter mAdapter; +public final class CallServiceAdapter implements DeathRecipient { + private final Set mAdapters = new HashSet<>(); /** - * {@hide} + * @hide */ - public CallServiceAdapter(ICallServiceAdapter adapter) { - mAdapter = adapter; + public CallServiceAdapter() { + } + + /** + * @hide + */ + public void addAdapter(ICallServiceAdapter adapter) { + if (mAdapters.add(adapter)) { + try { + adapter.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + mAdapters.remove(adapter); + } + } + } + + /** + * @hide + */ + public void removeAdapter(ICallServiceAdapter adapter) { + if (mAdapters.remove(adapter)) { + adapter.asBinder().unlinkToDeath(this, 0); + } + } + + /** ${inheritDoc} */ + @Override + public void binderDied() { + ICallServiceAdapter adapterToRemove = null; + for (ICallServiceAdapter adapter : mAdapters) { + if (!adapter.asBinder().isBinderAlive()) { + adapterToRemove = adapter; + break; + } + } + + if (adapterToRemove != null) { + removeAdapter(adapterToRemove); + } } /** * Provides Telecomm with the details of an incoming call. An invocation of this method must - * follow {@link CallService#setIncomingCallId} and use the call ID specified therein. Upon - * the invocation of this method, Telecomm will bring up the incoming-call interface where the - * user can elect to answer or reject a call. + * follow {@link CallService#setIncomingCallId} and use the call ID specified therein. Upon the + * invocation of this method, Telecomm will bring up the incoming-call interface where the user + * can elect to answer or reject a call. * * @param callInfo The details of the relevant call. */ public void notifyIncomingCall(CallInfo callInfo) { - try { - mAdapter.notifyIncomingCall(callInfo); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.notifyIncomingCall(callInfo); + } catch (RemoteException e) { + } } } @@ -59,9 +107,11 @@ public final class CallServiceAdapter { * @param callId The ID of the outgoing call. */ public void handleSuccessfulOutgoingCall(String callId) { - try { - mAdapter.handleSuccessfulOutgoingCall(callId); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.handleSuccessfulOutgoingCall(callId); + } catch (RemoteException e) { + } } } @@ -76,9 +126,11 @@ public final class CallServiceAdapter { ConnectionRequest request, int errorCode, String errorMsg) { - try { - mAdapter.handleFailedOutgoingCall(request, errorCode, errorMsg); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.handleFailedOutgoingCall(request, errorCode, errorMsg); + } catch (RemoteException e) { + } } } @@ -89,9 +141,11 @@ public final class CallServiceAdapter { * @param callId The unique ID of the call whose state is changing to active. */ public void setActive(String callId) { - try { - mAdapter.setActive(callId); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.setActive(callId); + } catch (RemoteException e) { + } } } @@ -101,9 +155,11 @@ public final class CallServiceAdapter { * @param callId The unique ID of the call whose state is changing to ringing. */ public void setRinging(String callId) { - try { - mAdapter.setRinging(callId); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.setRinging(callId); + } catch (RemoteException e) { + } } } @@ -113,9 +169,11 @@ public final class CallServiceAdapter { * @param callId The unique ID of the call whose state is changing to dialing. */ public void setDialing(String callId) { - try { - mAdapter.setDialing(callId); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.setDialing(callId); + } catch (RemoteException e) { + } } } @@ -124,13 +182,15 @@ public final class CallServiceAdapter { * * @param callId The unique ID of the call whose state is changing to disconnected. * @param disconnectCause The reason for the disconnection, any of - * {@link android.telephony.DisconnectCause}. + * {@link android.telephony.DisconnectCause}. * @param disconnectMessage Optional call-service-provided message about the disconnect. */ public void setDisconnected(String callId, int disconnectCause, String disconnectMessage) { - try { - mAdapter.setDisconnected(callId, disconnectCause, disconnectMessage); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.setDisconnected(callId, disconnectCause, disconnectMessage); + } catch (RemoteException e) { + } } } @@ -140,9 +200,11 @@ public final class CallServiceAdapter { * @param callId - The unique ID of the call whose state is changing to be on hold. */ public void setOnHold(String callId) { - try { - mAdapter.setOnHold(callId); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.setOnHold(callId); + } catch (RemoteException e) { + } } } @@ -153,9 +215,11 @@ public final class CallServiceAdapter { * @param ringback Whether Telecomm should start playing a ringback tone. */ public void setRequestingRingback(String callId, boolean ringback) { - try { - mAdapter.setRequestingRingback(callId, ringback); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.setRequestingRingback(callId, ringback); + } catch (RemoteException e) { + } } } @@ -167,9 +231,11 @@ public final class CallServiceAdapter { * @hide */ public void setCanConference(String callId, boolean canConference) { - try { - mAdapter.setCanConference(callId, canConference); - } catch (RemoteException ignored) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.setCanConference(callId, canConference); + } catch (RemoteException ignored) { + } } } @@ -179,13 +245,15 @@ public final class CallServiceAdapter { * * @param callId The unique ID of the call being conferenced. * @param conferenceCallId The unique ID of the conference call. Null if call is not - * conferenced. + * conferenced. * @hide */ public void setIsConferenced(String callId, String conferenceCallId) { - try { - mAdapter.setIsConferenced(callId, conferenceCallId); - } catch (RemoteException ignored) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.setIsConferenced(callId, conferenceCallId); + } catch (RemoteException ignored) { + } } } @@ -197,16 +265,20 @@ public final class CallServiceAdapter { * @hide */ public void removeCall(String callId) { - try { - mAdapter.removeCall(callId); - } catch (RemoteException ignored) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.removeCall(callId); + } catch (RemoteException ignored) { + } } } public void onPostDialWait(String callId, String remaining) { - try { - mAdapter.onPostDialWait(callId, remaining); - } catch (RemoteException ignored) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.onPostDialWait(callId, remaining); + } catch (RemoteException ignored) { + } } } @@ -216,9 +288,11 @@ public final class CallServiceAdapter { * @param callId The identifier of the call to handoff. */ public void handoffCall(String callId) { - try { - mAdapter.handoffCall(callId); - } catch (RemoteException e) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.handoffCall(callId); + } catch (RemoteException e) { + } } } @@ -228,9 +302,26 @@ public final class CallServiceAdapter { * @param callId The unique ID of the conference call. */ public void addConferenceCall(String callId) { - try { - mAdapter.addConferenceCall(callId, null); - } catch (RemoteException ignored) { + for (ICallServiceAdapter adapter : mAdapters) { + try { + adapter.addConferenceCall(callId, null); + } catch (RemoteException ignored) { + } + } + } + + /** + * Retrieves a list of remote connection services usable to place calls. + * @hide + */ + public void queryRemoteConnectionServices(RemoteServiceCallback callback) { + // Only supported when there is only one adapter. + if (mAdapters.size() == 1) { + try { + mAdapters.iterator().next().queryRemoteConnectionServices(callback); + } catch (RemoteException e) { + Log.e(this, e, "Exception trying to query for remote CSs"); + } } } } diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java index bf5727bd0dd36..61ac81629459e 100644 --- a/telecomm/java/android/telecomm/ConnectionRequest.java +++ b/telecomm/java/android/telecomm/ConnectionRequest.java @@ -33,17 +33,28 @@ public final class ConnectionRequest implements Parcelable { private final String mCallId; private final Uri mHandle; private final Bundle mExtras; + private final Subscription mSubscription; public ConnectionRequest(Uri handle, Bundle extras) { this(null, handle, extras); } public ConnectionRequest(String callId, Uri handle, Bundle extras) { + this(null, callId, handle, extras); + } + + public ConnectionRequest(Subscription subscription, String callId, Uri handle, Bundle extras) { mCallId = callId; mHandle = handle; mExtras = extras; + mSubscription = subscription; } + /** + * The subscription which should be used to place the call. + */ + public Subscription getSubscription() { return mSubscription; } + /** * An identifier for this call. */ diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index 5aba941eb6aa3..4b69a3c8247ac 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -16,12 +16,21 @@ package android.telecomm; +import android.content.ComponentName; import android.net.Uri; import android.os.Bundle; + +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; import android.telephony.DisconnectCause; +import com.android.internal.telecomm.ICallService; +import com.android.internal.telecomm.RemoteServiceCallback; + import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -36,6 +45,12 @@ public abstract class ConnectionService extends CallService { // Mappings from Connections to IDs as understood by the current CallService implementation private final Map mConnectionById = new HashMap<>(); private final Map mIdByConnection = new HashMap<>(); + private final RemoteConnectionManager mRemoteConnectionManager = new RemoteConnectionManager(); + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + private SimpleResponse> mSubscriptionLookupResponse; + private Uri mSubscriptionLookupHandle; + private boolean mAreSubscriptionsInitialized = false; private final Connection.Listener mConnectionListener = new Connection.Listener() { @Override @@ -313,6 +328,71 @@ public abstract class ConnectionService extends CallService { getAdapter().onPostDialWait(mIdByConnection.get(conn), remaining); } + /** + * @hide + */ + @Override + protected void onAdapterAttached(CallServiceAdapter adapter) { + if (mAreSubscriptionsInitialized) { + // No need to query again if we already did it. + return; + } + + getAdapter().queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { + @Override + public void onResult( + final List componentNames, + final List callServices) { + mHandler.post(new Runnable() { + @Override public void run() { + for (int i = 0; i < componentNames.size() && i < callServices.size(); i++) { + mRemoteConnectionManager.addConnectionService( + componentNames.get(i), + ICallService.Stub.asInterface(callServices.get(i))); + } + mAreSubscriptionsInitialized = true; + Log.d(this, "remote call services found: " + callServices); + maybeRespondToSubscriptionLookup(); + } + }); + } + + @Override + public void onError() { + mHandler.post(new Runnable() { + @Override public void run() { + mAreSubscriptionsInitialized = true; + maybeRespondToSubscriptionLookup(); + } + }); + } + }); + } + + public void lookupRemoteSubscriptions( + Uri handle, SimpleResponse> response) { + mSubscriptionLookupResponse = response; + mSubscriptionLookupHandle = handle; + maybeRespondToSubscriptionLookup(); + } + + public void maybeRespondToSubscriptionLookup() { + if (mAreSubscriptionsInitialized && mSubscriptionLookupResponse != null) { + mSubscriptionLookupResponse.onResult( + mSubscriptionLookupHandle, + mRemoteConnectionManager.getSubscriptions(mSubscriptionLookupHandle)); + + mSubscriptionLookupHandle = null; + mSubscriptionLookupResponse = null; + } + } + + public void createRemoteOutgoingConnection( + ConnectionRequest request, + SimpleResponse response) { + mRemoteConnectionManager.createOutgoingConnection(request, response); + } + /** * Returns all connections currently associated with this connection service. */ diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java new file mode 100644 index 0000000000000..92db0d858b982 --- /dev/null +++ b/telecomm/java/android/telecomm/RemoteConnection.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecomm; + +import android.net.Uri; + +import android.os.Bundle; +import android.os.RemoteException; +import android.telephony.DisconnectCause; + +import com.android.internal.telecomm.ICallService; + +import java.util.HashSet; +import java.util.Set; + +/** + * RemoteConnection object used by RemoteConnectionService. + */ +public final class RemoteConnection { + public interface Listener { + void onStateChanged(RemoteConnection connection, int state); + void onAudioStateChanged(RemoteConnection connection, CallAudioState state); + void onDisconnected(RemoteConnection connection, int cause, String message); + void onRequestingRingback(RemoteConnection connection, boolean ringback); + void onPostDialWait(RemoteConnection connection, String remainingDigits); + void onDestroyed(RemoteConnection connection); + } + + private final ICallService mCallService; + private final String mConnectionId; + private final Set mListeners = new HashSet<>(); + + private int mState; + private int mDisconnectCause = DisconnectCause.NOT_VALID; + private String mDisconnectMessage; + private boolean mRequestingRingback; + private boolean mConnected; + + /** + * @hide + */ + RemoteConnection(ICallService callService, String connectionId) { + mCallService = callService; + mConnectionId = connectionId; + + mConnected = true; + } + + public void addListener(Listener listener) { + mListeners.add(listener); + } + + public void removeListener(Listener listener) { + mListeners.remove(listener); + } + + public int getDisconnectCause() { + return mDisconnectCause; + } + + public String getDisconnectMessage() { + return mDisconnectMessage; + } + + public void answer() { + try { + if (mConnected) { + mCallService.answer(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + public void reject() { + try { + if (mConnected) { + mCallService.reject(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + public void hold() { + try { + if (mConnected) { + mCallService.hold(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + public void unhold() { + try { + if (mConnected) { + mCallService.unhold(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + public void disconnect() { + try { + if (mConnected) { + mCallService.disconnect(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + public void playDtmf(char digit) { + try { + if (mConnected) { + mCallService.playDtmfTone(mConnectionId, digit); + } + } catch (RemoteException ignored) { + } + } + + public void stopDtmf() { + try { + if (mConnected) { + mCallService.stopDtmfTone(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + public void postDialContinue(boolean proceed) { + try { + if (mConnected) { + mCallService.onPostDialContinue(mConnectionId, proceed); + } + } catch (RemoteException ignored) { + } + } + + /** + * @hide + */ + void setState(int state) { + if (mState != state) { + mState = state; + for (Listener l: mListeners) { + l.onStateChanged(this, state); + } + } + } + + /** + * @hide + */ + void setAudioState(CallAudioState state) { + for (Listener l: mListeners) { + l.onAudioStateChanged(this, state); + } + } + + /** + * @hide + */ + void setDisconnected(int cause, String message) { + if (mState != Connection.State.DISCONNECTED) { + mState = Connection.State.DISCONNECTED; + mDisconnectCause = cause; + mDisconnectMessage = message; + + for (Listener l : mListeners) { + l.onDisconnected(this, cause, message); + } + } + } + + /** + * @hide + */ + void setRequestingRingback(boolean ringback) { + if (mRequestingRingback != ringback) { + mRequestingRingback = ringback; + for (Listener l : mListeners) { + l.onRequestingRingback(this, ringback); + } + } + } + + /** + * @hide + */ + void setDestroyed() { + if (!mListeners.isEmpty()) { + // Make sure that the listeners are notified that the call is destroyed first. + if (mState != Connection.State.DISCONNECTED) { + setDisconnected(DisconnectCause.ERROR_UNSPECIFIED, "Connection destroyed."); + } + + Set listeners = new HashSet(mListeners); + mListeners.clear(); + for (Listener l : listeners) { + l.onDestroyed(this); + } + + mConnected = false; + } + } + + /** + * @hide + */ + void setPostDialWait(String remainingDigits) { + for (Listener l : mListeners) { + l.onPostDialWait(this, remainingDigits); + } + } +} diff --git a/telecomm/java/android/telecomm/RemoteConnectionManager.java b/telecomm/java/android/telecomm/RemoteConnectionManager.java new file mode 100644 index 0000000000000..4201f23e42901 --- /dev/null +++ b/telecomm/java/android/telecomm/RemoteConnectionManager.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + R* limitations under the License. + */ + +package android.telecomm; + +import android.content.ComponentName; +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.internal.telecomm.ICallService; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @hide + */ +public class RemoteConnectionManager { + private Map mRemoteConnectionServices = new HashMap<>(); + + void addConnectionService(ComponentName componentName, ICallService callService) { + if (!mRemoteConnectionServices.containsKey(componentName)) { + try { + RemoteConnectionService remoteConnectionService = + new RemoteConnectionService(componentName, callService); + mRemoteConnectionServices.put(componentName, remoteConnectionService); + } catch (RemoteException ignored) { + } + } + } + + List getSubscriptions(Uri handle) { + List subscriptions = new LinkedList<>(); + Log.d(this, "Getting subscriptions: " + mRemoteConnectionServices.keySet()); + for (RemoteConnectionService remoteService : mRemoteConnectionServices.values()) { + // TODO(santoscordon): Eventually this will be async. + subscriptions.addAll(remoteService.lookupSubscriptions(handle)); + } + return subscriptions; + } + + public void createOutgoingConnection( + ConnectionRequest request, + final SimpleResponse response) { + Subscription subscription = request.getSubscription(); + if (subscription == null) { + throw new IllegalArgumentException("subscription must be specified."); + } + + ComponentName componentName = request.getSubscription().getComponentName(); + if (!mRemoteConnectionServices.containsKey(componentName)) { + throw new UnsupportedOperationException("subscription not supported: " + componentName); + } else { + RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName); + remoteService.createOutgoingConnection(request, response); + } + } +} diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java new file mode 100644 index 0000000000000..86a43cb90f39c --- /dev/null +++ b/telecomm/java/android/telecomm/RemoteConnectionService.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + R* limitations under the License. + */ + +package android.telecomm; + +import android.content.ComponentName; +import android.net.Uri; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.telephony.DisconnectCause; + +import android.text.TextUtils; + +import com.android.internal.telecomm.ICallService; +import com.android.internal.telecomm.ICallServiceAdapter; +import com.android.internal.telecomm.RemoteServiceCallback; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +/** + * Remote connection service which other connection services can use to place calls on their behalf. + */ +public class RemoteConnectionService implements DeathRecipient { + private final ICallService mCallService; + private final ComponentName mComponentName; + + private String mConnectionId; + private ConnectionRequest mPendingRequest; + private SimpleResponse mPendingResponse; + // Remote connection services only support a single connection. + private RemoteConnection mConnection; + + private final ICallServiceAdapter mAdapter = new ICallServiceAdapter.Stub() { + + /** ${inheritDoc} */ + @Override + public void notifyIncomingCall(CallInfo callInfo) { + Log.w(this, "notifyIncomingCall not implemented in Remote connection"); + } + + /** ${inheritDoc} */ + @Override + public void handleSuccessfulOutgoingCall(String connectionId) { + if (isPendingConnection(connectionId)) { + mConnection = new RemoteConnection(mCallService, connectionId); + mPendingResponse.onResult(mPendingRequest, mConnection); + clearPendingInformation(); + } + } + + /** ${inheritDoc} */ + @Override + public void handleFailedOutgoingCall( + ConnectionRequest request, int errorCode, String errorMessage) { + if (isPendingConnection(request.getCallId())) { + // Use mPendingRequest instead of request so that we use the same object that was + // passed in to us. + mPendingResponse.onError(request); + mConnectionId = null; + clearPendingInformation(); + } + } + + /** ${inheritDoc} */ + @Override + public void setActive(String connectionId) { + if (isCurrentConnection(connectionId)) { + mConnection.setState(Connection.State.ACTIVE); + } + } + + /** ${inheritDoc} */ + @Override + public void setRinging(String connectionId) { + if (isCurrentConnection(connectionId)) { + mConnection.setState(Connection.State.RINGING); + } + } + + /** ${inheritDoc} */ + @Override + public void setDialing(String connectionId) { + if (isCurrentConnection(connectionId)) { + mConnection.setState(Connection.State.DIALING); + } + } + + /** ${inheritDoc} */ + @Override + public void setDisconnected( + String connectionId, int disconnectCause, String disconnectMessage) { + if (isCurrentConnection(connectionId)) { + mConnection.setDisconnected(disconnectCause, disconnectMessage); + } + } + + /** ${inheritDoc} */ + @Override + public void setOnHold(String connectionId) { + if (isCurrentConnection(connectionId)) { + mConnection.setState(Connection.State.HOLDING); + } + } + + /** ${inheritDoc} */ + @Override + public void setRequestingRingback(String connectionId, boolean isRequestingRingback) { + if (isCurrentConnection(connectionId)) { + mConnection.setRequestingRingback(isRequestingRingback); + } + } + + /** ${inheritDoc} */ + @Override + public void setCanConference(String connectionId, boolean canConference) { + // not supported for remote connections. + } + + /** ${inheritDoc} */ + @Override + public void setIsConferenced(String connectionId, String conferenceConnectionId) { + // not supported for remote connections. + } + + /** ${inheritDoc} */ + @Override + public void addConferenceCall(String connectionId, CallInfo callInfo) { + // not supported for remote connections. + } + + /** ${inheritDoc} */ + @Override + public void removeCall(String connectionId) { + if (isCurrentConnection(connectionId)) { + destroyConnection(); + } + } + + /** ${inheritDoc} */ + @Override + public void onPostDialWait(String connectionId, String remainingDigits) { + if (isCurrentConnection(connectionId)) { + mConnection.setPostDialWait(remainingDigits); + } + } + + /** ${inheritDoc} */ + @Override + public void handoffCall(String connectionId) { + // unnecessary. + } + + /** ${inheritDoc} */ + @Override + public void queryRemoteConnectionServices(RemoteServiceCallback callback) { + try { + // Not supported from remote connection service. + callback.onError(); + } catch (RemoteException e) { + } + } + }; + + RemoteConnectionService(ComponentName componentName, ICallService callService) + throws RemoteException { + mComponentName = componentName; + mCallService = callService; + + // TODO(santoscordon): Rename from setCallServiceAdapter to addCallServiceAdapter. + mCallService.setCallServiceAdapter(mAdapter); + mCallService.asBinder().linkToDeath(this, 0); + } + + @Override + public String toString() { + return "[RemoteCS - " + mCallService.asBinder().toString() + "]"; + } + + /** ${inheritDoc} */ + @Override + public void binderDied() { + if (mConnection != null) { + destroyConnection(); + } + + release(); + } + + /** + * Places an outgoing call. + */ + public void createOutgoingConnection( + ConnectionRequest request, + SimpleResponse response) { + + if (mConnectionId == null) { + String id = UUID.randomUUID().toString(); + CallInfo callInfo = new CallInfo(id, CallState.NEW, request.getHandle()); + try { + mCallService.call(callInfo); + mConnectionId = id; + mPendingResponse = response; + mPendingRequest = request; + } catch (RemoteException e) { + response.onError(request); + } + } else { + response.onError(request); + } + } + + // TODO(santoscordon): Handle incoming connections + // public void handleIncomingConnection() {} + + public List lookupSubscriptions(Uri handle) { + // TODO(santoscordon): Update this so that is actually calls into the RemoteConnection + // each time. + List subscriptions = new LinkedList<>(); + subscriptions.add(new Subscription( + mComponentName, + null /* id */, + null /* handle */, + 0 /* labelResId */, + 0 /* shortDescriptionResId */, + 0 /* iconResId */, + true /* isEnabled */, + false /* isSystemDefault */)); + return subscriptions; + } + + /** + * Releases the resources associated with this Remote connection service. Should be called when + * the remote service is no longer being used. + */ + void release() { + mCallService.asBinder().unlinkToDeath(this, 0); + } + + private boolean isPendingConnection(String id) { + return TextUtils.equals(mConnectionId, id) && mPendingResponse != null; + } + + private boolean isCurrentConnection(String id) { + return mConnection != null && TextUtils.equals(mConnectionId, id); + } + + private void clearPendingInformation() { + mPendingRequest = null; + mPendingResponse = null; + } + + private void destroyConnection() { + mConnection.setDestroyed(); + mConnection = null; + mConnectionId = null; + } +} diff --git a/telecomm/java/android/telecomm/SimpleResponse.java b/telecomm/java/android/telecomm/SimpleResponse.java new file mode 100644 index 0000000000000..8e84adb809c16 --- /dev/null +++ b/telecomm/java/android/telecomm/SimpleResponse.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecomm; + +/** + * Used to inform a client of asynchronously returned results. + */ +public interface SimpleResponse { + + /** + * Provide a set of results. + * + * @param request The original request. + * @param result The results. + */ + void onResult(IN request, OUT result); + + /** + * Indicates the inability to provide results. + * + * @param request The original request. + */ + void onError(IN request); +} diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl index 270c551f19491..87c8859cb24a4 100644 --- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl @@ -19,6 +19,8 @@ package com.android.internal.telecomm; import android.telecomm.CallInfo; import android.telecomm.ConnectionRequest; +import com.android.internal.telecomm.RemoteServiceCallback; + /** * Internal remote callback interface for call services. * @@ -56,4 +58,6 @@ oneway interface ICallServiceAdapter { void onPostDialWait(String callId, String remaining); void handoffCall(String callId); + + void queryRemoteConnectionServices(RemoteServiceCallback callback); } diff --git a/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl new file mode 100644 index 0000000000000..42c77d7284f2b --- /dev/null +++ b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telecomm; + +import android.content.ComponentName; + +/** + * Simple response callback object. + */ +oneway interface RemoteServiceCallback { + void onError(); + void onResult(in List components, in List callServices); +}