Merge "Remote Connection implementation."
diff --git a/Android.mk b/Android.mk
index b784f3d..43b7e59 100644
--- a/Android.mk
+++ b/Android.mk
@@ -333,6 +333,7 @@
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 4db4c5a..75db903 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27595,8 +27595,9 @@
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 @@
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 @@
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<android.telecomm.ConnectionRequest, android.telecomm.RemoteConnection>);
method public final void disconnect(java.lang.String);
method public java.util.Collection<android.telecomm.Connection> getAllConnections();
method public final void hold(java.lang.String);
+ method public void lookupRemoteSubscriptions(android.net.Uri, android.telecomm.SimpleResponse<android.net.Uri, java.util.List<android.telecomm.Subscription>>);
+ 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 @@
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<android.telecomm.ConnectionRequest, android.telecomm.RemoteConnection>);
+ method public java.util.List<android.telecomm.Subscription> 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();
@@ -38354,6 +38395,22 @@
}
+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<android.content.ComponentName>, java.util.List<android.os.IBinder>) 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 cf7c901..12ba1ff 100644
--- a/telecomm/java/android/telecomm/CallService.java
+++ b/telecomm/java/android/telecomm/CallService.java
@@ -27,8 +27,6 @@
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 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 @@
*/
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 2c5f078..30084d0 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<ICallServiceAdapter> 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 @@
* @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 @@
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 @@
* @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 @@
* @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 @@
* @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 @@
*
* @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 @@
* @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 @@
* @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 @@
* @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 @@
*
* @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 @@
* @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 @@
* @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 @@
* @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 bf5727b..61ac816 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -33,18 +33,29 @@
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.
*/
public String getCallId() { return mCallId; }
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 5aba941..4b69a3c 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 @@
// Mappings from Connections to IDs as understood by the current CallService implementation
private final Map<String, Connection> mConnectionById = new HashMap<>();
private final Map<Connection, String> mIdByConnection = new HashMap<>();
+ private final RemoteConnectionManager mRemoteConnectionManager = new RemoteConnectionManager();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private SimpleResponse<Uri, List<Subscription>> mSubscriptionLookupResponse;
+ private Uri mSubscriptionLookupHandle;
+ private boolean mAreSubscriptionsInitialized = false;
private final Connection.Listener mConnectionListener = new Connection.Listener() {
@Override
@@ -314,6 +329,71 @@
}
/**
+ * @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<ComponentName> componentNames,
+ final List<IBinder> 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<Uri, List<Subscription>> 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<ConnectionRequest, RemoteConnection> response) {
+ mRemoteConnectionManager.createOutgoingConnection(request, response);
+ }
+
+ /**
* Returns all connections currently associated with this connection service.
*/
public Collection<Connection> getAllConnections() {
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
new file mode 100644
index 0000000..92db0d8
--- /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<Listener> 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<Listener> listeners = new HashSet<Listener>(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 0000000..4201f23
--- /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<ComponentName, RemoteConnectionService> 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<Subscription> getSubscriptions(Uri handle) {
+ List<Subscription> 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<ConnectionRequest, RemoteConnection> 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 0000000..86a43cb
--- /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<ConnectionRequest, RemoteConnection> 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<ConnectionRequest, RemoteConnection> 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<Subscription> lookupSubscriptions(Uri handle) {
+ // TODO(santoscordon): Update this so that is actually calls into the RemoteConnection
+ // each time.
+ List<Subscription> 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 0000000..8e84adb
--- /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<IN, OUT> {
+
+ /**
+ * 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 270c551..87c8859 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -19,6 +19,8 @@
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 @@
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 0000000..42c77d7
--- /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<ComponentName> components, in List<IBinder> callServices);
+}