Merge "Add theme color to notifications (2/4)" into lmp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a8d1f99..43a1f5e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.BIND_CONNECTION_SERVICE" />
+ <uses-permission android:name="android.permission.BIND_INCALL_SERVICE" />
<!-- Protects the ability to register any PhoneAccount with a capability flags of either
PhoneAccount#CAPABILITY_CALL_PROVIDER or PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f2f80ee..981c131 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -91,6 +91,10 @@
<!-- Indication to not use a SIM call manager -->
<string name="do_not_use_sim_call_manager">Do not use Wi-Fi calling</string>
+ <!-- Message indicating that the user is not allowed to make non-emergency outgoing phone calls
+ due to a user restriction -->
+ <string name="outgoing_call_not_allowed">This user is not allowed to make non-emergency phone calls</string>
+
<!-- DO NOT TRANSLATE. Label for test Subscription 0. -->
<string name="test_account_0_label">Q Mobile</string>
<!-- DO NOT TRANSLATE. Label for test Subscription 1. -->
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index e2dbb79..7d694de 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -73,8 +73,6 @@
void onRequestingRingback(Call call, boolean requestingRingback);
void onPostDialWait(Call call, String remaining);
void onCallCapabilitiesChanged(Call call);
- void onExpiredConferenceCall(Call call);
- void onConfirmedConferenceCall(Call call);
void onParentChanged(Call call);
void onChildrenChanged(Call call);
void onCannedSmsResponsesLoaded(Call call);
@@ -110,10 +108,6 @@
@Override
public void onCallCapabilitiesChanged(Call call) {}
@Override
- public void onExpiredConferenceCall(Call call) {}
- @Override
- public void onConfirmedConferenceCall(Call call) {}
- @Override
public void onParentChanged(Call call) {}
@Override
public void onChildrenChanged(Call call) {}
@@ -346,15 +340,7 @@
}
int getState() {
- if (mIsConference) {
- if (!mChildCalls.isEmpty()) {
- // If we have child calls, just return the child call.
- return mChildCalls.get(0).getState();
- }
- return CallState.ACTIVE;
- } else {
- return mState;
- }
+ return mState;
}
/**
@@ -855,27 +841,11 @@
mConnectionService.onPhoneAccountClicked(this);
}
- void conferenceInto(Call conferenceCall) {
+ void conferenceWith(Call otherCall) {
if (mConnectionService == null) {
Log.w(this, "conference requested on a call without a connection service.");
} else {
- mConnectionService.conference(conferenceCall, this);
- }
- }
-
- void expireConference() {
- // The conference call expired before we got a confirmation of the conference from the
- // connection service...so start shutting down.
- clearConnectionService();
- for (Listener l : mListeners) {
- l.onExpiredConferenceCall(this);
- }
- }
-
- void confirmConference() {
- Log.v(this, "confirming Conf call %s", mListeners);
- for (Listener l : mListeners) {
- l.onConfirmedConferenceCall(this);
+ mConnectionService.conference(this, otherCall);
}
}
@@ -888,6 +858,10 @@
Log.e(this, new Exception(), "setting the parent to self");
return;
}
+ if (parentCall == mParentCall) {
+ // nothing to do
+ return;
+ }
Preconditions.checkState(parentCall == null || mParentCall == null);
Call oldParent = mParentCall;
@@ -1214,7 +1188,7 @@
}
}
- private int getStateFromConnectionState(int state) {
+ static int getStateFromConnectionState(int state) {
switch (state) {
case Connection.STATE_INITIALIZING:
return CallState.CONNECTING;
diff --git a/src/com/android/telecomm/CallActivity.java b/src/com/android/telecomm/CallActivity.java
index 06cc9cb..10db104 100644
--- a/src/com/android/telecomm/CallActivity.java
+++ b/src/com/android/telecomm/CallActivity.java
@@ -23,11 +23,12 @@
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
+import android.os.UserManager;
import android.telecomm.PhoneAccountHandle;
import android.telecomm.TelecommManager;
import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.widget.Toast;
/**
* Activity that handles system CALL actions and forwards them to {@link CallsManager}.
@@ -120,6 +121,19 @@
String uriString = intent.getData().getSchemeSpecificPart();
Uri handle = Uri.fromParts(
PhoneNumberUtils.isUriNumber(uriString) ? "sip" : "tel", uriString, null);
+
+ UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS)
+ && !TelephonyUtil.shouldProcessAsEmergency(this, handle)) {
+ // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+ // restriction.
+ Toast.makeText(this, getResources().getString(R.string.outgoing_call_not_allowed),
+ Toast.LENGTH_SHORT).show();
+ Log.d(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
+ + "restriction");
+ return;
+ }
+
PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE);
diff --git a/src/com/android/telecomm/CallIdMapper.java b/src/com/android/telecomm/CallIdMapper.java
index f5055da..b82d1e1 100644
--- a/src/com/android/telecomm/CallIdMapper.java
+++ b/src/com/android/telecomm/CallIdMapper.java
@@ -78,7 +78,7 @@
if (objId instanceof String) {
callId = (String) objId;
}
- if (!isValidCallId(callId)) {
+ if (!isValidCallId(callId) && !isValidConferenceId(callId)) {
return null;
}
@@ -94,6 +94,10 @@
return callId != null && callId.startsWith(mCallIdPrefix);
}
+ boolean isValidConferenceId(String callId) {
+ return callId != null;
+ }
+
String getNewId() {
sIdCount++;
return mCallIdPrefix + sIdCount;
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 314020d..b4e892c 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -21,6 +21,7 @@
import android.telecomm.AudioState;
import android.telecomm.CallState;
import android.telecomm.GatewayInfo;
+import android.telecomm.ParcelableConference;
import android.telecomm.PhoneAccountHandle;
import android.telephony.DisconnectCause;
@@ -188,20 +189,6 @@
}
@Override
- public void onExpiredConferenceCall(Call call) {
- call.removeListener(this);
- }
-
- @Override
- public void onConfirmedConferenceCall(Call call) {
- addCall(call);
- Log.v(this, "confirming Conf call %s", call);
- for (CallsManagerListener listener : mListeners) {
- listener.onIsConferencedChanged(call);
- }
- }
-
- @Override
public void onParentChanged(Call call) {
for (CallsManagerListener listener : mListeners) {
listener.onIsConferencedChanged(call);
@@ -417,16 +404,7 @@
* @param otherCall The other call to conference with.
*/
void conference(Call call, Call otherCall) {
- Call conferenceCall = new Call(
- mConnectionServiceRepository,
- null /* handle */,
- null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
- null /* targetPhoneAccount */,
- false /* isIncoming */,
- true /* isConference */);
- conferenceCall.addListener(this);
- call.conferenceInto(conferenceCall);
+ call.conferenceWith(otherCall);
}
/**
@@ -533,6 +511,17 @@
}
/**
+ * Instructs Telecomm to disconnect all calls.
+ */
+ void disconnectAllCalls() {
+ Log.v(this, "disconnectAllCalls");
+
+ for (Call call : mCalls) {
+ disconnectCall(call);
+ }
+ }
+
+ /**
* Instructs Telecomm to put the specified call on hold. Intended to be invoked by the
* in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
* the user hitting the hold button during an active call.
@@ -751,6 +740,27 @@
return null;
}
+ Call createConferenceCall(
+ PhoneAccountHandle phoneAccount,
+ ParcelableConference parcelableConference) {
+ Call call = new Call(
+ mConnectionServiceRepository,
+ null /* handle */,
+ null /* gatewayInfo */,
+ null /* connectionManagerPhoneAccount */,
+ phoneAccount,
+ false /* isIncoming */,
+ true /* isConference */);
+
+ setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()));
+ call.setCallCapabilities(parcelableConference.getCapabilities());
+
+ // TODO: Move this to be a part of addCall()
+ call.addListener(this);
+ addCall(call);
+ return call;
+ }
+
/**
* Adds the specified call to the main list of live calls.
*
diff --git a/src/com/android/telecomm/ConnectionServiceWrapper.java b/src/com/android/telecomm/ConnectionServiceWrapper.java
index 23ffab7..21f03e9 100644
--- a/src/com/android/telecomm/ConnectionServiceWrapper.java
+++ b/src/com/android/telecomm/ConnectionServiceWrapper.java
@@ -28,6 +28,8 @@
import android.telecomm.ConnectionRequest;
import android.telecomm.ConnectionService;
import android.telecomm.GatewayInfo;
+
+import android.telecomm.ParcelableConference;
import android.telecomm.ParcelableConnection;
import android.telecomm.PhoneAccount;
import android.telecomm.PhoneAccountHandle;
@@ -163,6 +165,7 @@
call = mCallIdMapper.getCall(args.arg1);
String disconnectMessage = (String) args.arg2;
int disconnectCause = args.argi1;
+ Log.d(this, "disconnect call %s %s", args.arg1, call);
if (call != null) {
mCallsManager.markCallAsDisconnected(call, disconnectCause,
disconnectMessage);
@@ -208,16 +211,11 @@
if (childCall != null) {
String conferenceCallId = (String) args.arg2;
if (conferenceCallId == null) {
+ Log.d(this, "unsetting parent: %s", args.arg1);
childCall.setParentCall(null);
} else {
Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
- if (conferenceCall != null &&
- !mPendingConferenceCalls.contains(conferenceCall)) {
- childCall.setParentCall(conferenceCall);
- } else {
- //Log.w(this, "setIsConferenced, unknown conference id %s",
- // conferenceCallId);
- }
+ childCall.setParentCall(conferenceCall);
}
} else {
//Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
@@ -228,12 +226,32 @@
break;
}
case MSG_ADD_CONFERENCE_CALL: {
- Call conferenceCall = mCallIdMapper.getCall(msg.obj);
- if (mPendingConferenceCalls.remove(conferenceCall)) {
- Log.v(this, "confirming conf call %s", conferenceCall);
- conferenceCall.confirmConference();
- } else {
- //Log.w(this, "addConference, unknown call id: %s", callId);
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String id = (String) args.arg1;
+ if (mCallIdMapper.getCall(id) != null) {
+ Log.w(this, "Attempting to add a conference call using an existing " +
+ "call id %s", id);
+ break;
+ }
+ ParcelableConference parcelableConference =
+ (ParcelableConference) args.arg2;
+ // need to create a new Call
+ Call conferenceCall = mCallsManager.createConferenceCall(
+ null, parcelableConference);
+ mCallIdMapper.addCall(conferenceCall, id);
+ conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
+
+ Log.d(this, "adding children to conference");
+ for (String callId : parcelableConference.getConnectionIds()) {
+ Call childCall = mCallIdMapper.getCall(callId);
+ Log.d(this, "found child: %s", callId);
+ if (childCall != null) {
+ childCall.setParentCall(conferenceCall);
+ }
+ }
+ } finally {
+ args.recycle();
}
break;
}
@@ -421,7 +439,7 @@
@Override
public void setActive(String callId) {
logIncoming("setActive %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget();
}
}
@@ -457,7 +475,8 @@
public void setDisconnected(
String callId, int disconnectCause, String disconnectMessage) {
logIncoming("setDisconnected %s %d %s", callId, disconnectCause, disconnectMessage);
- if (mCallIdMapper.isValidCallId(callId)) {
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
+ Log.d(this, "disconnect call %s", callId);
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = disconnectMessage;
@@ -469,7 +488,7 @@
@Override
public void setOnHold(String callId) {
logIncoming("setOnHold %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
mHandler.obtainMessage(MSG_SET_ON_HOLD, callId).sendToTarget();
}
}
@@ -486,8 +505,9 @@
@Override
public void removeCall(String callId) {
logIncoming("removeCall %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
mHandler.obtainMessage(MSG_REMOVE_CALL, callId).sendToTarget();
+ mHandler.obtainMessage(MSG_REMOVE_CALL, callId);
}
}
@@ -504,7 +524,7 @@
public void setIsConferenced(String callId, String conferenceCallId) {
logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
if (mCallIdMapper.isValidCallId(callId) &&
- mCallIdMapper.isValidCallId(conferenceCallId)) {
+ mCallIdMapper.isValidConferenceId(conferenceCallId)) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = conferenceCallId;
@@ -513,11 +533,14 @@
}
@Override
- public void addConferenceCall(String callId) {
- logIncoming("addConferenceCall %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, callId).sendToTarget();
- }
+ public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
+ logIncoming("addConferenceCall %s %s", callId, parcelableConference);
+ // We do not check call Ids here because we do not yet know the call ID for new
+ // conference calls.
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = parcelableConference;
+ mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, args).sendToTarget();
}
@Override
@@ -874,25 +897,13 @@
}
}
- void conference(final Call conferenceCall, Call call) {
- final String conferenceId = mCallIdMapper.getCallId(call);
+ void conference(final Call call, Call otherCall) {
final String callId = mCallIdMapper.getCallId(call);
- if (conferenceId != null && callId != null &&
- isServiceValid("conference")) {
+ final String otherCallId = mCallIdMapper.getCallId(otherCall);
+ if (callId != null && otherCallId != null && isServiceValid("conference")) {
try {
- conferenceCall.setConnectionService(this);
- mPendingConferenceCalls.add(conferenceCall);
- mHandler.postDelayed(new Runnable() {
- @Override public void run() {
- if (mPendingConferenceCalls.remove(conferenceCall)) {
- conferenceCall.expireConference();
- Log.i(this, "Conference call expired: %s", conferenceCall);
- }
- }
- }, Timeouts.getConferenceCallExpireMillis());
-
- logOutgoing("conference %s %s", conferenceId, callId);
- mServiceInterface.conference(conferenceId, callId);
+ logOutgoing("conference %s %s", callId, otherCallId);
+ mServiceInterface.conference(callId, otherCallId);
} catch (RemoteException ignored) {
}
}
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index 47f7f2b..4f022c0 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -16,28 +16,36 @@
package com.android.telecomm;
+import android.Manifest;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.telecomm.AudioState;
+import android.telecomm.CallState;
+import android.telecomm.InCallService;
+import android.telecomm.ParcelableCall;
import android.telecomm.PhoneCapabilities;
import android.telecomm.PropertyPresentation;
-import android.telecomm.CallState;
-import android.telecomm.ParcelableCall;
+import android.util.ArrayMap;
import com.android.internal.telecomm.IInCallService;
import com.google.common.collect.ImmutableCollection;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -52,12 +60,12 @@
private class InCallServiceConnection implements ServiceConnection {
/** {@inheritDoc} */
@Override public void onServiceConnected(ComponentName name, IBinder service) {
- onConnected(service);
+ onConnected(name, service);
}
/** {@inheritDoc} */
@Override public void onServiceDisconnected(ComponentName name) {
- onDisconnected();
+ onDisconnected(name);
}
}
@@ -99,11 +107,13 @@
@Override
public void onStartActivityFromInCall(Call call, PendingIntent intent) {
- if (mInCallService != null) {
+ if (!mInCallServices.isEmpty()) {
Log.i(this, "Calling startActivity, intent: %s", intent);
- try {
- mInCallService.startActivity(mCallIdMapper.getCallId(call), intent);
- } catch (RemoteException ignored) {
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.startActivity(mCallIdMapper.getCallId(call), intent);
+ } catch (RemoteException ignored) {
+ }
}
}
}
@@ -119,29 +129,45 @@
}
};
- /** Maintains a binding connection to the in-call app. */
- private final InCallServiceConnection mConnection = new InCallServiceConnection();
+ /**
+ * Maintains a binding connection to the in-call app(s).
+ * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+ * load factor before resizing, 1 means we only expect a single thread to
+ * access the map so make only a single shard
+ */
+ private final Map<ComponentName, InCallServiceConnection> mServiceConnections =
+ new ConcurrentHashMap<ComponentName, InCallServiceConnection>(8, 0.9f, 1);
- /** The in-call app implementation, see {@link IInCallService}. */
- private IInCallService mInCallService;
+ /** The in-call app implementations, see {@link IInCallService}. */
+ private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>();
private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
- IInCallService getService() {
- return mInCallService;
+ /** The {@link ComponentName} of the default InCall UI */
+ private ComponentName mInCallComponentName;
+
+ public InCallController() {
+ Context context = TelecommApp.getInstance();
+ Resources resources = context.getResources();
+
+ mInCallComponentName = new ComponentName(
+ resources.getString(R.string.ui_default_package),
+ resources.getString(R.string.incall_default_class));
}
@Override
public void onCallAdded(Call call) {
- if (mInCallService == null) {
+ if (mInCallServices.isEmpty()) {
bind();
} else {
Log.i(this, "Adding call: %s", call);
- if (mCallIdMapper.getCallId(call) == null) {
- mCallIdMapper.addCall(call);
- call.addListener(mCallListener);
+ // Track the call if we don't already know about it.
+ addCall(call);
+
+ ParcelableCall parcelableCall = toParcelableCall(call);
+ for (IInCallService inCallService : mInCallServices.values()) {
try {
- mInCallService.addCall(toParcelableCall(call));
+ inCallService.addCall(parcelableCall);
} catch (RemoteException ignored) {
}
}
@@ -173,37 +199,43 @@
@Override
public void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState) {
- if (mInCallService != null) {
+ if (!mInCallServices.isEmpty()) {
Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
newAudioState);
- try {
- mInCallService.onAudioStateChanged(newAudioState);
- } catch (RemoteException ignored) {
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.onAudioStateChanged(newAudioState);
+ } catch (RemoteException ignored) {
+ }
}
}
}
void onPostDialWait(Call call, String remaining) {
- if (mInCallService != null) {
+ if (!mInCallServices.isEmpty()) {
Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
- try {
- mInCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
- } catch (RemoteException ignored) {
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
+ } catch (RemoteException ignored) {
+ }
}
}
}
@Override
public void onIsConferencedChanged(Call call) {
- Log.v(this, "onIsConferencedChanged %s", call);
+ Log.d(this, "onIsConferencedChanged %s", call);
updateCall(call);
}
void bringToForeground(boolean showDialpad) {
- if (mInCallService != null) {
- try {
- mInCallService.bringToForeground(showDialpad);
- } catch (RemoteException ignored) {
+ if (!mInCallServices.isEmpty()) {
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.bringToForeground(showDialpad);
+ } catch (RemoteException ignored) {
+ }
}
} else {
Log.w(this, "Asking to bring unbound in-call UI to foreground.");
@@ -215,10 +247,12 @@
*/
private void unbind() {
ThreadUtil.checkOnMainThread();
- if (mInCallService != null) {
+ if (!mInCallServices.isEmpty()) {
Log.i(this, "Unbinding from InCallService");
- TelecommApp.getInstance().unbindService(mConnection);
- mInCallService = null;
+ for (InCallServiceConnection connection : mServiceConnections.values()) {
+ TelecommApp.getInstance().unbindService(connection);
+ }
+ mInCallServices.clear();
}
}
@@ -228,22 +262,46 @@
*/
private void bind() {
ThreadUtil.checkOnMainThread();
- if (mInCallService == null) {
+ if (mInCallServices.isEmpty()) {
+ mServiceConnections.clear();
Context context = TelecommApp.getInstance();
- Resources resources = context.getResources();
- ComponentName component = new ComponentName(
- resources.getString(R.string.ui_default_package),
- resources.getString(R.string.incall_default_class));
- Log.i(this, "Attempting to bind to InCallService: %s", component);
+ PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
+ Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
- Intent serviceIntent = new Intent(IInCallService.class.getName());
- serviceIntent.setComponent(component);
+ for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
+ ServiceInfo serviceInfo = entry.serviceInfo;
+ if (serviceInfo != null) {
+ boolean hasServiceBindPermission = serviceInfo.permission != null &&
+ serviceInfo.permission.equals(
+ Manifest.permission.BIND_INCALL_SERVICE);
+ boolean hasControlInCallPermission = packageManager.checkPermission(
+ Manifest.permission.CONTROL_INCALL_EXPERIENCE,
+ serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
- if (!context.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
- UserHandle.CURRENT)) {
- Log.w(this, "Could not connect to the in-call app (%s)", component);
+ if (!hasServiceBindPermission) {
+ Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission: " +
+ serviceInfo.packageName);
+ continue;
+ }
- // TODO: Implement retry or fall-back-to-default logic.
+ if (!hasControlInCallPermission) {
+ Log.w(this,
+ "InCall UI does not have CONTROL_INCALL_EXPERIENCE permission: " +
+ serviceInfo.packageName);
+ continue;
+ }
+
+ Log.i(this, "Attempting to bind to InCall " + serviceInfo.packageName);
+ InCallServiceConnection inCallServiceConnection = new InCallServiceConnection();
+ ComponentName componentName = new ComponentName(serviceInfo.packageName,
+ serviceInfo.name);
+ intent.setComponent(componentName);
+
+ if (context.bindServiceAsUser(intent, inCallServiceConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
+ mServiceConnections.put(componentName, inCallServiceConnection);
+ }
+ }
}
}
}
@@ -253,26 +311,34 @@
* this class and in-call app by sending the first update to in-call app. This method is
* called after a successful binding connection is established.
*
+ * @param componentName The service {@link ComponentName}.
* @param service The {@link IInCallService} implementation.
*/
- private void onConnected(IBinder service) {
+ private void onConnected(ComponentName componentName, IBinder service) {
ThreadUtil.checkOnMainThread();
- mInCallService = IInCallService.Stub.asInterface(service);
+
+ IInCallService inCallService = IInCallService.Stub.asInterface(service);
try {
- mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
+ inCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
mCallIdMapper));
+ mInCallServices.put(componentName, inCallService);
} catch (RemoteException e) {
Log.e(this, e, "Failed to set the in-call adapter.");
- mInCallService = null;
return;
}
- // Upon successful connection, send the state of the world to the in-call app.
+ // Upon successful connection, send the state of the world to the service.
ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
if (!calls.isEmpty()) {
for (Call call : calls) {
- onCallAdded(call);
+ try {
+ // Track the call if we don't already know about it.
+ addCall(call);
+
+ inCallService.addCall(toParcelableCall(call));
+ } catch (RemoteException ignored) {
+ }
}
onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
} else {
@@ -281,20 +347,50 @@
}
/**
- * Cleans up the instance of in-call app after the service has been unbound.
+ * Cleans up an instance of in-call app after the service has been unbound.
+ *
+ * @param disconnectedComponent The {@link ComponentName} of the service which disconnected.
*/
- private void onDisconnected() {
+ private void onDisconnected(ComponentName disconnectedComponent) {
ThreadUtil.checkOnMainThread();
- mInCallService = null;
+ if (mInCallServices.containsKey(disconnectedComponent)) {
+ mInCallServices.remove(disconnectedComponent);
+ }
+
+ // If the default in-call UI has disconnected, disconnect all calls and un-bind all other
+ // InCallService implementations.
+ if (disconnectedComponent.equals(mInCallComponentName)) {
+ Log.i(this, "In-call UI %s disconnected.", disconnectedComponent);
+ CallsManager.getInstance().disconnectAllCalls();
+
+ // Iterate through the in-call services, removing them as they are un-bound.
+ Iterator<Map.Entry<ComponentName, IInCallService>> it =
+ mInCallServices.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<ComponentName, IInCallService> entry = it.next();
+ ComponentName componentName = entry.getKey();
+
+ InCallServiceConnection connection = mServiceConnections.remove(componentName);
+ it.remove();
+ if (connection == null) {
+ continue;
+ }
+
+ Log.i(this, "Unbinding other InCallService %s", componentName);
+ TelecommApp.getInstance().unbindService(connection);
+ }
+ }
}
private void updateCall(Call call) {
- if (mInCallService != null) {
- try {
- ParcelableCall parcelableCall = toParcelableCall(call);
- Log.v(this, "updateCall %s ==> %s", call, parcelableCall);
- mInCallService.updateCall(parcelableCall);
- } catch (RemoteException ignored) {
+ if (!mInCallServices.isEmpty()) {
+ ParcelableCall parcelableCall = toParcelableCall(call);
+ Log.v(this, "updateCall %s ==> %s", call, parcelableCall);
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.updateCall(parcelableCall);
+ } catch (RemoteException ignored) {
+ }
}
}
}
@@ -372,4 +468,15 @@
conferenceableCallIds,
call.getExtras());
}
+
+ /**
+ * Adds the call to the list of calls tracked by the {@link InCallController}.
+ * @param call The call to add.
+ */
+ private void addCall(Call call) {
+ if (mCallIdMapper.getCallId(call) == null) {
+ mCallIdMapper.addCall(call);
+ call.addListener(mCallListener);
+ }
+ }
}
diff --git a/src/com/android/telecomm/Timeouts.java b/src/com/android/telecomm/Timeouts.java
index 3ae3f1d..3ff0575 100644
--- a/src/com/android/telecomm/Timeouts.java
+++ b/src/com/android/telecomm/Timeouts.java
@@ -51,11 +51,4 @@
public static long getDirectToVoicemailMillis() {
return get("direct_to_voicemail_ms", 500L);
}
-
- /**
- * Returns the amount of time that a connection service has to respond to a "conference" action.
- */
- public static long getConferenceCallExpireMillis() {
- return get("conference_call_expire_ms", 15 * 1000L);
- }
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index bbc3292..c17803f 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission
android:name="com.android.telecomm.permission.REGISTER_PROVIDER_OR_SUBSCRIPTION" />
+ <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
<application android:label="@string/app_name">
<uses-library android:name="android.test.runner" />
@@ -40,6 +41,14 @@
</intent-filter>
</service>
+ <service android:name="com.android.telecomm.testapps.TestInCallServiceImpl"
+ android:process="com.android.telecomm.testapps.TestInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE" >
+ <intent-filter>
+ <action android:name="android.telecomm.InCallService"/>
+ </intent-filter>
+ </service>
+
<activity android:name="com.android.telecomm.testapps.TestCallActivity"
android:label="@string/testCallActivityLabel">
<intent-filter>
@@ -58,7 +67,8 @@
</receiver>
<activity android:name="com.android.telecomm.testapps.TestDialerActivity"
- android:label="@string/testDialerActivityLabel" >
+ android:label="@string/testDialerActivityLabel"
+ android:process="com.android.telecomm.testapps.TestInCallService">
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<category android:name="android.intent.category.DEFAULT" />
@@ -89,8 +99,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
-
-
</application>
<!--
diff --git a/tests/src/com/android/telecomm/testapps/TestInCallServiceImpl.java b/tests/src/com/android/telecomm/testapps/TestInCallServiceImpl.java
new file mode 100644
index 0000000..e48d5fb
--- /dev/null
+++ b/tests/src/com/android/telecomm/testapps/TestInCallServiceImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.telecomm.testapps;
+
+import android.telecomm.InCallService;
+import android.telecomm.Phone;
+import android.util.Log;
+
+import java.lang.Override;
+import java.lang.String;
+
+/**
+ * Test In-Call service implementation. Logs incoming events. Mainly used to test binding to
+ * multiple {@link InCallService} implementations.
+ */
+public class TestInCallServiceImpl extends InCallService {
+ private static final String TAG = "TestInCallServiceImpl";
+
+ private Phone mPhone;
+
+ private Phone.Listener mPhoneListener = new Phone.Listener() {
+ @Override
+ public void onCallAdded(Phone phone, android.telecomm.Call call) {
+ Log.i(TAG, "onCallAdded: "+call.toString());
+ }
+ @Override
+ public void onCallRemoved(Phone phone, android.telecomm.Call call) {
+ Log.i(TAG, "onCallRemoved: "+call.toString());
+ }
+ };
+
+ @Override
+ public void onPhoneCreated(Phone phone) {
+ Log.i(TAG, "onPhoneCreated");
+ mPhone = phone;
+ mPhone.addListener(mPhoneListener);
+
+ }
+
+ @Override
+ public void onPhoneDestroyed(Phone phone) {
+ Log.i(TAG, "onPhoneDestroyed");
+ mPhone.removeListener(mPhoneListener);
+ mPhone = null;
+ }
+}