Merge "Add SIP support to TestCallActivity" into lmp-mr1-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 88e90ae..33c9838 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -57,7 +57,8 @@
android:label="@string/telecommAppLabel"
android:icon="@mipmap/ic_launcher_phone"
android:allowBackup="false"
- android:supportsRtl="true">
+ android:supportsRtl="true"
+ android:process="com.android.phone">
<!-- CALL vs CALL_PRIVILEGED vs CALL_EMERGENCY
We have three different intents through which a call can be initiated each with its
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index cb722cf..36048dd 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -89,6 +89,7 @@
void onConnectionManagerPhoneAccountChanged(Call call);
void onPhoneAccountChanged(Call call);
void onConferenceableCallsChanged(Call call);
+ boolean onCanceledViaNewOutgoingCallBroadcast(Call call);
}
abstract static class ListenerBase implements Listener {
@@ -138,6 +139,10 @@
public void onPhoneAccountChanged(Call call) {}
@Override
public void onConferenceableCallsChanged(Call call) {}
+ @Override
+ public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) {
+ return false;
+ }
}
private static final OnQueryCompleteListener sCallerInfoQueryListener =
@@ -423,8 +428,10 @@
mHandle = null;
} else {
mHandle = handle;
- if (mHandle != null && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) {
- // If the number is actually empty, set it to null.
+ if (mHandle != null && !PhoneAccount.SCHEME_VOICEMAIL.equals(mHandle.getScheme())
+ && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) {
+ // If the number is actually empty, set it to null, unless this is a
+ // SCHEME_VOICEMAIL uri which always has an empty number.
mHandle = null;
}
}
@@ -768,17 +775,21 @@
}
}
+ void disconnect() {
+ disconnect(false);
+ }
+
/**
* Attempts to disconnect the call through the connection service.
*/
- void disconnect() {
+ void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
// Track that the call is now locally disconnecting.
setLocallyDisconnecting(true);
if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT ||
mState == CallState.CONNECTING) {
Log.v(this, "Aborting call %s", this);
- abort();
+ abort(wasViaNewOutgoingCallBroadcaster);
} else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
if (mConnectionService == null) {
Log.e(this, new Exception(), "disconnect() request on a call without a"
@@ -794,11 +805,30 @@
}
}
- void abort() {
+ void abort(boolean wasViaNewOutgoingCallBroadcaster) {
if (mCreateConnectionProcessor != null) {
mCreateConnectionProcessor.abort();
} else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT
|| mState == CallState.CONNECTING) {
+ if (wasViaNewOutgoingCallBroadcaster) {
+ // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically
+ // destroy the call. Instead, we announce the cancelation and CallsManager handles
+ // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and
+ // then re-dial them quickly using a gateway, allowing the first call to end
+ // causes jank. This timeout allows CallsManager to transition the first call into
+ // the second call so that in-call only ever sees a single call...eliminating the
+ // jank altogether.
+ for (Listener listener : mListeners) {
+ if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) {
+ // The first listener to handle this wins. A return value of true means that
+ // the listener will handle the disconnection process later and so we
+ // should not continue it here.
+ setLocallyDisconnecting(false);
+ return;
+ }
+ }
+ }
+
handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
} else {
Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING");
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 300f4fa..b677b85 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -22,6 +22,7 @@
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.provider.CallLog.Calls;
import android.telecom.AudioState;
import android.telecom.CallState;
@@ -37,7 +38,6 @@
import android.telephony.TelephonyManager;
import com.android.internal.util.IndentingPrintWriter;
-
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
@@ -97,7 +97,7 @@
/**
* 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
@@ -124,6 +124,9 @@
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
+ private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
+ /* Handler tied to thread in which CallManager was initialized. */
+ private final Handler mHandler = new Handler();
/**
* The call the user is currently interacting with. This is the call that should have audio
@@ -289,6 +292,22 @@
}
}
+ @Override
+ public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) {
+ mPendingCallsToDisconnect.add(call);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (mPendingCallsToDisconnect.remove(call)) {
+ Log.i(this, "Delayed disconnection of call: %s", call);
+ call.disconnect();
+ }
+ }
+ }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
+
+ return true;
+ }
+
ImmutableCollection<Call> getCalls() {
return ImmutableList.copyOf(mCalls);
}
@@ -390,6 +409,30 @@
call.startCreateConnection(mPhoneAccountRegistrar);
}
+ private Call getNewOutgoingCall(Uri handle) {
+ // First check to see if we can reuse any of the calls that are waiting to disconnect.
+ // See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information.
+ for (Call pendingCall : mPendingCallsToDisconnect) {
+ if (Objects.equals(pendingCall.getHandle(), handle)) {
+ mPendingCallsToDisconnect.remove(pendingCall);
+ Log.i(this, "Reusing disconnected call %s", pendingCall);
+ return pendingCall;
+ }
+ }
+
+ // 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.
+ return new Call(
+ mContext,
+ mConnectionServiceRepository,
+ handle,
+ null /* gatewayInfo */,
+ null /* connectionManagerPhoneAccount */,
+ null /* phoneAccountHandle */,
+ false /* isIncoming */,
+ false /* isConference */);
+ }
+
/**
* Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
*
@@ -399,17 +442,7 @@
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
*/
Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- // 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 call = new Call(
- mContext,
- mConnectionServiceRepository,
- handle,
- null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
- null /* phoneAccountHandle */,
- false /* isIncoming */,
- false /* isConference */);
+ Call call = getNewOutgoingCall(handle);
List<PhoneAccountHandle> accounts =
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme());
@@ -444,6 +477,11 @@
// a call, or cancel this call altogether.
if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, isEmergencyCall)) {
// just cancel at this point.
+ if (mCalls.contains(call)) {
+ // This call can already exist if it is a reused call,
+ // See {@link #getNewOutgoingCall}.
+ call.disconnect();
+ }
return null;
}
@@ -463,7 +501,9 @@
// Do not add the call if it is a potential MMI code.
if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
call.addListener(this);
- } else {
+ } else if (!mCalls.contains(call)) {
+ // We check if mCalls already contains the call because we could potentially be reusing
+ // a call which was previously added (See {@link #getNewOutgoingCall}).
addCall(call);
}
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 67c63a0..f31f423 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -228,8 +228,7 @@
CallAttemptRecord record = new CallAttemptRecord(
mPhoneAccountRegistrar.getSimCallManager(),
mAttemptRecords.get(0).targetPhoneAccount);
- Log.v(this, "setConnectionManager, changing %s -> %s",
- mAttemptRecords.get(0).targetPhoneAccount, record);
+ Log.v(this, "setConnectionManager, changing %s -> %s", mAttemptRecords.get(0), record);
mAttemptRecords.set(0, record);
} else {
Log.v(this, "setConnectionManager, not changing");
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index d645009..9100718 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -114,7 +114,7 @@
if (endEarly) {
if (mCall != null) {
- mCall.disconnect();
+ mCall.disconnect(true /* wasViaNewOutgoingCall */);
}
return;
}
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index bb23123..3550201 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -250,7 +250,8 @@
// Return the registered sim call manager iff it still exists (we keep a sticky
// setting to survive account deletion and re-addition)
for (int i = 0; i < mState.accounts.size(); i++) {
- if (mState.accounts.get(i).getAccountHandle().equals(mState.simCallManager)) {
+ if (mState.accounts.get(i).getAccountHandle().equals(mState.simCallManager)
+ && !resolveComponent(mState.simCallManager.getComponentName()).isEmpty()) {
return mState.simCallManager;
}
}
@@ -260,14 +261,9 @@
String defaultConnectionMgr =
mContext.getResources().getString(R.string.default_connection_manager_component);
if (!TextUtils.isEmpty(defaultConnectionMgr)) {
- PackageManager pm = mContext.getPackageManager();
-
ComponentName componentName = ComponentName.unflattenFromString(defaultConnectionMgr);
- Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
- intent.setComponent(componentName);
-
// Make sure that the component can be resolved.
- List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0);
+ List<ResolveInfo> resolveInfos = resolveComponent(componentName);
if (!resolveInfos.isEmpty()) {
// See if there is registered PhoneAccount by this component.
List<PhoneAccountHandle> handles = getAllPhoneAccountHandles();
@@ -287,6 +283,13 @@
return null;
}
+ private List<ResolveInfo> resolveComponent(ComponentName componentName) {
+ PackageManager pm = mContext.getPackageManager();
+ Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
+ intent.setComponent(componentName);
+ return pm.queryIntentServices(intent, 0);
+ }
+
/**
* Retrieves a list of all {@link PhoneAccountHandle}s registered.
*
@@ -520,7 +523,10 @@
List<PhoneAccountHandle> accountHandles = new ArrayList<>();
for (PhoneAccount m : mState.accounts) {
if (m.hasCapabilities(flags) && (uriScheme == null || m.supportsUriScheme(uriScheme))) {
- accountHandles.add(m.getAccountHandle());
+ // Also filter out unresolveable accounts
+ if (!resolveComponent(m.getAccountHandle().getComponentName()).isEmpty()) {
+ accountHandles.add(m.getAccountHandle());
+ }
}
}
return accountHandles;
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 35f61ae..869e98a 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -52,4 +52,14 @@
public static long getDirectToVoicemailMillis(ContentResolver contentResolver) {
return get(contentResolver, "direct_to_voicemail_ms", 500L);
}
+
+ /**
+ * Returns the amount of time to wait before disconnecting a call that was canceled via
+ * NEW_OUTGOING_CALL broadcast. This timeout allows apps which repost the call using a gateway
+ * to reuse the existing call, preventing the call from causing a start->end->start jank in the
+ * in-call UI.
+ */
+ public static long getNewOutgoingCallCancelMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "new_outgoing_call_cancel_ms", 200L);
+ }
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 0da81bf..df7e93e 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -23,6 +23,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
<uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
+ <uses-permission android:name="android.permission.REGISTER_CONNECTION_MANAGER" />
<uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION" />
<application android:label="@string/app_name"