• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Log;
22 import android.telecom.ParcelableConnection;
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 com.android.internal.annotations.VisibleForTesting;
29 
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Objects;
36 
37 /**
38  * This class creates connections to place new outgoing calls or to attach to an existing incoming
39  * call. In either case, this class cycles through a set of connection services until:
40  *   - a connection service returns a newly created connection in which case the call is displayed
41  *     to the user
42  *   - a connection service cancels the process, in which case the call is aborted
43  */
44 @VisibleForTesting
45 public class CreateConnectionProcessor implements CreateConnectionResponse {
46 
47     // Describes information required to attempt to make a phone call
48     private static class CallAttemptRecord {
49         // The PhoneAccount describing the target connection service which we will
50         // contact in order to process an attempt
51         public final PhoneAccountHandle connectionManagerPhoneAccount;
52         // The PhoneAccount which we will tell the target connection service to use
53         // for attempting to make the actual phone call
54         public final PhoneAccountHandle targetPhoneAccount;
55 
CallAttemptRecord( PhoneAccountHandle connectionManagerPhoneAccount, PhoneAccountHandle targetPhoneAccount)56         public CallAttemptRecord(
57                 PhoneAccountHandle connectionManagerPhoneAccount,
58                 PhoneAccountHandle targetPhoneAccount) {
59             this.connectionManagerPhoneAccount = connectionManagerPhoneAccount;
60             this.targetPhoneAccount = targetPhoneAccount;
61         }
62 
63         @Override
toString()64         public String toString() {
65             return "CallAttemptRecord("
66                     + Objects.toString(connectionManagerPhoneAccount) + ","
67                     + Objects.toString(targetPhoneAccount) + ")";
68         }
69 
70         /**
71          * Determines if this instance of {@code CallAttemptRecord} has the same underlying
72          * {@code PhoneAccountHandle}s as another instance.
73          *
74          * @param obj The other instance to compare against.
75          * @return {@code True} if the {@code CallAttemptRecord}s are equal.
76          */
77         @Override
equals(Object obj)78         public boolean equals(Object obj) {
79             if (obj instanceof CallAttemptRecord) {
80                 CallAttemptRecord other = (CallAttemptRecord) obj;
81                 return Objects.equals(connectionManagerPhoneAccount,
82                         other.connectionManagerPhoneAccount) &&
83                         Objects.equals(targetPhoneAccount, other.targetPhoneAccount);
84             }
85             return false;
86         }
87     }
88 
89     private final Call mCall;
90     private final ConnectionServiceRepository mRepository;
91     private List<CallAttemptRecord> mAttemptRecords;
92     private Iterator<CallAttemptRecord> mAttemptRecordIterator;
93     private CreateConnectionResponse mCallResponse;
94     private DisconnectCause mLastErrorDisconnectCause;
95     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
96     private final Context mContext;
97     private CreateConnectionTimeout mTimeout;
98     private ConnectionServiceWrapper mService;
99     private int mConnectionAttempt;
100 
101     @VisibleForTesting
CreateConnectionProcessor( Call call, ConnectionServiceRepository repository, CreateConnectionResponse response, PhoneAccountRegistrar phoneAccountRegistrar, Context context)102     public CreateConnectionProcessor(
103             Call call, ConnectionServiceRepository repository, CreateConnectionResponse response,
104             PhoneAccountRegistrar phoneAccountRegistrar, Context context) {
105         Log.v(this, "CreateConnectionProcessor created for Call = %s", call);
106         mCall = call;
107         mRepository = repository;
108         mCallResponse = response;
109         mPhoneAccountRegistrar = phoneAccountRegistrar;
110         mContext = context;
111         mConnectionAttempt = 0;
112     }
113 
isProcessingComplete()114     boolean isProcessingComplete() {
115         return mCallResponse == null;
116     }
117 
isCallTimedOut()118     boolean isCallTimedOut() {
119         return mTimeout != null && mTimeout.isCallTimedOut();
120     }
121 
getConnectionAttempt()122     public int getConnectionAttempt() {
123         return mConnectionAttempt;
124     }
125 
126     @VisibleForTesting
process()127     public void process() {
128         Log.v(this, "process");
129         clearTimeout();
130         mAttemptRecords = new ArrayList<>();
131         if (mCall.getTargetPhoneAccount() != null) {
132             mAttemptRecords.add(new CallAttemptRecord(
133                     mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount()));
134         }
135         if (!mCall.isSelfManaged()) {
136             adjustAttemptsForConnectionManager();
137             adjustAttemptsForEmergency(mCall.getTargetPhoneAccount());
138         }
139         mAttemptRecordIterator = mAttemptRecords.iterator();
140         attemptNextPhoneAccount();
141     }
142 
hasMorePhoneAccounts()143     boolean hasMorePhoneAccounts() {
144         return mAttemptRecordIterator.hasNext();
145     }
146 
continueProcessingIfPossible(CreateConnectionResponse response, DisconnectCause disconnectCause)147     void continueProcessingIfPossible(CreateConnectionResponse response,
148             DisconnectCause disconnectCause) {
149         Log.v(this, "continueProcessingIfPossible");
150         mCallResponse = response;
151         mLastErrorDisconnectCause = disconnectCause;
152         attemptNextPhoneAccount();
153     }
154 
abort()155     void abort() {
156         Log.v(this, "abort");
157 
158         // Clear the response first to prevent attemptNextConnectionService from attempting any
159         // more services.
160         CreateConnectionResponse response = mCallResponse;
161         mCallResponse = null;
162         clearTimeout();
163 
164         ConnectionServiceWrapper service = mCall.getConnectionService();
165         if (service != null) {
166             service.abort(mCall);
167             mCall.clearConnectionService();
168         }
169         if (response != null) {
170             response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.LOCAL));
171         }
172     }
173 
attemptNextPhoneAccount()174     private void attemptNextPhoneAccount() {
175         Log.v(this, "attemptNextPhoneAccount");
176         CallAttemptRecord attempt = null;
177         if (mAttemptRecordIterator.hasNext()) {
178             attempt = mAttemptRecordIterator.next();
179 
180             if (!mPhoneAccountRegistrar.phoneAccountRequiresBindPermission(
181                     attempt.connectionManagerPhoneAccount)) {
182                 Log.w(this,
183                         "Connection mgr does not have BIND_TELECOM_CONNECTION_SERVICE for "
184                                 + "attempt: %s", attempt);
185                 attemptNextPhoneAccount();
186                 return;
187             }
188 
189             // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it
190             // also requires the BIND_TELECOM_CONNECTION_SERVICE permission.
191             if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) &&
192                     !mPhoneAccountRegistrar.phoneAccountRequiresBindPermission(
193                             attempt.targetPhoneAccount)) {
194                 Log.w(this,
195                         "Target PhoneAccount does not have BIND_TELECOM_CONNECTION_SERVICE for "
196                                 + "attempt: %s", attempt);
197                 attemptNextPhoneAccount();
198                 return;
199             }
200         }
201 
202         if (mCallResponse != null && attempt != null) {
203             Log.i(this, "Trying attempt %s", attempt);
204             PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
205             mService = mRepository.getService(phoneAccount.getComponentName(),
206                     phoneAccount.getUserHandle());
207             if (mService == null) {
208                 Log.i(this, "Found no connection service for attempt %s", attempt);
209                 attemptNextPhoneAccount();
210             } else {
211                 mConnectionAttempt++;
212                 mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
213                 mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
214                 mCall.setConnectionService(mService);
215                 setTimeoutIfNeeded(mService, attempt);
216                 if (mCall.isIncoming()) {
217                     mService.createConnection(mCall, CreateConnectionProcessor.this);
218                 } else {
219                     // Start to create the connection for outgoing call after the ConnectionService
220                     // of the call has gained the focus.
221                     mCall.getConnectionServiceFocusManager().requestFocus(
222                             mCall,
223                             new CallsManager.RequestCallback(new CallsManager.PendingAction() {
224                                 @Override
225                                 public void performAction() {
226                                     Log.d(this, "perform create connection");
227                                     mService.createConnection(
228                                             mCall,
229                                             CreateConnectionProcessor.this);
230                                 }
231                             }));
232 
233                 }
234             }
235         } else {
236             Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
237             DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ?
238                     mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR);
239             notifyCallConnectionFailure(disconnectCause);
240         }
241     }
242 
setTimeoutIfNeeded(ConnectionServiceWrapper service, CallAttemptRecord attempt)243     private void setTimeoutIfNeeded(ConnectionServiceWrapper service, CallAttemptRecord attempt) {
244         clearTimeout();
245 
246         CreateConnectionTimeout timeout = new CreateConnectionTimeout(
247                 mContext, mPhoneAccountRegistrar, service, mCall);
248         if (timeout.isTimeoutNeededForCall(getConnectionServices(mAttemptRecords),
249                 attempt.connectionManagerPhoneAccount)) {
250             mTimeout = timeout;
251             timeout.registerTimeout();
252         }
253     }
254 
clearTimeout()255     private void clearTimeout() {
256         if (mTimeout != null) {
257             mTimeout.unregisterTimeout();
258             mTimeout = null;
259         }
260     }
261 
shouldSetConnectionManager()262     private boolean shouldSetConnectionManager() {
263         if (mAttemptRecords.size() == 0) {
264             return false;
265         }
266 
267         if (mAttemptRecords.size() > 1) {
268             Log.d(this, "shouldSetConnectionManager, error, mAttemptRecords should not have more "
269                     + "than 1 record");
270             return false;
271         }
272 
273         PhoneAccountHandle connectionManager =
274                 mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall);
275         if (connectionManager == null) {
276             return false;
277         }
278 
279         PhoneAccountHandle targetPhoneAccountHandle = mAttemptRecords.get(0).targetPhoneAccount;
280         if (Objects.equals(connectionManager, targetPhoneAccountHandle)) {
281             return false;
282         }
283 
284         // Connection managers are only allowed to manage SIM subscriptions.
285         // TODO: Should this really be checking the "calling user" test for phone account?
286         PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar
287                 .getPhoneAccountUnchecked(targetPhoneAccountHandle);
288         if (targetPhoneAccount == null) {
289             Log.d(this, "shouldSetConnectionManager, phone account not found");
290             return false;
291         }
292         boolean isSimSubscription = (targetPhoneAccount.getCapabilities() &
293                 PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0;
294         if (!isSimSubscription) {
295             return false;
296         }
297 
298         return true;
299     }
300 
301     // If there exists a registered connection manager then use it.
adjustAttemptsForConnectionManager()302     private void adjustAttemptsForConnectionManager() {
303         if (shouldSetConnectionManager()) {
304             CallAttemptRecord record = new CallAttemptRecord(
305                     mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall),
306                     mAttemptRecords.get(0).targetPhoneAccount);
307             Log.v(this, "setConnectionManager, changing %s -> %s", mAttemptRecords.get(0), record);
308             mAttemptRecords.add(0, record);
309         } else {
310             Log.v(this, "setConnectionManager, not changing");
311         }
312     }
313 
314     // If we are possibly attempting to call a local emergency number, ensure that the
315     // plain PSTN connection services are listed, and nothing else.
adjustAttemptsForEmergency(PhoneAccountHandle preferredPAH)316     private void adjustAttemptsForEmergency(PhoneAccountHandle preferredPAH) {
317         if (mCall.isEmergencyCall()) {
318             Log.i(this, "Emergency number detected");
319             mAttemptRecords.clear();
320             // Phone accounts in profile do not handle emergency call, use phone accounts in
321             // current user.
322             List<PhoneAccount> allAccounts = mPhoneAccountRegistrar
323                     .getAllPhoneAccountsOfCurrentUser();
324 
325             if (allAccounts.isEmpty()) {
326                 // If the list of phone accounts is empty at this point, it means Telephony hasn't
327                 // registered any phone accounts yet. Add a fallback emergency phone account so
328                 // that emergency calls can still go through. We create a new ArrayLists here just
329                 // in case the implementation of PhoneAccountRegistrar ever returns an unmodifiable
330                 // list.
331                 allAccounts = new ArrayList<PhoneAccount>();
332                 allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount());
333             }
334 
335             // First, possibly add the SIM phone account that the user prefers
336             PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
337                     preferredPAH);
338             if (preferredPA != null &&
339                     preferredPA.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) &&
340                     preferredPA.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
341                 Log.i(this, "Will try PSTN account %s for emergency",
342                         preferredPA.getAccountHandle());
343                 mAttemptRecords.add(new CallAttemptRecord(preferredPAH, preferredPAH));
344             }
345 
346             // Next, add all SIM phone accounts which can place emergency calls.
347             TelephonyUtil.sortSimPhoneAccounts(mContext, allAccounts);
348 
349             // If preferredPA already has an emergency PhoneAccount, do not add others since the
350             // emergency call be redialed in Telephony.
351             if (mAttemptRecords.isEmpty()) {
352                 for (PhoneAccount phoneAccount : allAccounts) {
353                     if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
354                             && phoneAccount.hasCapabilities(
355                             PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
356                         PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
357                         Log.i(this, "Will try PSTN account %s for emergency", phoneAccountHandle);
358                         mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
359                                 phoneAccountHandle));
360                         // Add only one emergency SIM PhoneAccount to the attempt list.
361                         break;
362                     }
363                 }
364             }
365 
366             // Next, add the connection manager account as a backup if it can place emergency calls.
367             PhoneAccountHandle callManagerHandle =
368                     mPhoneAccountRegistrar.getSimCallManagerOfCurrentUser();
369             if (callManagerHandle != null) {
370                 // TODO: Should this really be checking the "calling user" test for phone account?
371                 PhoneAccount callManager = mPhoneAccountRegistrar
372                         .getPhoneAccountUnchecked(callManagerHandle);
373                 if (callManager != null && callManager.hasCapabilities(
374                         PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
375                     CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle,
376                             mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
377                                     mCall.getHandle() == null
378                                             ? null : mCall.getHandle().getScheme()));
379                     if (!mAttemptRecords.contains(callAttemptRecord)) {
380                         Log.i(this, "Will try Connection Manager account %s for emergency",
381                                 callManager);
382                         mAttemptRecords.add(callAttemptRecord);
383                     }
384                 }
385             }
386         }
387     }
388 
389     /** Returns all connection services used by the call attempt records. */
getConnectionServices( List<CallAttemptRecord> records)390     private static Collection<PhoneAccountHandle> getConnectionServices(
391             List<CallAttemptRecord> records) {
392         HashSet<PhoneAccountHandle> result = new HashSet<>();
393         for (CallAttemptRecord record : records) {
394             result.add(record.connectionManagerPhoneAccount);
395         }
396         return result;
397     }
398 
399 
notifyCallConnectionFailure(DisconnectCause errorDisconnectCause)400     private void notifyCallConnectionFailure(DisconnectCause errorDisconnectCause) {
401         if (mCallResponse != null) {
402             clearTimeout();
403             mCallResponse.handleCreateConnectionFailure(errorDisconnectCause);
404             mCallResponse = null;
405             mCall.clearConnectionService();
406         }
407     }
408 
409     @Override
handleCreateConnectionSuccess( CallIdMapper idMapper, ParcelableConnection connection)410     public void handleCreateConnectionSuccess(
411             CallIdMapper idMapper,
412             ParcelableConnection connection) {
413         if (mCallResponse == null) {
414             // Nobody is listening for this connection attempt any longer; ask the responsible
415             // ConnectionService to tear down any resources associated with the call
416             mService.abort(mCall);
417         } else {
418             // Success -- share the good news and remember that we are no longer interested
419             // in hearing about any more attempts
420             mCallResponse.handleCreateConnectionSuccess(idMapper, connection);
421             mCallResponse = null;
422             // If there's a timeout running then don't clear it. The timeout can be triggered
423             // after the call has successfully been created but before it has become active.
424         }
425     }
426 
shouldFailCallIfConnectionManagerFails(DisconnectCause cause)427     private boolean shouldFailCallIfConnectionManagerFails(DisconnectCause cause) {
428         // Connection Manager does not exist or does not match registered Connection Manager
429         // Since Connection manager is a proxy for SIM, fall back to SIM
430         PhoneAccountHandle handle = mCall.getConnectionManagerPhoneAccount();
431         if (handle == null || !handle.equals(mPhoneAccountRegistrar.getSimCallManagerFromCall(
432                 mCall))) {
433             return false;
434         }
435 
436         // The Call's Connection Service does not exist
437         ConnectionServiceWrapper connectionManager = mCall.getConnectionService();
438         if (connectionManager == null) {
439             return true;
440         }
441 
442         // In this case, fall back to a sim because connection manager declined
443         if (cause.getCode() == DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED) {
444             Log.d(CreateConnectionProcessor.this, "Connection manager declined to handle the "
445                     + "call, falling back to not using a connection manager");
446             return false;
447         }
448 
449         if (!connectionManager.isServiceValid("createConnection")) {
450             Log.d(CreateConnectionProcessor.this, "Connection manager unbound while trying "
451                     + "create a connection, falling back to not using a connection manager");
452             return false;
453         }
454 
455         // Do not fall back from connection manager and simply fail call if the failure reason is
456         // other
457         Log.d(CreateConnectionProcessor.this, "Connection Manager denied call with the following " +
458                 "error: " + cause.getReason() + ". Not falling back to SIM.");
459         return true;
460     }
461 
462     @Override
handleCreateConnectionFailure(DisconnectCause errorDisconnectCause)463     public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) {
464         // Failure of some sort; record the reasons for failure and try again if possible
465         Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause);
466         if (shouldFailCallIfConnectionManagerFails(errorDisconnectCause)) {
467             notifyCallConnectionFailure(errorDisconnectCause);
468             return;
469         }
470         mLastErrorDisconnectCause = errorDisconnectCause;
471         attemptNextPhoneAccount();
472     }
473 }
474