Merge "TestConnectionService needs more permissions!" 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/BluetoothPhoneService.java b/src/com/android/server/telecom/BluetoothPhoneService.java
index 8bf50ae..e9a00ff 100644
--- a/src/com/android/server/telecom/BluetoothPhoneService.java
+++ b/src/com/android/server/telecom/BluetoothPhoneService.java
@@ -680,7 +680,7 @@
String ringingAddress = null;
int ringingAddressType = 128;
- if (ringingCall != null) {
+ if (ringingCall != null && ringingCall.getHandle() != null) {
ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
if (ringingAddress != null) {
ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index e2bead7..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 =
@@ -417,8 +422,20 @@
void setHandle(Uri handle, int presentation) {
if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
- mHandle = handle;
mHandlePresentation = presentation;
+ if (mHandlePresentation == TelecomManager.PRESENTATION_RESTRICTED ||
+ mHandlePresentation == TelecomManager.PRESENTATION_UNKNOWN) {
+ mHandle = null;
+ } else {
+ mHandle = handle;
+ 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;
+ }
+ }
+
mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(mContext,
mHandle.getSchemeSpecificPart());
startCallerInfoLookup();
@@ -758,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"
@@ -784,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 fab2679..f31f423 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -89,6 +89,7 @@
private DisconnectCause mLastErrorDisconnectCause;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final Context mContext;
+ private boolean mShouldUseConnectionManager = true;
CreateConnectionProcessor(
Call call, ConnectionServiceRepository repository, CreateConnectionResponse response,
@@ -185,6 +186,10 @@
}
private boolean shouldSetConnectionManager() {
+ if (!mShouldUseConnectionManager) {
+ return false;
+ }
+
if (mAttemptRecords.size() == 0) {
return false;
}
@@ -223,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");
@@ -307,12 +311,44 @@
}
}
+ private boolean shouldFallbackToNoConnectionManager(DisconnectCause cause) {
+ PhoneAccountHandle handle = mCall.getConnectionManagerPhoneAccount();
+ if (handle == null || !handle.equals(mPhoneAccountRegistrar.getSimCallManager())) {
+ return false;
+ }
+
+ ConnectionServiceWrapper connectionManager = mCall.getConnectionService();
+ if (connectionManager == null) {
+ return false;
+ }
+
+ if (cause.getCode() == DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED) {
+ Log.d(CreateConnectionProcessor.this, "Connection manager declined to handle the "
+ + "call, falling back to not using a connection manager");
+ return true;
+ }
+
+ if (!connectionManager.isServiceValid("createConnection")) {
+ Log.d(CreateConnectionProcessor.this, "Connection manager unbound while trying "
+ + "create a connection, falling back to not using a connection manager");
+ return true;
+ }
+
+ return false;
+ }
+
@Override
public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) {
// Failure of some sort; record the reasons for failure and try again if possible
Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause);
mLastErrorDisconnectCause = errorDisconnectCause;
- attemptNextPhoneAccount();
+ if (shouldFallbackToNoConnectionManager(errorDisconnectCause)) {
+ mShouldUseConnectionManager = false;
+ // Restart from the beginning.
+ process();
+ } else {
+ attemptNextPhoneAccount();
+ }
}
}
}
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/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 2ac9379..dcfa19a 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -139,8 +139,7 @@
@Override
public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
- if (rejectWithMessage) {
-
+ if (rejectWithMessage && call.getHandle() != null) {
rejectCallWithMessage(call.getContext(), call.getHandle().getSchemeSpecificPart(),
textMessage);
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 2656547..c3a9f9f 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -459,6 +460,47 @@
}
/**
+ * @see android.telecom.TelecomManager#handleMmi
+ */
+ @Override
+ public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
+ String dialString) {
+ enforceModifyPermissionOrDefaultDialer();
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ try {
+ int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+ retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return retval;
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#getAdnUriForPhoneAccount
+ */
+ @Override
+ public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
+ enforceModifyPermissionOrDefaultDialer();
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+ long token = Binder.clearCallingIdentity();
+ String retval = "content://icc/adn/";
+ try {
+ long subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+ retval = retval + "subId/" + subId;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return Uri.parse(retval);
+ }
+
+ /**
* @see android.telecom.TelecomManager#isTtySupported
*/
@Override
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);
+ }
}