Home | History | Annotate | Download | only in telecom
      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