Merge "Fix for call switching." into lmp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1a4fae7..43a1f5e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -33,6 +33,9 @@
<uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
<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. -->
<permission
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 6221c1d..cda9091 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -36,9 +36,9 @@
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"အမြန်တုံ့ပြန်ချက်"</string>
<string name="respond_via_sms_menu_reset_default_activity" msgid="1461742052902053466">"ပုံသေ အပ်ပလီကေးရှင်းအား ပြန်ပြောင်းရန်"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ထံ စာတိုပို့လိုက်ပါပြီ"</string>
- <string name="phone_account_preferences_title" msgid="3932310998135189661">"ဖုန်း အကောင့် ဦးစားပေးချက်များ"</string>
- <string name="default_outgoing_account_title" msgid="8261079649574578970">"ပုံသေ အထွက် အကောင့်"</string>
- <string name="sim_call_manager_account" msgid="2559930293628077755">"ကြိုးမဲ့ ခေါ်ဆိုမှု အကောင့်"</string>
+ <string name="phone_account_preferences_title" msgid="3932310998135189661">"ဖုန်း အကောင့် ဦးစားပေးချက်များ"</string>
+ <string name="default_outgoing_account_title" msgid="8261079649574578970">"ပုံသေ အထွက် အကောင့်"</string>
+ <string name="sim_call_manager_account" msgid="2559930293628077755">"ကြိုးမဲ့ ခေါ်ဆိုမှု အကောင့်"</string>
<string name="account_ask_every_time" msgid="944077828070287407">"အကြိမ်တိုင်းမှာ မေးရန်"</string>
- <string name="do_not_use_sim_call_manager" msgid="5519252524007323694">"ကြိုးမဲ့ ခေါ်ဆိုမှုကို မသုံးပါနှင့်"</string>
+ <string name="do_not_use_sim_call_manager" msgid="5519252524007323694">"ကြိုးမဲ့ ခေါ်ဆိုမှုကို မသုံးပါနှင့်"</string>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..e93e855
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ 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
+ -->
+
+<resources>
+ <color name="theme_color">#0288d1</color>
+</resources>
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 6d3da71..7718df6 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -26,7 +26,6 @@
import android.telecomm.PhoneCapabilities;
import android.telecomm.PropertyPresentation;
import android.telecomm.CallState;
-import android.telecomm.ConnectionRequest;
import android.telecomm.GatewayInfo;
import android.telecomm.ParcelableConnection;
import android.telecomm.PhoneAccount;
@@ -53,7 +52,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Encapsulates all aspects of a given phone call throughout its lifecycle, starting
@@ -67,14 +66,11 @@
interface Listener {
void onSuccessfulOutgoingCall(Call call, int callState);
void onFailedOutgoingCall(Call call, int errorCode, String errorMsg);
- void onCancelledOutgoingCall(Call call);
void onSuccessfulIncomingCall(Call call);
void onFailedIncomingCall(Call call);
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);
@@ -98,8 +94,6 @@
@Override
public void onFailedOutgoingCall(Call call, int errorCode, String errorMsg) {}
@Override
- public void onCancelledOutgoingCall(Call call) {}
- @Override
public void onSuccessfulIncomingCall(Call call) {}
@Override
public void onFailedIncomingCall(Call call) {}
@@ -110,10 +104,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) {}
@@ -248,8 +238,14 @@
/** Info used by the connection services. */
private Bundle mExtras = Bundle.EMPTY;
- /** Set of listeners on this call. */
- private Set<Listener> mListeners = new CopyOnWriteArraySet<>();
+ /** Set of listeners on this call.
+ *
+ * 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 Set<Listener> mListeners = Collections.newSetFromMap(
+ new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
private CreateConnectionProcessor mCreateConnectionProcessor;
@@ -322,7 +318,9 @@
}
void removeListener(Listener listener) {
- mListeners.remove(listener);
+ if (listener != null) {
+ mListeners.remove(listener);
+ }
}
/** {@inheritDoc} */
@@ -338,15 +336,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;
}
/**
@@ -618,15 +608,14 @@
}
@Override
- public void handleCreateConnectionSuccessful(
- ConnectionRequest request, ParcelableConnection connection) {
+ public void handleCreateConnectionSuccess(ParcelableConnection connection) {
Log.v(this, "handleCreateConnectionSuccessful %s", connection);
mCreateConnectionProcessor = null;
setTargetPhoneAccount(connection.getPhoneAccount());
setHandle(connection.getHandle(), connection.getHandlePresentation());
setCallerDisplayName(
connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
-
+ setCallCapabilities(connection.getCapabilities());
setVideoProvider(connection.getVideoProvider());
setVideoState(connection.getVideoState());
setRequestingRingback(connection.isRequestingRingback());
@@ -652,43 +641,20 @@
}
@Override
- public void handleCreateConnectionFailed(int code, String msg) {
+ public void handleCreateConnectionFailure(int code, String msg) {
mCreateConnectionProcessor = null;
- if (mIsIncoming) {
- clearConnectionService();
- setDisconnectCause(code, null);
- setState(CallState.DISCONNECTED);
+ clearConnectionService();
+ setDisconnectCause(code, msg);
+ CallsManager.getInstance().markCallAsDisconnected(this, code, msg);
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onFailedIncomingCall(this);
+ if (mIsIncoming) {
+ for (Listener listener : mListeners) {
+ listener.onFailedIncomingCall(this);
}
} else {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onFailedOutgoingCall(this, code, msg);
+ for (Listener listener : mListeners) {
+ listener.onFailedOutgoingCall(this, code, msg);
}
- clearConnectionService();
- }
- }
-
- @Override
- public void handleCreateConnectionCancelled() {
- mCreateConnectionProcessor = null;
- if (mIsIncoming) {
- clearConnectionService();
- setDisconnectCause(DisconnectCause.OUTGOING_CANCELED, null);
-
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onFailedIncomingCall(this);
- }
- } else {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onCancelledOutgoingCall(this);
- }
- clearConnectionService();
}
}
@@ -742,8 +708,11 @@
void abort() {
if (mCreateConnectionProcessor != null) {
mCreateConnectionProcessor.abort();
- } else if (mState == CallState.PRE_DIAL_WAIT) {
- handleCreateConnectionFailed(DisconnectCause.LOCAL, null);
+ } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT
+ || mState == CallState.CONNECTING) {
+ handleCreateConnectionFailure(DisconnectCause.OUTGOING_CANCELED, null);
+ } else {
+ Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING");
}
}
@@ -847,27 +816,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);
}
}
@@ -880,6 +833,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;
@@ -1206,8 +1163,10 @@
}
}
- private int getStateFromConnectionState(int state) {
+ static int getStateFromConnectionState(int state) {
switch (state) {
+ case Connection.STATE_INITIALIZING:
+ return CallState.CONNECTING;
case Connection.STATE_ACTIVE:
return CallState.ACTIVE;
case Connection.STATE_DIALING:
diff --git a/src/com/android/telecomm/CallActivity.java b/src/com/android/telecomm/CallActivity.java
index 06cc9cb..89b89e6 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}.
@@ -116,10 +117,22 @@
* @param intent Call intent containing data about the handle to call.
*/
private void processOutgoingCallIntent(Intent intent) {
-
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/CallLogManager.java b/src/com/android/telecomm/CallLogManager.java
index d0668b9..ee65255 100644
--- a/src/com/android/telecomm/CallLogManager.java
+++ b/src/com/android/telecomm/CallLogManager.java
@@ -23,6 +23,7 @@
import android.telecomm.CallState;
import android.telecomm.PhoneAccountHandle;
import android.telecomm.VideoProfile;
+import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
import com.android.internal.telephony.CallerInfo;
@@ -88,8 +89,9 @@
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
- if ((newState == CallState.DISCONNECTED || newState == CallState.ABORTED) &&
- oldState != CallState.PRE_DIAL_WAIT) {
+ if ((newState == CallState.DISCONNECTED || newState == CallState.ABORTED)
+ && oldState != CallState.PRE_DIAL_WAIT
+ && call.getDisconnectCause() != DisconnectCause.OUTGOING_CANCELED) {
int type;
if (!call.isIncoming()) {
type = Calls.OUTGOING_TYPE;
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 7ab48db..4e3a0cb 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -21,16 +21,17 @@
import android.telecomm.AudioState;
import android.telecomm.CallState;
import android.telecomm.GatewayInfo;
+import android.telecomm.ParcelableConference;
import android.telecomm.PhoneAccountHandle;
import android.telephony.DisconnectCause;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Singleton.
@@ -65,8 +66,13 @@
/**
* The main call repository. Keeps an instance of all live calls. New incoming and outgoing
* calls are added to the map and removed when the calls move to the disconnected state.
+ *
+ * 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 Set<Call> mCalls = new CopyOnWriteArraySet<Call>();
+ private final Set<Call> mCalls = Collections.newSetFromMap(
+ new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
private final ConnectionServiceRepository mConnectionServiceRepository =
new ConnectionServiceRepository();
@@ -74,7 +80,10 @@
private final InCallController mInCallController = new InCallController();
private final CallAudioManager mCallAudioManager;
private final Ringer mRinger;
- private final Set<CallsManagerListener> mListeners = new HashSet<>();
+ // For this set initial table size to 16 because we add 13 listeners in
+ // the CallsManager constructor.
+ private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap(
+ new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
private final HeadsetMediaButton mHeadsetMediaButton;
private final WiredHeadsetManager mWiredHeadsetManager;
private final TtyManager mTtyManager;
@@ -149,13 +158,6 @@
}
@Override
- public void onCancelledOutgoingCall(Call call) {
- Log.v(this, "onCancelledOutgoingCall, call: %s", call);
- setCallState(call, CallState.ABORTED);
- removeCall(call);
- }
-
- @Override
public void onSuccessfulIncomingCall(Call call) {
Log.d(this, "onSuccessfulIncomingCall");
setCallState(call, CallState.RINGING);
@@ -181,20 +183,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);
@@ -262,8 +250,8 @@
/**
* Starts the process to attach the call to a connection service.
*
- * @param phoneAccountHandle The phone account which contains the component name of the connection
- * service to use for this call.
+ * @param phoneAccountHandle The phone account which contains the component name of the
+ * connection service to use for this call.
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
*/
void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
@@ -285,7 +273,6 @@
call.startCreateConnection();
}
-
/**
* Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
*
@@ -293,10 +280,9 @@
* placeOutgoingCall directly.
*
* @param handle Handle to connect the call with.
- * @param phoneAccountHandle The phone account which contains the component name of the connection
- * service to use for this call.
- * @param extras The optional extras Bundle passed with the intent used for the outgoing call.
- *
+ * @param phoneAccountHandle The phone account which contains the component name of the
+ * connection service to use for this call.
+ * @param extras The optional extras Bundle passed with the intent used for the incoming call.
*/
Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
// We only allow a single outgoing call at any given time. Before placing a call, make sure
@@ -308,6 +294,27 @@
return null;
}
+ TelecommApp app = TelecommApp.getInstance();
+
+ // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
+ // as if a phoneAccount was not specified (does the default behavior instead).
+ if (phoneAccountHandle != null) {
+ List<PhoneAccountHandle> enabledAccounts =
+ app.getPhoneAccountRegistrar().getOutgoingPhoneAccounts();
+ if (!enabledAccounts.contains(phoneAccountHandle)) {
+ phoneAccountHandle = null;
+ }
+ }
+
+ if (phoneAccountHandle == null) {
+ // No preset account, check if default exists
+ PhoneAccountHandle defaultAccountHandle =
+ app.getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount();
+ if (defaultAccountHandle != null) {
+ phoneAccountHandle = defaultAccountHandle;
+ }
+ }
+
// Create a call with original handle. The handle may be changed when the call is attached
// to a connection service, but in most cases will remain the same.
call = new Call(
@@ -319,7 +326,14 @@
false /* isIncoming */,
false /* isConference */);
call.setExtras(extras);
- call.setState(CallState.CONNECTING);
+
+ final boolean emergencyCall = TelephonyUtil.shouldProcessAsEmergency(app, call.getHandle());
+ if (phoneAccountHandle == null && !emergencyCall) {
+ // This is the state where the user is expected to select an account
+ call.setState(CallState.PRE_DIAL_WAIT);
+ } else {
+ call.setState(CallState.CONNECTING);
+ }
if (!isPotentialMMICode(handle)) {
addCall(call);
@@ -339,15 +353,14 @@
* @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
* @param videoState The desired video state for the outgoing call.
*/
- void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
- PhoneAccountHandle accountHandle, boolean speakerphoneOn, int videoState) {
+ void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
+ int videoState) {
if (call == null) {
// don't do anything if the call no longer exists
Log.i(this, "Canceling unknown call.");
return;
}
- TelecommApp app = TelecommApp.getInstance();
final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayHandle();
if (gatewayInfo == null) {
@@ -357,49 +370,22 @@
Log.pii(uriHandle), Log.pii(handle));
}
- //TODO: phone account is already set in {@link #startOutgoingCall}, refactor so this is
- // not redundant.
- //
- // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
- // as if a phoneAccount was not specified (does the default behavior instead).
- if (accountHandle != null) {
- List<PhoneAccountHandle> enabledAccounts =
- app.getPhoneAccountRegistrar().getOutgoingPhoneAccounts();
- if (!enabledAccounts.contains(accountHandle)) {
- accountHandle = null;
- }
- }
-
call.setHandle(uriHandle);
call.setGatewayInfo(gatewayInfo);
call.setStartWithSpeakerphoneOn(speakerphoneOn);
call.setVideoState(videoState);
- // This block of code will attempt to pre-determine a phone account
+ TelecommApp app = TelecommApp.getInstance();
final boolean emergencyCall = TelephonyUtil.shouldProcessAsEmergency(app, call.getHandle());
if (emergencyCall) {
// Emergency -- CreateConnectionProcessor will choose accounts automatically
call.setTargetPhoneAccount(null);
- } else if (accountHandle != null) {
- // NOTE: this is currently not an option because no feature currently exists to
- // preset a phone account
- Log.d(this, "CALL with phone account: " + accountHandle);
- call.setTargetPhoneAccount(accountHandle);
- } else {
- // No preset account, check if default exists
- PhoneAccountHandle defaultAccountHandle =
- app.getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount();
- if (defaultAccountHandle != null) {
- call.setTargetPhoneAccount(defaultAccountHandle);
- }
}
if (call.getTargetPhoneAccount() != null || emergencyCall) {
- // If the account is selected, proceed to place the outgoing call
+ // If the account has been set, proceed to place the outgoing call.
+ // Otherwise the connection will be initiated when the account is set by the user.
call.startCreateConnection();
- } else {
- // This is the state where the user is expected to select an account
- call.setState(CallState.PRE_DIAL_WAIT);
}
}
@@ -410,16 +396,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);
}
/**
@@ -526,6 +503,18 @@
}
/**
+ * 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.
@@ -647,6 +636,13 @@
}
/**
+ * Removes an existing disconnected call, and notifies the in-call app.
+ */
+ void markCallAsRemoved(Call call) {
+ removeCall(call);
+ }
+
+ /**
* Cleans up any calls currently associated with the specified connection service when the
* service binder disconnects unexpectedly.
*
@@ -654,7 +650,7 @@
*/
void handleConnectionServiceDeath(ConnectionServiceWrapper service) {
if (service != null) {
- for (Call call : ImmutableList.copyOf(mCalls)) {
+ for (Call call : mCalls) {
if (call.getConnectionService() == service) {
markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
}
@@ -742,6 +738,28 @@
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/ConnectionServiceRepository.java b/src/com/android/telecomm/ConnectionServiceRepository.java
index ec2c90e..efd1ba4 100644
--- a/src/com/android/telecomm/ConnectionServiceRepository.java
+++ b/src/com/android/telecomm/ConnectionServiceRepository.java
@@ -38,21 +38,6 @@
ConnectionServiceRepository() {
}
- Collection<ConnectionServiceWrapper> lookupServices() {
- PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
- Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
- ArrayList<ConnectionServiceWrapper> services = new ArrayList<>();
-
- for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
- ServiceInfo serviceInfo = entry.serviceInfo;
- if (serviceInfo != null) {
- services.add(getService(new ComponentName(
- serviceInfo.packageName, serviceInfo.name)));
- }
- }
- return services;
- }
-
ConnectionServiceWrapper getService(ComponentName componentName) {
ConnectionServiceWrapper service = mServiceCache.get(componentName);
if (service == null) {
diff --git a/src/com/android/telecomm/ConnectionServiceWrapper.java b/src/com/android/telecomm/ConnectionServiceWrapper.java
index 2d5ade2..264ee63 100644
--- a/src/com/android/telecomm/ConnectionServiceWrapper.java
+++ b/src/com/android/telecomm/ConnectionServiceWrapper.java
@@ -25,9 +25,12 @@
import android.os.Message;
import android.os.RemoteException;
import android.telecomm.AudioState;
+import android.telecomm.Connection;
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;
@@ -44,10 +47,10 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
@@ -56,77 +59,40 @@
* {@link IConnectionService}.
*/
final class ConnectionServiceWrapper extends ServiceBinder<IConnectionService> {
- private static final int MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL = 1;
- private static final int MSG_HANDLE_CREATE_CONNECTION_FAILED = 2;
- private static final int MSG_HANDLE_CREATE_CONNECTION_CANCELLED = 3;
- private static final int MSG_SET_ACTIVE = 4;
- private static final int MSG_SET_RINGING = 5;
- private static final int MSG_SET_DIALING = 6;
- private static final int MSG_SET_DISCONNECTED = 7;
- private static final int MSG_SET_ON_HOLD = 8;
- private static final int MSG_SET_REQUESTING_RINGBACK = 9;
- private static final int MSG_SET_CALL_CAPABILITIES = 10;
- private static final int MSG_SET_IS_CONFERENCED = 11;
- private static final int MSG_ADD_CONFERENCE_CALL = 12;
- private static final int MSG_REMOVE_CALL = 13;
- private static final int MSG_ON_POST_DIAL_WAIT = 14;
- private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 15;
- private static final int MSG_SET_VIDEO_PROVIDER = 16;
- private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 17;
- private static final int MSG_SET_STATUS_HINTS = 18;
- private static final int MSG_SET_HANDLE = 19;
- private static final int MSG_SET_CALLER_DISPLAY_NAME = 20;
- private static final int MSG_SET_VIDEO_STATE = 21;
- private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 22;
- private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 23;
+ private static final int MSG_HANDLE_CREATE_CONNECTION_COMPLETE = 1;
+ private static final int MSG_SET_ACTIVE = 2;
+ private static final int MSG_SET_RINGING = 3;
+ private static final int MSG_SET_DIALING = 4;
+ private static final int MSG_SET_DISCONNECTED = 5;
+ private static final int MSG_SET_ON_HOLD = 6;
+ private static final int MSG_SET_REQUESTING_RINGBACK = 7;
+ private static final int MSG_SET_CALL_CAPABILITIES = 8;
+ private static final int MSG_SET_IS_CONFERENCED = 9;
+ private static final int MSG_ADD_CONFERENCE_CALL = 10;
+ private static final int MSG_REMOVE_CALL = 11;
+ private static final int MSG_ON_POST_DIAL_WAIT = 12;
+ private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 13;
+ private static final int MSG_SET_VIDEO_PROVIDER = 14;
+ private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 15;
+ private static final int MSG_SET_STATUS_HINTS = 16;
+ private static final int MSG_SET_HANDLE = 17;
+ private static final int MSG_SET_CALLER_DISPLAY_NAME = 18;
+ private static final int MSG_SET_VIDEO_STATE = 19;
+ private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20;
+ private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 21;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Call call;
switch (msg.what) {
- case MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL: {
+ case MSG_HANDLE_CREATE_CONNECTION_COMPLETE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
ConnectionRequest request = (ConnectionRequest) args.arg2;
- if (mPendingResponses.containsKey(callId)) {
- ParcelableConnection connection = (ParcelableConnection) args.arg3;
- mPendingResponses.remove(callId).
- handleCreateConnectionSuccessful(request, connection);
- }
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_HANDLE_CREATE_CONNECTION_FAILED: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- String callId = (String) args.arg1;
- ConnectionRequest request = (ConnectionRequest) args.arg2;
- int statusCode = args.argi1;
- String statusMsg = (String) args.arg3;
- removeCall(
- mCallIdMapper.getCall(callId),
- statusCode,
- statusMsg);
- } finally {
- args.recycle();
- }
- break;
- }
- case MSG_HANDLE_CREATE_CONNECTION_CANCELLED: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- String callId = (String) args.arg1;
- ConnectionRequest request = (ConnectionRequest) args.arg2;
- if (mPendingResponses.containsKey(callId)) {
- mPendingResponses.remove(callId)
- .handleCreateConnectionCancelled();
- } else {
- //Log.w(this, "handleCreateConnectionCancelled, unknown call: %s", callId);
- }
+ ParcelableConnection connection = (ParcelableConnection) args.arg3;
+ handleCreateConnectionComplete(callId, request, connection);
} finally {
args.recycle();
}
@@ -162,6 +128,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);
@@ -207,16 +174,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);
@@ -227,17 +189,47 @@
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;
}
- case MSG_REMOVE_CALL:
+ case MSG_REMOVE_CALL: {
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ if (call.isActive()) {
+ mCallsManager.markCallAsDisconnected(
+ call, DisconnectCause.NORMAL, null);
+ } else {
+ mCallsManager.markCallAsRemoved(call);
+ }
+ }
break;
+ }
case MSG_ON_POST_DIAL_WAIT: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -362,55 +354,25 @@
private final class Adapter extends IConnectionServiceAdapter.Stub {
@Override
- public void handleCreateConnectionSuccessful(
+ public void handleCreateConnectionComplete(
String callId,
ConnectionRequest request,
ParcelableConnection connection) {
- logIncoming("handleCreateConnectionSuccessful %s", request);
+ logIncoming("handleCreateConnectionComplete %s", request);
if (mCallIdMapper.isValidCallId(callId)) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = request;
args.arg3 = connection;
- mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL, args)
+ mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_COMPLETE, args)
.sendToTarget();
}
}
@Override
- public void handleCreateConnectionFailed(
- String callId,
- ConnectionRequest request,
- int errorCode,
- String errorMsg) {
- logIncoming("handleCreateConnectionFailed %s %d %s", request, errorCode, errorMsg);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = request;
- args.argi1 = errorCode;
- args.arg3 = errorMsg;
- mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_FAILED, args).sendToTarget();
- }
- }
-
- @Override
- public void handleCreateConnectionCancelled(
- String callId,
- ConnectionRequest request) {
- logIncoming("handleCreateConnectionCancelled %s", request);
- if (mCallIdMapper.isValidCallId(callId)) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callId;
- args.arg2 = request;
- mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_CANCELLED, args).sendToTarget();
- }
- }
-
- @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();
}
}
@@ -446,7 +408,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;
@@ -458,7 +421,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();
}
}
@@ -475,6 +438,10 @@
@Override
public void removeCall(String callId) {
logIncoming("removeCall %s", callId);
+ if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
+ mHandler.obtainMessage(MSG_REMOVE_CALL, callId).sendToTarget();
+ mHandler.obtainMessage(MSG_REMOVE_CALL, callId);
+ }
}
@Override
@@ -490,7 +457,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;
@@ -499,11 +466,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
@@ -602,7 +572,13 @@
private final Adapter mAdapter = new Adapter();
private final CallsManager mCallsManager = CallsManager.getInstance();
- private final Set<Call> mPendingConferenceCalls = new HashSet<>();
+ /**
+ * 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 Set<Call> mPendingConferenceCalls = Collections.newSetFromMap(
+ new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService");
private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
@@ -677,7 +653,7 @@
call.isIncoming());
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
- mPendingResponses.remove(callId).handleCreateConnectionFailed(
+ mPendingResponses.remove(callId).handleCreateConnectionFailure(
DisconnectCause.OUTGOING_FAILURE, e.toString());
}
}
@@ -685,7 +661,7 @@
@Override
public void onFailure() {
Log.e(this, new Exception(), "Failure to call %s", getComponentName());
- response.handleCreateConnectionFailed(DisconnectCause.OUTGOING_FAILURE, null);
+ response.handleCreateConnectionFailure(DisconnectCause.OUTGOING_FAILURE, null);
}
};
@@ -823,10 +799,19 @@
removeCall(call, DisconnectCause.ERROR_UNSPECIFIED, null);
}
+ void removeCall(String callId, int disconnectCause, String disconnectMessage) {
+ CreateConnectionResponse response = mPendingResponses.remove(callId);
+ if (response != null) {
+ response.handleCreateConnectionFailure(disconnectCause, disconnectMessage);
+ }
+
+ mCallIdMapper.removeCall(callId);
+ }
+
void removeCall(Call call, int disconnectCause, String disconnectMessage) {
CreateConnectionResponse response = mPendingResponses.remove(mCallIdMapper.getCallId(call));
if (response != null) {
- response.handleCreateConnectionFailed(disconnectCause, disconnectMessage);
+ response.handleCreateConnectionFailure(disconnectCause, disconnectMessage);
}
mCallIdMapper.removeCall(call);
@@ -854,25 +839,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) {
}
}
@@ -906,6 +879,25 @@
}
}
+ private void handleCreateConnectionComplete(
+ String callId,
+ ConnectionRequest request,
+ ParcelableConnection connection) {
+ // TODO: Note we are not using parameter "request", which is a side effect of our tacit
+ // assumption that we have at most one outgoing connection attempt per ConnectionService.
+ // This may not continue to be the case.
+ if (connection.getState() == Connection.STATE_DISCONNECTED) {
+ // A connection that begins in the DISCONNECTED state is an indication of
+ // failure to connect; we handle all failures uniformly
+ removeCall(callId, connection.getDisconnectCause(), connection.getDisconnectMessage());
+ } else {
+ // Successful connection
+ if (mPendingResponses.containsKey(callId)) {
+ mPendingResponses.remove(callId).handleCreateConnectionSuccess(connection);
+ }
+ }
+ }
+
/**
* Called when the associated connection service dies.
*/
@@ -915,7 +907,7 @@
new CreateConnectionResponse[mPendingResponses.values().size()]);
mPendingResponses.clear();
for (int i = 0; i < responses.length; i++) {
- responses[i].handleCreateConnectionFailed(DisconnectCause.ERROR_UNSPECIFIED, null);
+ responses[i].handleCreateConnectionFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
}
}
mCallIdMapper.clear();
@@ -943,7 +935,8 @@
}
// Make a list of ConnectionServices that are listed as being associated with SIM accounts
- final Set<ConnectionServiceWrapper> simServices = new HashSet<>();
+ final Set<ConnectionServiceWrapper> simServices = Collections.newSetFromMap(
+ new ConcurrentHashMap<ConnectionServiceWrapper, Boolean>(8, 0.9f, 1));
for (PhoneAccountHandle handle : registrar.getOutgoingPhoneAccounts()) {
PhoneAccount account = registrar.getPhoneAccount(handle);
if ((account.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
diff --git a/src/com/android/telecomm/CreateConnectionProcessor.java b/src/com/android/telecomm/CreateConnectionProcessor.java
index cc7808e..aacb80b 100644
--- a/src/com/android/telecomm/CreateConnectionProcessor.java
+++ b/src/com/android/telecomm/CreateConnectionProcessor.java
@@ -16,7 +16,6 @@
package com.android.telecomm;
-import android.telecomm.ConnectionRequest;
import android.telecomm.ParcelableConnection;
import android.telecomm.PhoneAccount;
import android.telecomm.PhoneAccountHandle;
@@ -102,15 +101,38 @@
mCall.clearConnectionService();
}
if (response != null) {
- response.handleCreateConnectionCancelled();
+ response.handleCreateConnectionFailure(DisconnectCause.OUTGOING_CANCELED, null);
}
}
private void attemptNextPhoneAccount() {
Log.v(this, "attemptNextPhoneAccount");
+ PhoneAccountRegistrar registrar = TelecommApp.getInstance().getPhoneAccountRegistrar();
+ CallAttemptRecord attempt = null;
+ if (mAttemptRecordIterator.hasNext()) {
+ attempt = mAttemptRecordIterator.next();
- if (mResponse != null && mAttemptRecordIterator.hasNext()) {
- CallAttemptRecord attempt = mAttemptRecordIterator.next();
+ if (!registrar.phoneAccountHasPermission(attempt.connectionManagerPhoneAccount)) {
+ Log.w(this,
+ "Connection mgr does not have BIND_CONNECTION_SERVICE for attempt: %s",
+ attempt);
+ attemptNextPhoneAccount();
+ return;
+ }
+
+ // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it
+ // also has BIND_CONNECTION_SERVICE permission.
+ if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) &&
+ !registrar.phoneAccountHasPermission(attempt.targetPhoneAccount)) {
+ Log.w(this,
+ "Target PhoneAccount does not have BIND_CONNECTION_SERVICE for attempt: %s",
+ attempt);
+ attemptNextPhoneAccount();
+ return;
+ }
+ }
+
+ if (mResponse != null && attempt != null) {
Log.i(this, "Trying attempt %s", attempt);
ConnectionServiceWrapper service =
mRepository.getService(
@@ -128,7 +150,7 @@
} else {
Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
if (mResponse != null) {
- mResponse.handleCreateConnectionFailed(mLastErrorCode, mLastErrorMsg);
+ mResponse.handleCreateConnectionFailure(mLastErrorCode, mLastErrorMsg);
mResponse = null;
mCall.clearConnectionService();
}
@@ -211,30 +233,26 @@
}
@Override
- public void handleCreateConnectionSuccessful(
- ConnectionRequest request, ParcelableConnection connection) {
+ public void handleCreateConnectionSuccess(ParcelableConnection connection) {
if (mResponse == null) {
+ // Nobody is listening for this connection attempt any longer; ask the responsible
+ // ConnectionService to tear down any resources associated with the call
mService.abort(mCall);
} else {
- mResponse.handleCreateConnectionSuccessful(request, connection);
+ // Success -- share the good news and remember that we are no longer interested
+ // in hearing about any more attempts
+ mResponse.handleCreateConnectionSuccess(connection);
mResponse = null;
}
}
@Override
- public void handleCreateConnectionFailed(int code, String msg) {
+ public void handleCreateConnectionFailure(int code, String msg) {
+ // Failure of some sort; record the reasons for failure and try again if possible
+ Log.d(CreateConnectionProcessor.this, "Connection failed: %d (%s)", code, msg);
mLastErrorCode = code;
mLastErrorMsg = msg;
- Log.d(CreateConnectionProcessor.this, "Connection failed: %d (%s)", code, msg);
attemptNextPhoneAccount();
}
-
- @Override
- public void handleCreateConnectionCancelled() {
- if (mResponse != null) {
- mResponse.handleCreateConnectionCancelled();
- mResponse = null;
- }
- }
}
}
diff --git a/src/com/android/telecomm/CreateConnectionResponse.java b/src/com/android/telecomm/CreateConnectionResponse.java
index 380f42c..b907e3a 100644
--- a/src/com/android/telecomm/CreateConnectionResponse.java
+++ b/src/com/android/telecomm/CreateConnectionResponse.java
@@ -16,15 +16,12 @@
package com.android.telecomm;
-import android.telecomm.ConnectionRequest;
import android.telecomm.ParcelableConnection;
/**
* A callback for providing the result of creating a connection.
*/
interface CreateConnectionResponse {
- void handleCreateConnectionSuccessful(
- ConnectionRequest request, ParcelableConnection connection);
- void handleCreateConnectionFailed(int code, String msg);
- void handleCreateConnectionCancelled();
+ void handleCreateConnectionSuccess(ParcelableConnection connection);
+ void handleCreateConnectionFailure(int code, String message);
}
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/MissedCallNotifier.java b/src/com/android/telecomm/MissedCallNotifier.java
index 94de312..eede65c 100644
--- a/src/com/android/telecomm/MissedCallNotifier.java
+++ b/src/com/android/telecomm/MissedCallNotifier.java
@@ -121,6 +121,7 @@
// Create the notification.
Notification.Builder builder = new Notification.Builder(mContext);
builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
+ .setColor(mContext.getResources().getColor(R.color.theme_color))
.setWhen(call.getCreationTimeMillis())
.setContentTitle(mContext.getText(titleResId))
.setContentText(expandedText)
diff --git a/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java b/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
index a3aa740..a45f4ef 100644
--- a/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
@@ -24,7 +24,6 @@
import android.net.Uri;
import android.os.UserHandle;
import android.telecomm.GatewayInfo;
-import android.telecomm.PhoneAccountHandle;
import android.telecomm.TelecommManager;
import android.telecomm.VideoProfile;
import android.telephony.PhoneNumberUtils;
@@ -100,11 +99,19 @@
String resultHandle = getResultData();
Log.v(this, "- got number from resultData: %s", Log.pii(resultHandle));
+ boolean endEarly = false;
if (resultHandle == null) {
Log.v(this, "Call cancelled (null number), returning...");
- return;
+ endEarly = true;
} else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(context, resultHandle)) {
Log.w(this, "Cannot modify outgoing call to emergency number %s.", resultHandle);
+ endEarly = true;
+ }
+
+ if (endEarly) {
+ if (mCall != null) {
+ mCall.disconnect();
+ }
return;
}
@@ -125,8 +132,7 @@
}
GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
- PhoneAccountHandle accountHandle = getAccountHandleFromIntent(intent);
- mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo, accountHandle,
+ mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
mIntent.getBooleanExtra(TelecommManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
false),
mIntent.getIntExtra(TelecommManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
@@ -207,7 +213,7 @@
int videoState = mIntent.getIntExtra(
TelecommManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.VideoState.AUDIO_ONLY);
- mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, handle, null), null, null,
+ mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, handle, null), null,
speakerphoneOn, videoState);
// Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
@@ -277,12 +283,6 @@
Log.d(this, "Found and copied gateway provider extras to broadcast intent.");
return;
}
- PhoneAccountHandle extraAccountHandle =
- src.getParcelableExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE);
- if (extraAccountHandle != null) {
- dst.putExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE, extraAccountHandle);
- Log.d(this, "Found and copied account extra to broadcast intent.");
- }
Log.d(this, "No provider extras found in call intent.");
}
@@ -327,20 +327,6 @@
return null;
}
- /**
- * Extracts account/connection provider information from a provided intent..
- *
- * @param intent to extract account information from.
- * @return Account object containing extracted account information
- */
- public static PhoneAccountHandle getAccountHandleFromIntent(Intent intent) {
- if (intent == null) {
- return null;
- }
-
- return intent.getParcelableExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE);
- }
-
private void launchSystemDialer(Context context, Uri handle) {
Intent systemDialerIntent = new Intent();
final Resources resources = context.getResources();
diff --git a/src/com/android/telecomm/PhoneAccountRegistrar.java b/src/com/android/telecomm/PhoneAccountRegistrar.java
index 98dcdf3..f3a5a0b 100644
--- a/src/com/android/telecomm/PhoneAccountRegistrar.java
+++ b/src/com/android/telecomm/PhoneAccountRegistrar.java
@@ -16,9 +16,11 @@
package com.android.telecomm;
+import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.telecomm.ConnectionService;
import android.telecomm.PhoneAccount;
import android.telecomm.PhoneAccountHandle;
@@ -45,6 +47,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.SecurityException;
import java.lang.String;
import java.util.ArrayList;
import java.util.Iterator;
@@ -231,6 +234,15 @@
// TODO: Should we implement an artificial limit for # of accounts associated with a single
// ComponentName?
public void registerPhoneAccount(PhoneAccount account) {
+ // Enforce the requirement that a connection service for a phone account has the correct
+ // permission.
+ if (!phoneAccountHasPermission(account.getAccountHandle())) {
+ Log.w(this, "Phone account %s does not have BIND_CONNECTION_SERVICE permission.",
+ account.getAccountHandle());
+ throw new SecurityException(
+ "PhoneAccount connection service requires BIND_CONNECTION_SERVICE permission.");
+ }
+
mState.accounts.add(account);
// Search for duplicates and remove any that are found.
for (int i = 0; i < mState.accounts.size() - 1; i++) {
@@ -288,7 +300,9 @@
}
public void removeListener(Listener l) {
- mListeners.remove(l);
+ if (l != null) {
+ mListeners.remove(l);
+ }
}
private void fireAccountsChanged() {
@@ -309,6 +323,27 @@
}
}
+ /**
+ * Determines if the connection service specified by a {@link PhoneAccountHandle} has the
+ * {@link Manifest.permission#BIND_CONNECTION_SERVICE} permission.
+ *
+ * @param phoneAccountHandle The phone account to check.
+ * @return {@code True} if the phone account has permission.
+ */
+ public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) {
+ PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
+ try {
+ ServiceInfo serviceInfo = packageManager.getServiceInfo(
+ phoneAccountHandle.getComponentName(), 0);
+
+ return serviceInfo.permission != null &&
+ serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(this, "Name not found %s", e);
+ return false;
+ }
+ }
+
////////////////////////////////////////////////////////////////////////////////////////////////
// TODO: Add a corresponding has(...) method to class PhoneAccount itself and remove this one
diff --git a/src/com/android/telecomm/ServiceBinder.java b/src/com/android/telecomm/ServiceBinder.java
index e918593..48442de 100644
--- a/src/com/android/telecomm/ServiceBinder.java
+++ b/src/com/android/telecomm/ServiceBinder.java
@@ -26,10 +26,11 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Abstract class to perform the work of binding and unbinding to the specified service interface.
@@ -153,8 +154,12 @@
/**
* Set of currently registered listeners.
+ * 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 Set<Listener> mListeners = Sets.newHashSet();
+ private final Set<Listener> mListeners = Collections.newSetFromMap(
+ new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
/**
* Persists the specified parameters and initializes the new instance.
@@ -231,7 +236,9 @@
}
final void removeListener(Listener listener) {
- mListeners.remove(listener);
+ if (listener != null) {
+ mListeners.remove(listener);
+ }
}
/**
@@ -291,9 +298,7 @@
setServiceInterface(binder);
if (binder == null) {
- // Use a copy of the listener list to allow the listeners to unregister themselves
- // as part of the unbind without causing issues.
- for (Listener l : ImmutableSet.copyOf(mListeners)) {
+ for (Listener l : mListeners) {
l.onUnbind(this);
}
}
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/src/com/android/telecomm/WiredHeadsetManager.java b/src/com/android/telecomm/WiredHeadsetManager.java
index e59f1a5..aa2bbd1 100644
--- a/src/com/android/telecomm/WiredHeadsetManager.java
+++ b/src/com/android/telecomm/WiredHeadsetManager.java
@@ -22,7 +22,9 @@
import android.content.IntentFilter;
import android.media.AudioManager;
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/** Listens for and caches headset state. */
class WiredHeadsetManager {
@@ -45,7 +47,13 @@
private final WiredHeadsetBroadcastReceiver mReceiver;
private boolean mIsPluggedIn;
- private final HashSet<Listener> mListeners = new HashSet<>();
+ /**
+ * 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 Set<Listener> mListeners = Collections.newSetFromMap(
+ new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
WiredHeadsetManager(Context context) {
mReceiver = new WiredHeadsetBroadcastReceiver();
@@ -63,7 +71,9 @@
}
void removeListener(Listener listener) {
- mListeners.remove(listener);
+ if (listener != null) {
+ mListeners.remove(listener);
+ }
}
boolean isPluggedIn() {
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index bbc3292..d8877fb 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -22,24 +22,35 @@
<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" />
<!-- Miscellaneous telecomm app-related test activities. -->
- <service android:name="com.android.telecomm.testapps.TestConnectionService">
+ <service android:name="com.android.telecomm.testapps.TestConnectionService"
+ android:permission="android.permission.BIND_CONNECTION_SERVICE" >
<intent-filter>
<action android:name="android.telecomm.ConnectionService" />
</intent-filter>
</service>
- <service android:name="com.android.telecomm.testapps.TestConnectionManager">
+ <service android:name="com.android.telecomm.testapps.TestConnectionManager"
+ android:permission="android.permission.BIND_CONNECTION_SERVICE" >
<intent-filter>
<action android:name="android.telecomm.ConnectionService" />
</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 +69,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 +101,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
-
-
</application>
<!--
diff --git a/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java b/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
index 0688f3b..5fd75e5 100644
--- a/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
+++ b/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
@@ -96,6 +96,8 @@
TelecommManager telecommManager =
(TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE);
+ telecommManager.clearAccounts(context.getPackageName());
+
telecommManager.registerPhoneAccount(PhoneAccount.builder()
.withAccountHandle(
new PhoneAccountHandle(
@@ -105,21 +107,22 @@
.withSubscriptionNumber("555-TEST")
.withCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
.withIconResId(R.drawable.stat_sys_phone_call)
- .withLabel("Dummy Service")
- .withShortDescription("a short description for the dummy service")
+ .withLabel("TelecommTestApp Call Provider")
+ .withShortDescription("a short description for the call provider")
.build());
telecommManager.registerPhoneAccount(PhoneAccount.builder()
.withAccountHandle(
new PhoneAccountHandle(
- new ComponentName(context, TestConnectionManager.class),
+ new ComponentName(context, TestConnectionService.class),
SIM_SUBSCRIPTION_ID))
- .withHandle(Uri.parse("tel:555-CMGR"))
- .withSubscriptionNumber("555-CMGR")
- .withCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+ .withHandle(Uri.parse("tel:555-TSIM"))
+ .withSubscriptionNumber("555-TSIM")
+ .withCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
.withIconResId(R.drawable.stat_sys_phone_call)
- .withLabel("Dummy Connection Manager")
- .withShortDescription("a short description for the dummy connection manager")
+ .withLabel("TelecommTestApp SIM Subscription")
+ .withShortDescription("a short description for the sim subscription")
.build());
telecommManager.registerPhoneAccount(PhoneAccount.builder()
diff --git a/tests/src/com/android/telecomm/testapps/TestConnectionManager.java b/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
index 69c5ea6..902ace7 100644
--- a/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
+++ b/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
@@ -181,7 +181,7 @@
}
private static void log(String msg) {
- Log.w("telecomtestcs", "[TestConnectionService] " + msg);
+ Log.w("telecomtestcs", "[TestConnectionManager] " + msg);
}
@Override
@@ -200,7 +200,7 @@
PhoneAccountHandle connectionManagerAccount,
final ConnectionRequest request) {
return new TestManagedConnection(
- createRemoteOutgoingConnection(
+ createRemoteIncomingConnection(
request.getAccountHandle(),
request),
true);
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;
+ }
+}