Merge "Fixing conference managment regression." into lmp-mr1-dev
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 8bf516d..8a85f2c 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -410,6 +410,28 @@
return mState;
}
+ private boolean shouldContinueProcessingAfterDisconnect() {
+ // Stop processing once the call is active.
+ if (!CreateConnectionTimeout.isCallBeingPlaced(this)) {
+ return false;
+ }
+
+ // Make sure that there are additional connection services to process.
+ if (mCreateConnectionProcessor == null
+ || !mCreateConnectionProcessor.isProcessingComplete()
+ || !mCreateConnectionProcessor.hasMorePhoneAccounts()) {
+ return false;
+ }
+
+ if (mDisconnectCause == null) {
+ return false;
+ }
+
+ // Continue processing if the current attempt failed or timed out.
+ return mDisconnectCause.getCode() == DisconnectCause.ERROR ||
+ mCreateConnectionProcessor.isCallTimedOut();
+ }
+
/**
* Sets the call state. Although there exists the notion of appropriate state transitions
* (see {@link CallState}), in practice those expectations break down when cellular systems
@@ -420,13 +442,8 @@
if (mState != newState) {
Log.v(this, "setState %s -> %s", mState, newState);
- if (newState == CallState.DISCONNECTED
- && (mState == CallState.DIALING || mState == CallState.CONNECTING)
- && mCreateConnectionProcessor != null
- && mCreateConnectionProcessor.isProcessingComplete()
- && mCreateConnectionProcessor.hasMorePhoneAccounts()
- && mDisconnectCause != null
- && mDisconnectCause.getCode() == DisconnectCause.ERROR) {
+ if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
+ Log.w(this, "continuing processing disconnected call with another service");
mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
return;
}
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 91091ec..eec1427 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -17,17 +17,24 @@
package com.android.server.telecom;
import android.content.Context;
+import android.telecom.CallState;
import android.telecom.DisconnectCause;
import android.telecom.ParcelableConnection;
import android.telecom.Phone;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
// TODO: Needed for move to system service: import com.android.internal.R;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.Objects;
/**
@@ -90,6 +97,7 @@
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final Context mContext;
private boolean mShouldUseConnectionManager = true;
+ private CreateConnectionTimeout mTimeout;
CreateConnectionProcessor(
Call call, ConnectionServiceRepository repository, CreateConnectionResponse response,
@@ -105,8 +113,13 @@
return mResponse == null;
}
+ boolean isCallTimedOut() {
+ return mTimeout != null && mTimeout.isCallTimedOut();
+ }
+
void process() {
Log.v(this, "process");
+ clearTimeout();
mAttemptRecords = new ArrayList<>();
if (mCall.getTargetPhoneAccount() != null) {
mAttemptRecords.add(new CallAttemptRecord(
@@ -137,6 +150,7 @@
// more services.
CreateConnectionResponse response = mResponse;
mResponse = null;
+ clearTimeout();
ConnectionServiceWrapper service = mCall.getConnectionService();
if (service != null) {
@@ -189,12 +203,15 @@
mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
mCall.setConnectionService(service);
+ setTimeoutIfNeeded(service, attempt);
+
Log.i(this, "Attempting to call from %s", service.getComponentName());
service.createConnection(mCall, new Response(service));
}
} else {
Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
if (mResponse != null) {
+ clearTimeout();
mResponse.handleCreateConnectionFailure(mLastErrorDisconnectCause != null ?
mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR));
mResponse = null;
@@ -203,6 +220,25 @@
}
}
+ private void setTimeoutIfNeeded(ConnectionServiceWrapper service, CallAttemptRecord attempt) {
+ clearTimeout();
+
+ CreateConnectionTimeout timeout = new CreateConnectionTimeout(
+ mContext, mPhoneAccountRegistrar, service, mCall);
+ if (timeout.isTimeoutNeededForCall(getConnectionServices(mAttemptRecords),
+ attempt.connectionManagerPhoneAccount)) {
+ mTimeout = timeout;
+ timeout.registerTimeout();
+ }
+ }
+
+ private void clearTimeout() {
+ if (mTimeout != null) {
+ mTimeout.unregisterTimeout();
+ mTimeout = null;
+ }
+ }
+
private boolean shouldSetConnectionManager() {
if (!mShouldUseConnectionManager) {
return false;
@@ -287,7 +323,7 @@
// Next, add the connection manager account as a backup if it can place emergency calls.
PhoneAccountHandle callManagerHandle = mPhoneAccountRegistrar.getSimCallManager();
- if (callManagerHandle != null) {
+ if (mShouldUseConnectionManager && callManagerHandle != null) {
PhoneAccount callManager = mPhoneAccountRegistrar
.getPhoneAccount(callManagerHandle);
if (callManager.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
@@ -306,6 +342,16 @@
}
}
+ /** Returns all connection services used by the call attempt records. */
+ private static Collection<PhoneAccountHandle> getConnectionServices(
+ List<CallAttemptRecord> records) {
+ HashSet<PhoneAccountHandle> result = new HashSet<>();
+ for (CallAttemptRecord record : records) {
+ result.add(record.connectionManagerPhoneAccount);
+ }
+ return result;
+ }
+
private class Response implements CreateConnectionResponse {
private final ConnectionServiceWrapper mService;
@@ -326,6 +372,8 @@
// in hearing about any more attempts
mResponse.handleCreateConnectionSuccess(idMapper, connection);
mResponse = null;
+ // If there's a timeout running then don't clear it. The timeout can be triggered
+ // after the call has successfully been created but before it has become active.
}
}
diff --git a/src/com/android/server/telecom/CreateConnectionTimeout.java b/src/com/android/server/telecom/CreateConnectionTimeout.java
new file mode 100644
index 0000000..3f308cc
--- /dev/null
+++ b/src/com/android/server/telecom/CreateConnectionTimeout.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2015, 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.server.telecom;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.telecom.CallState;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Registers a timeout for a call and disconnects the call when the timeout expires.
+ */
+final class CreateConnectionTimeout extends PhoneStateListener implements Runnable {
+ private final Context mContext;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final ConnectionServiceWrapper mConnectionService;
+ private final Call mCall;
+ private final Handler mHandler = new Handler();
+ private boolean mIsRegistered;
+ private boolean mIsCallTimedOut;
+
+ CreateConnectionTimeout(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
+ ConnectionServiceWrapper service, Call call) {
+ mContext = context;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
+ mConnectionService = service;
+ mCall = call;
+ }
+
+ boolean isTimeoutNeededForCall(Collection<PhoneAccountHandle> accounts,
+ PhoneAccountHandle currentAccount) {
+ // Non-emergency calls timeout automatically at the radio layer. No need for a timeout here.
+ if (!TelephonyUtil.shouldProcessAsEmergency(mContext, mCall.getHandle())) {
+ return false;
+ }
+
+ // If there's no connection manager to fallback on then there's no point in having a
+ // timeout.
+ PhoneAccountHandle connectionManager = mPhoneAccountRegistrar.getSimCallManager();
+ if (!accounts.contains(connectionManager)) {
+ return false;
+ }
+
+ // No need to add a timeout if the current attempt is over the connection manager.
+ if (Objects.equals(connectionManager, currentAccount)) {
+ return false;
+ }
+
+ // To reduce the number of scenarios where a timeout is needed, only use a timeout if
+ // we're connected to Wi-Fi. This ensures that the fallback connection manager has an
+ // alternate route to place the call. TODO: remove this condition or allow connection
+ // managers to specify transports. See http://b/19199181.
+ if (!isConnectedToWifi()) {
+ return false;
+ }
+
+ Log.d(this, "isTimeoutNeededForCall, returning true");
+ return true;
+ }
+
+ void registerTimeout() {
+ Log.d(this, "registerTimeout");
+ mIsRegistered = true;
+ // First find out the cellular service state. Based on the state we decide whether a timeout
+ // will actually be enforced and if so how long it should be.
+ TelephonyManager telephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ telephonyManager.listen(this, PhoneStateListener.LISTEN_SERVICE_STATE);
+ telephonyManager.listen(this, 0);
+ }
+
+ void unregisterTimeout() {
+ Log.d(this, "unregisterTimeout");
+ mIsRegistered = false;
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ boolean isCallTimedOut() {
+ return mIsCallTimedOut;
+ }
+
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ long timeoutLengthMillis = getTimeoutLengthMillis(serviceState);
+ if (!mIsRegistered) {
+ Log.d(this, "onServiceStateChanged, timeout no longer registered, skipping");
+ } else if (timeoutLengthMillis <= 0) {
+ Log.d(this, "onServiceStateChanged, timeout set to %d, skipping", timeoutLengthMillis);
+ } else if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
+ // If cellular service is available then don't bother with a timeout.
+ Log.d(this, "onServiceStateChanged, cellular service available, skipping");
+ } else {
+ mHandler.postDelayed(this, timeoutLengthMillis);
+ }
+ }
+
+ @Override
+ public void run() {
+ if (mIsRegistered && isCallBeingPlaced(mCall)) {
+ Log.d(this, "run, call timed out, calling disconnect");
+ mIsCallTimedOut = true;
+ mConnectionService.disconnect(mCall);
+ }
+ }
+
+ static boolean isCallBeingPlaced(Call call) {
+ int state = call.getState();
+ return state == CallState.NEW
+ || state == CallState.CONNECTING
+ || state == CallState.DIALING;
+ }
+
+ private long getTimeoutLengthMillis(ServiceState serviceState) {
+ // If the radio is off then use a longer timeout. This gives us more time to power on the
+ // radio.
+ if (serviceState.getState() == ServiceState.STATE_POWER_OFF) {
+ return Timeouts.getEmergencyCallTimeoutRadioOffMillis(
+ mContext.getContentResolver());
+ } else {
+ return Timeouts.getEmergencyCallTimeoutMillis(mContext.getContentResolver());
+ }
+ }
+
+ private boolean isConnectedToWifi() {
+ ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ if (cm != null) {
+ NetworkInfo ni = cm.getActiveNetworkInfo();
+ return ni != null && ni.isConnected() && ni.getType() == ConnectivityManager.TYPE_WIFI;
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 75b94c2..b5cf39a 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -72,4 +72,21 @@
return get(contentResolver, "delay_between_dtmf_tones_ms", 300L);
}
+ /**
+ * Returns the amount of time to wait for an emergency call to be placed before routing to
+ * a different call service. A value of 0 or less means no timeout should be used.
+ */
+ public static long getEmergencyCallTimeoutMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "emergency_call_timeout_millis", 25000L /* 25 seconds */);
+ }
+
+ /**
+ * Returns the amount of time to wait for an emergency call to be placed before routing to
+ * a different call service. This timeout is used only when the radio is powered off (for
+ * example in airplane mode). A value of 0 or less means no timeout should be used.
+ */
+ public static long getEmergencyCallTimeoutRadioOffMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "emergency_call_timeout_radio_off_millis",
+ 60000L /* 1 minute */);
+ }
}