1 /* 2 * Copyright 2014, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.telecom; 18 19 import android.content.Context; 20 import android.telecom.DisconnectCause; 21 import android.telecom.ParcelableConnection; 22 import android.telecom.Phone; 23 import android.telecom.PhoneAccount; 24 import android.telecom.PhoneAccountHandle; 25 26 // TODO: Needed for move to system service: import com.android.internal.R; 27 28 import java.util.ArrayList; 29 import java.util.Iterator; 30 import java.util.List; 31 import java.util.Objects; 32 33 /** 34 * This class creates connections to place new outgoing calls or to attach to an existing incoming 35 * call. In either case, this class cycles through a set of connection services until: 36 * - a connection service returns a newly created connection in which case the call is displayed 37 * to the user 38 * - a connection service cancels the process, in which case the call is aborted 39 */ 40 final class CreateConnectionProcessor { 41 42 // Describes information required to attempt to make a phone call 43 private static class CallAttemptRecord { 44 // The PhoneAccount describing the target connection service which we will 45 // contact in order to process an attempt 46 public final PhoneAccountHandle connectionManagerPhoneAccount; 47 // The PhoneAccount which we will tell the target connection service to use 48 // for attempting to make the actual phone call 49 public final PhoneAccountHandle targetPhoneAccount; 50 51 public CallAttemptRecord( 52 PhoneAccountHandle connectionManagerPhoneAccount, 53 PhoneAccountHandle targetPhoneAccount) { 54 this.connectionManagerPhoneAccount = connectionManagerPhoneAccount; 55 this.targetPhoneAccount = targetPhoneAccount; 56 } 57 58 @Override 59 public String toString() { 60 return "CallAttemptRecord(" 61 + Objects.toString(connectionManagerPhoneAccount) + "," 62 + Objects.toString(targetPhoneAccount) + ")"; 63 } 64 65 /** 66 * Determines if this instance of {@code CallAttemptRecord} has the same underlying 67 * {@code PhoneAccountHandle}s as another instance. 68 * 69 * @param obj The other instance to compare against. 70 * @return {@code True} if the {@code CallAttemptRecord}s are equal. 71 */ 72 @Override 73 public boolean equals(Object obj) { 74 if (obj instanceof CallAttemptRecord) { 75 CallAttemptRecord other = (CallAttemptRecord) obj; 76 return Objects.equals(connectionManagerPhoneAccount, 77 other.connectionManagerPhoneAccount) && 78 Objects.equals(targetPhoneAccount, other.targetPhoneAccount); 79 } 80 return false; 81 } 82 } 83 84 private final Call mCall; 85 private final ConnectionServiceRepository mRepository; 86 private List<CallAttemptRecord> mAttemptRecords; 87 private Iterator<CallAttemptRecord> mAttemptRecordIterator; 88 private CreateConnectionResponse mResponse; 89 private DisconnectCause mLastErrorDisconnectCause; 90 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 91 private final Context mContext; 92 93 CreateConnectionProcessor( 94 Call call, ConnectionServiceRepository repository, CreateConnectionResponse response, 95 PhoneAccountRegistrar phoneAccountRegistrar, Context context) { 96 mCall = call; 97 mRepository = repository; 98 mResponse = response; 99 mPhoneAccountRegistrar = phoneAccountRegistrar; 100 mContext = context; 101 } 102 103 void process() { 104 Log.v(this, "process"); 105 mAttemptRecords = new ArrayList<>(); 106 if (mCall.getTargetPhoneAccount() != null) { 107 mAttemptRecords.add(new CallAttemptRecord( 108 mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount())); 109 } 110 adjustAttemptsForConnectionManager(); 111 adjustAttemptsForEmergency(); 112 mAttemptRecordIterator = mAttemptRecords.iterator(); 113 attemptNextPhoneAccount(); 114 } 115 116 void abort() { 117 Log.v(this, "abort"); 118 119 // Clear the response first to prevent attemptNextConnectionService from attempting any 120 // more services. 121 CreateConnectionResponse response = mResponse; 122 mResponse = null; 123 124 ConnectionServiceWrapper service = mCall.getConnectionService(); 125 if (service != null) { 126 service.abort(mCall); 127 mCall.clearConnectionService(); 128 } 129 if (response != null) { 130 response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.LOCAL)); 131 } 132 } 133 134 private void attemptNextPhoneAccount() { 135 Log.v(this, "attemptNextPhoneAccount"); 136 CallAttemptRecord attempt = null; 137 if (mAttemptRecordIterator.hasNext()) { 138 attempt = mAttemptRecordIterator.next(); 139 140 if (!mPhoneAccountRegistrar.phoneAccountHasPermission( 141 attempt.connectionManagerPhoneAccount)) { 142 Log.w(this, 143 "Connection mgr does not have BIND_CONNECTION_SERVICE for attempt: %s", 144 attempt); 145 attemptNextPhoneAccount(); 146 return; 147 } 148 149 // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it 150 // also has BIND_CONNECTION_SERVICE permission. 151 if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) && 152 !mPhoneAccountRegistrar.phoneAccountHasPermission(attempt.targetPhoneAccount)) { 153 Log.w(this, 154 "Target PhoneAccount does not have BIND_CONNECTION_SERVICE for attempt: %s", 155 attempt); 156 attemptNextPhoneAccount(); 157 return; 158 } 159 } 160 161 if (mResponse != null && attempt != null) { 162 Log.i(this, "Trying attempt %s", attempt); 163 ConnectionServiceWrapper service = 164 mRepository.getService( 165 attempt.connectionManagerPhoneAccount.getComponentName()); 166 if (service == null) { 167 Log.i(this, "Found no connection service for attempt %s", attempt); 168 attemptNextPhoneAccount(); 169 } else { 170 mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount); 171 mCall.setTargetPhoneAccount(attempt.targetPhoneAccount); 172 mCall.setConnectionService(service); 173 Log.i(this, "Attempting to call from %s", service.getComponentName()); 174 service.createConnection(mCall, new Response(service)); 175 } 176 } else { 177 Log.v(this, "attemptNextPhoneAccount, no more accounts, failing"); 178 if (mResponse != null) { 179 mResponse.handleCreateConnectionFailure(mLastErrorDisconnectCause != null ? 180 mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR)); 181 mResponse = null; 182 mCall.clearConnectionService(); 183 } 184 } 185 } 186 187 private boolean shouldSetConnectionManager() { 188 if (mAttemptRecords.size() == 0) { 189 return false; 190 } 191 192 if (mAttemptRecords.size() > 1) { 193 Log.d(this, "shouldSetConnectionManager, error, mAttemptRecords should not have more " 194 + "than 1 record"); 195 return false; 196 } 197 198 PhoneAccountHandle connectionManager = mPhoneAccountRegistrar.getSimCallManager(); 199 if (connectionManager == null) { 200 return false; 201 } 202 203 PhoneAccountHandle targetPhoneAccountHandle = mAttemptRecords.get(0).targetPhoneAccount; 204 if (Objects.equals(connectionManager, targetPhoneAccountHandle)) { 205 return false; 206 } 207 208 // Connection managers are only allowed to manage SIM subscriptions. 209 PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar.getPhoneAccount( 210 targetPhoneAccountHandle); 211 boolean isSimSubscription = (targetPhoneAccount.getCapabilities() & 212 PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0; 213 if (!isSimSubscription) { 214 return false; 215 } 216 217 return true; 218 } 219 220 // If there exists a registered connection manager then use it. 221 private void adjustAttemptsForConnectionManager() { 222 if (shouldSetConnectionManager()) { 223 CallAttemptRecord record = new CallAttemptRecord( 224 mPhoneAccountRegistrar.getSimCallManager(), 225 mAttemptRecords.get(0).targetPhoneAccount); 226 Log.v(this, "setConnectionManager, changing %s -> %s", 227 mAttemptRecords.get(0).targetPhoneAccount, record); 228 mAttemptRecords.set(0, record); 229 } else { 230 Log.v(this, "setConnectionManager, not changing"); 231 } 232 } 233 234 // If we are possibly attempting to call a local emergency number, ensure that the 235 // plain PSTN connection services are listed, and nothing else. 236 private void adjustAttemptsForEmergency() { 237 if (TelephonyUtil.shouldProcessAsEmergency(mContext, mCall.getHandle())) { 238 Log.i(this, "Emergency number detected"); 239 mAttemptRecords.clear(); 240 List<PhoneAccount> allAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts(); 241 242 if (allAccounts.isEmpty()) { 243 // If the list of phone accounts is empty at this point, it means Telephony hasn't 244 // registered any phone accounts yet. Add a fallback emergency phone account so 245 // that emergency calls can still go through. We create a new ArrayLists here just 246 // in case the implementation of PhoneAccountRegistrar ever returns an unmodifiable 247 // list. 248 allAccounts = new ArrayList<PhoneAccount>(); 249 allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount()); 250 } 251 252 253 // First, add SIM phone accounts which can place emergency calls. 254 for (PhoneAccount phoneAccount : allAccounts) { 255 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) && 256 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 257 Log.i(this, "Will try PSTN account %s for emergency", 258 phoneAccount.getAccountHandle()); 259 mAttemptRecords.add( 260 new CallAttemptRecord( 261 phoneAccount.getAccountHandle(), 262 phoneAccount.getAccountHandle())); 263 } 264 } 265 266 // Next, add the connection manager account as a backup if it can place emergency calls. 267 PhoneAccountHandle callManagerHandle = mPhoneAccountRegistrar.getSimCallManager(); 268 if (callManagerHandle != null) { 269 PhoneAccount callManager = mPhoneAccountRegistrar 270 .getPhoneAccount(callManagerHandle); 271 if (callManager.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) { 272 CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle, 273 mPhoneAccountRegistrar. 274 getDefaultOutgoingPhoneAccount(mCall.getHandle().getScheme()) 275 ); 276 277 if (!mAttemptRecords.contains(callAttemptRecord)) { 278 Log.i(this, "Will try Connection Manager account %s for emergency", 279 callManager); 280 mAttemptRecords.add(callAttemptRecord); 281 } 282 } 283 } 284 } 285 } 286 287 private class Response implements CreateConnectionResponse { 288 private final ConnectionServiceWrapper mService; 289 290 Response(ConnectionServiceWrapper service) { 291 mService = service; 292 } 293 294 @Override 295 public void handleCreateConnectionSuccess( 296 CallIdMapper idMapper, 297 ParcelableConnection connection) { 298 if (mResponse == null) { 299 // Nobody is listening for this connection attempt any longer; ask the responsible 300 // ConnectionService to tear down any resources associated with the call 301 mService.abort(mCall); 302 } else { 303 // Success -- share the good news and remember that we are no longer interested 304 // in hearing about any more attempts 305 mResponse.handleCreateConnectionSuccess(idMapper, connection); 306 mResponse = null; 307 } 308 } 309 310 @Override 311 public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) { 312 // Failure of some sort; record the reasons for failure and try again if possible 313 Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause); 314 mLastErrorDisconnectCause = errorDisconnectCause; 315 attemptNextPhoneAccount(); 316 } 317 } 318 } 319