Loading src/com/android/server/telecom/Call.java +24 −7 Original line number Diff line number Diff line Loading @@ -410,6 +410,28 @@ final class Call implements CreateConnectionResponse { 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 Loading @@ -420,13 +442,8 @@ final class Call implements CreateConnectionResponse { 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; } Loading src/com/android/server/telecom/CreateConnectionProcessor.java +49 −1 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -90,6 +97,7 @@ final class CreateConnectionProcessor { private final PhoneAccountRegistrar mPhoneAccountRegistrar; private final Context mContext; private boolean mShouldUseConnectionManager = true; private CreateConnectionTimeout mTimeout; CreateConnectionProcessor( Call call, ConnectionServiceRepository repository, CreateConnectionResponse response, Loading @@ -105,8 +113,13 @@ final class CreateConnectionProcessor { 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( Loading Loading @@ -137,6 +150,7 @@ final class CreateConnectionProcessor { // more services. CreateConnectionResponse response = mResponse; mResponse = null; clearTimeout(); ConnectionServiceWrapper service = mCall.getConnectionService(); if (service != null) { Loading Loading @@ -189,12 +203,15 @@ final class CreateConnectionProcessor { 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; Loading @@ -203,6 +220,25 @@ final class CreateConnectionProcessor { } } 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; Loading Loading @@ -287,7 +323,7 @@ final class CreateConnectionProcessor { // 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)) { Loading @@ -306,6 +342,16 @@ final class CreateConnectionProcessor { } } /** 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; Loading @@ -326,6 +372,8 @@ final class CreateConnectionProcessor { // 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. } } Loading src/com/android/server/telecom/CreateConnectionTimeout.java 0 → 100644 +155 −0 Original line number Diff line number Diff line /* * 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; } } src/com/android/server/telecom/Timeouts.java +17 −0 Original line number Diff line number Diff line Loading @@ -72,4 +72,21 @@ public final class Timeouts { 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 */); } } Loading
src/com/android/server/telecom/Call.java +24 −7 Original line number Diff line number Diff line Loading @@ -410,6 +410,28 @@ final class Call implements CreateConnectionResponse { 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 Loading @@ -420,13 +442,8 @@ final class Call implements CreateConnectionResponse { 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; } Loading
src/com/android/server/telecom/CreateConnectionProcessor.java +49 −1 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -90,6 +97,7 @@ final class CreateConnectionProcessor { private final PhoneAccountRegistrar mPhoneAccountRegistrar; private final Context mContext; private boolean mShouldUseConnectionManager = true; private CreateConnectionTimeout mTimeout; CreateConnectionProcessor( Call call, ConnectionServiceRepository repository, CreateConnectionResponse response, Loading @@ -105,8 +113,13 @@ final class CreateConnectionProcessor { 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( Loading Loading @@ -137,6 +150,7 @@ final class CreateConnectionProcessor { // more services. CreateConnectionResponse response = mResponse; mResponse = null; clearTimeout(); ConnectionServiceWrapper service = mCall.getConnectionService(); if (service != null) { Loading Loading @@ -189,12 +203,15 @@ final class CreateConnectionProcessor { 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; Loading @@ -203,6 +220,25 @@ final class CreateConnectionProcessor { } } 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; Loading Loading @@ -287,7 +323,7 @@ final class CreateConnectionProcessor { // 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)) { Loading @@ -306,6 +342,16 @@ final class CreateConnectionProcessor { } } /** 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; Loading @@ -326,6 +372,8 @@ final class CreateConnectionProcessor { // 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. } } Loading
src/com/android/server/telecom/CreateConnectionTimeout.java 0 → 100644 +155 −0 Original line number Diff line number Diff line /* * 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; } }
src/com/android/server/telecom/Timeouts.java +17 −0 Original line number Diff line number Diff line Loading @@ -72,4 +72,21 @@ public final class Timeouts { 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 */); } }