/* * 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 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; } }