1 /*
2  * Copyright (C) 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.services.telephony;
18 
19 import android.content.ActivityNotFoundException;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.telecom.Conference;
26 import android.telecom.Connection;
27 import android.telecom.ConnectionRequest;
28 import android.telecom.ConnectionService;
29 import android.telecom.DisconnectCause;
30 import android.telecom.PhoneAccount;
31 import android.telecom.PhoneAccountHandle;
32 import android.telecom.TelecomManager;
33 import android.telecom.VideoProfile;
34 import android.telephony.CarrierConfigManager;
35 import android.telephony.PhoneNumberUtils;
36 import android.telephony.ServiceState;
37 import android.telephony.SubscriptionManager;
38 import android.telephony.TelephonyManager;
39 import android.text.TextUtils;
40 
41 import com.android.internal.telephony.Call;
42 import com.android.internal.telephony.CallStateException;
43 import com.android.internal.telephony.IccCard;
44 import com.android.internal.telephony.IccCardConstants;
45 import com.android.internal.telephony.Phone;
46 import com.android.internal.telephony.PhoneConstants;
47 import com.android.internal.telephony.PhoneFactory;
48 import com.android.internal.telephony.SubscriptionController;
49 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
50 import com.android.internal.telephony.imsphone.ImsPhone;
51 import com.android.phone.MMIDialogActivity;
52 import com.android.phone.PhoneUtils;
53 import com.android.phone.R;
54 
55 import java.util.ArrayList;
56 import java.util.List;
57 import java.util.regex.Pattern;
58 
59 /**
60  * Service for making GSM and CDMA connections.
61  */
62 public class TelephonyConnectionService extends ConnectionService {
63 
64     // If configured, reject attempts to dial numbers matching this pattern.
65     private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
66             Pattern.compile("\\*228[0-9]{0,2}");
67 
68     private final TelephonyConferenceController mTelephonyConferenceController =
69             new TelephonyConferenceController(this);
70     private final CdmaConferenceController mCdmaConferenceController =
71             new CdmaConferenceController(this);
72     private final ImsConferenceController mImsConferenceController =
73             new ImsConferenceController(this);
74 
75     private ComponentName mExpectedComponentName = null;
76     private EmergencyCallHelper mEmergencyCallHelper;
77     private EmergencyTonePlayer mEmergencyTonePlayer;
78 
79     /**
80      * A listener to actionable events specific to the TelephonyConnection.
81      */
82     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
83             new TelephonyConnection.TelephonyConnectionListener() {
84         @Override
85         public void onOriginalConnectionConfigured(TelephonyConnection c) {
86             addConnectionToConferenceController(c);
87         }
88     };
89 
90     @Override
onCreate()91     public void onCreate() {
92         super.onCreate();
93         mExpectedComponentName = new ComponentName(this, this.getClass());
94         mEmergencyTonePlayer = new EmergencyTonePlayer(this);
95         TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
96     }
97 
98     @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)99     public Connection onCreateOutgoingConnection(
100             PhoneAccountHandle connectionManagerPhoneAccount,
101             final ConnectionRequest request) {
102         Log.i(this, "onCreateOutgoingConnection, request: " + request);
103 
104         Uri handle = request.getAddress();
105         if (handle == null) {
106             Log.d(this, "onCreateOutgoingConnection, handle is null");
107             return Connection.createFailedConnection(
108                     DisconnectCauseUtil.toTelecomDisconnectCause(
109                             android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
110                             "No phone number supplied"));
111         }
112 
113         String scheme = handle.getScheme();
114         final String number;
115         if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
116             // TODO: We don't check for SecurityException here (requires
117             // CALL_PRIVILEGED permission).
118             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
119             if (phone == null) {
120                 Log.d(this, "onCreateOutgoingConnection, phone is null");
121                 return Connection.createFailedConnection(
122                         DisconnectCauseUtil.toTelecomDisconnectCause(
123                                 android.telephony.DisconnectCause.OUT_OF_SERVICE,
124                                 "Phone is null"));
125             }
126             number = phone.getVoiceMailNumber();
127             if (TextUtils.isEmpty(number)) {
128                 Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
129                 return Connection.createFailedConnection(
130                         DisconnectCauseUtil.toTelecomDisconnectCause(
131                                 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
132                                 "Voicemail scheme provided but no voicemail number set."));
133             }
134 
135             // Convert voicemail: to tel:
136             handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
137         } else {
138             if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
139                 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
140                 return Connection.createFailedConnection(
141                         DisconnectCauseUtil.toTelecomDisconnectCause(
142                                 android.telephony.DisconnectCause.INVALID_NUMBER,
143                                 "Handle scheme is not type tel"));
144             }
145 
146             number = handle.getSchemeSpecificPart();
147             if (TextUtils.isEmpty(number)) {
148                 Log.d(this, "onCreateOutgoingConnection, unable to parse number");
149                 return Connection.createFailedConnection(
150                         DisconnectCauseUtil.toTelecomDisconnectCause(
151                                 android.telephony.DisconnectCause.INVALID_NUMBER,
152                                 "Unable to parse number"));
153             }
154 
155             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
156             if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) {
157                 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number
158                 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and
159                 // when dialed could lock LTE SIMs to 3G if not prohibited..
160                 boolean disableActivation = false;
161                 CarrierConfigManager cfgManager = (CarrierConfigManager)
162                         phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
163                 if (cfgManager != null) {
164                     disableActivation = cfgManager.getConfigForSubId(phone.getSubId())
165                             .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL);
166                 }
167 
168                 if (disableActivation) {
169                     return Connection.createFailedConnection(
170                             DisconnectCauseUtil.toTelecomDisconnectCause(
171                                     android.telephony.DisconnectCause
172                                             .CDMA_ALREADY_ACTIVATED,
173                                     "Tried to dial *228"));
174                 }
175             }
176         }
177 
178         boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(this, number);
179 
180         // Get the right phone object from the account data passed in.
181         final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
182         if (phone == null) {
183             final Context context = getApplicationContext();
184             if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) {
185                 // Check SIM card state before the outgoing call.
186                 // Start the SIM unlock activity if PIN_REQUIRED.
187                 final Phone defaultPhone = PhoneFactory.getDefaultPhone();
188                 final IccCard icc = defaultPhone.getIccCard();
189                 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN;
190                 if (icc != null) {
191                     simState = icc.getState();
192                 }
193                 if (simState == IccCardConstants.State.PIN_REQUIRED) {
194                     final String simUnlockUiPackage = context.getResources().getString(
195                             R.string.config_simUnlockUiPackage);
196                     final String simUnlockUiClass = context.getResources().getString(
197                             R.string.config_simUnlockUiClass);
198                     if (simUnlockUiPackage != null && simUnlockUiClass != null) {
199                         Intent simUnlockIntent = new Intent().setComponent(new ComponentName(
200                                 simUnlockUiPackage, simUnlockUiClass));
201                         simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
202                         try {
203                             context.startActivity(simUnlockIntent);
204                         } catch (ActivityNotFoundException exception) {
205                             Log.e(this, exception, "Unable to find SIM unlock UI activity.");
206                         }
207                     }
208                     return Connection.createFailedConnection(
209                             DisconnectCauseUtil.toTelecomDisconnectCause(
210                                     android.telephony.DisconnectCause.OUT_OF_SERVICE,
211                                     "SIM_STATE_PIN_REQUIRED"));
212                 }
213             }
214 
215             Log.d(this, "onCreateOutgoingConnection, phone is null");
216             return Connection.createFailedConnection(
217                     DisconnectCauseUtil.toTelecomDisconnectCause(
218                             android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null"));
219         }
220 
221         // Check both voice & data RAT to enable normal CS call,
222         // when voice RAT is OOS but Data RAT is present.
223         int state = phone.getServiceState().getState();
224         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
225             if (phone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE) {
226                 state = phone.getServiceState().getDataRegState();
227             }
228         }
229         boolean useEmergencyCallHelper = false;
230 
231         // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
232         // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
233         if (!isEmergencyNumber && phone.isInEcm()) {
234             boolean allowNonEmergencyCalls = true;
235             CarrierConfigManager cfgManager = (CarrierConfigManager)
236                     phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
237             if (cfgManager != null) {
238                 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId())
239                         .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL);
240             }
241 
242             if (!allowNonEmergencyCalls) {
243                 return Connection.createFailedConnection(
244                         DisconnectCauseUtil.toTelecomDisconnectCause(
245                                 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY,
246                                 "Cannot make non-emergency call in ECM mode."
247                         ));
248             }
249         }
250 
251         if (isEmergencyNumber) {
252             if (!phone.isRadioOn()) {
253                 useEmergencyCallHelper = true;
254             }
255         } else {
256             switch (state) {
257                 case ServiceState.STATE_IN_SERVICE:
258                 case ServiceState.STATE_EMERGENCY_ONLY:
259                     break;
260                 case ServiceState.STATE_OUT_OF_SERVICE:
261                     if (phone.isUtEnabled() && number.endsWith("#")) {
262                         Log.d(this, "onCreateOutgoingConnection dial for UT");
263                         break;
264                     } else {
265                         return Connection.createFailedConnection(
266                                 DisconnectCauseUtil.toTelecomDisconnectCause(
267                                         android.telephony.DisconnectCause.OUT_OF_SERVICE,
268                                         "ServiceState.STATE_OUT_OF_SERVICE"));
269                     }
270                 case ServiceState.STATE_POWER_OFF:
271                     return Connection.createFailedConnection(
272                             DisconnectCauseUtil.toTelecomDisconnectCause(
273                                     android.telephony.DisconnectCause.POWER_OFF,
274                                     "ServiceState.STATE_POWER_OFF"));
275                 default:
276                     Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
277                     return Connection.createFailedConnection(
278                             DisconnectCauseUtil.toTelecomDisconnectCause(
279                                     android.telephony.DisconnectCause.OUTGOING_FAILURE,
280                                     "Unknown service state " + state));
281             }
282         }
283 
284         final Context context = getApplicationContext();
285         if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled(context) &&
286                 !isEmergencyNumber) {
287             return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
288                     android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED));
289         }
290 
291         // Check for additional limits on CDMA phones.
292         final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone);
293         if (failedConnection != null) {
294             return failedConnection;
295         }
296 
297         final TelephonyConnection connection =
298                 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
299                         request.getTelecomCallId(), request.getAddress());
300         if (connection == null) {
301             return Connection.createFailedConnection(
302                     DisconnectCauseUtil.toTelecomDisconnectCause(
303                             android.telephony.DisconnectCause.OUTGOING_FAILURE,
304                             "Invalid phone type"));
305         }
306         connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
307         connection.setInitializing();
308         connection.setVideoState(request.getVideoState());
309 
310         if (useEmergencyCallHelper) {
311             if (mEmergencyCallHelper == null) {
312                 mEmergencyCallHelper = new EmergencyCallHelper(this);
313             }
314             mEmergencyCallHelper.startTurnOnRadioSequence(phone,
315                     new EmergencyCallHelper.Callback() {
316                         @Override
317                         public void onComplete(boolean isRadioReady) {
318                             if (connection.getState() == Connection.STATE_DISCONNECTED) {
319                                 // If the connection has already been disconnected, do nothing.
320                             } else if (isRadioReady) {
321                                 connection.setInitialized();
322                                 placeOutgoingConnection(connection, phone, request);
323                             } else {
324                                 Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
325                                 connection.setDisconnected(
326                                         DisconnectCauseUtil.toTelecomDisconnectCause(
327                                                 android.telephony.DisconnectCause.POWER_OFF,
328                                                 "Failed to turn on radio."));
329                                 connection.destroy();
330                             }
331                         }
332                     });
333 
334         } else {
335             placeOutgoingConnection(connection, phone, request);
336         }
337 
338         return connection;
339     }
340 
341     @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)342     public Connection onCreateIncomingConnection(
343             PhoneAccountHandle connectionManagerPhoneAccount,
344             ConnectionRequest request) {
345         Log.i(this, "onCreateIncomingConnection, request: " + request);
346         // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
347         // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
348         PhoneAccountHandle accountHandle = request.getAccountHandle();
349         boolean isEmergency = false;
350         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
351                 accountHandle.getId())) {
352             Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " +
353                     "Treat as an Emergency Call.");
354             isEmergency = true;
355         }
356         Phone phone = getPhoneForAccount(accountHandle, isEmergency);
357         if (phone == null) {
358             return Connection.createFailedConnection(
359                     DisconnectCauseUtil.toTelecomDisconnectCause(
360                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
361                             "Phone is null"));
362         }
363 
364         Call call = phone.getRingingCall();
365         if (!call.getState().isRinging()) {
366             Log.i(this, "onCreateIncomingConnection, no ringing call");
367             return Connection.createFailedConnection(
368                     DisconnectCauseUtil.toTelecomDisconnectCause(
369                             android.telephony.DisconnectCause.INCOMING_MISSED,
370                             "Found no ringing call"));
371         }
372 
373         com.android.internal.telephony.Connection originalConnection =
374                 call.getState() == Call.State.WAITING ?
375                     call.getLatestConnection() : call.getEarliestConnection();
376         if (isOriginalConnectionKnown(originalConnection)) {
377             Log.i(this, "onCreateIncomingConnection, original connection already registered");
378             return Connection.createCanceledConnection();
379         }
380 
381         Connection connection =
382                 createConnectionFor(phone, originalConnection, false /* isOutgoing */,
383                         request.getAccountHandle(), request.getTelecomCallId(),
384                         request.getAddress());
385         if (connection == null) {
386             return Connection.createCanceledConnection();
387         } else {
388             return connection;
389         }
390     }
391 
392     @Override
triggerConferenceRecalculate()393     public void triggerConferenceRecalculate() {
394         if (mTelephonyConferenceController.shouldRecalculate()) {
395             mTelephonyConferenceController.recalculate();
396         }
397     }
398 
399     @Override
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)400     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
401             ConnectionRequest request) {
402         Log.i(this, "onCreateUnknownConnection, request: " + request);
403         // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's
404         // Emergency PhoneAccount
405         PhoneAccountHandle accountHandle = request.getAccountHandle();
406         boolean isEmergency = false;
407         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
408                 accountHandle.getId())) {
409             Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " +
410                     "Treat as an Emergency Call.");
411             isEmergency = true;
412         }
413         Phone phone = getPhoneForAccount(accountHandle, isEmergency);
414         if (phone == null) {
415             return Connection.createFailedConnection(
416                     DisconnectCauseUtil.toTelecomDisconnectCause(
417                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
418                             "Phone is null"));
419         }
420         Bundle extras = request.getExtras();
421 
422         final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
423 
424         // Handle the case where an unknown connection has an IMS external call ID specified; we can
425         // skip the rest of the guesswork and just grad that unknown call now.
426         if (phone.getImsPhone() != null && extras != null &&
427                 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) {
428 
429             ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
430             ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
431             int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
432                     -1);
433 
434             if (externalCallTracker != null) {
435                 com.android.internal.telephony.Connection connection =
436                         externalCallTracker.getConnectionById(externalCallId);
437 
438                 if (connection != null) {
439                     allConnections.add(connection);
440                 }
441             }
442         }
443 
444         if (allConnections.isEmpty()) {
445             final Call ringingCall = phone.getRingingCall();
446             if (ringingCall.hasConnections()) {
447                 allConnections.addAll(ringingCall.getConnections());
448             }
449             final Call foregroundCall = phone.getForegroundCall();
450             if ((foregroundCall.getState() != Call.State.DISCONNECTED)
451                     && (foregroundCall.hasConnections())) {
452                 allConnections.addAll(foregroundCall.getConnections());
453             }
454             if (phone.getImsPhone() != null) {
455                 final Call imsFgCall = phone.getImsPhone().getForegroundCall();
456                 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall
457                         .hasConnections()) {
458                     allConnections.addAll(imsFgCall.getConnections());
459                 }
460             }
461             final Call backgroundCall = phone.getBackgroundCall();
462             if (backgroundCall.hasConnections()) {
463                 allConnections.addAll(phone.getBackgroundCall().getConnections());
464             }
465         }
466 
467         com.android.internal.telephony.Connection unknownConnection = null;
468         for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
469             if (!isOriginalConnectionKnown(telephonyConnection)) {
470                 unknownConnection = telephonyConnection;
471                 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection);
472                 break;
473             }
474         }
475 
476         if (unknownConnection == null) {
477             Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
478             return Connection.createCanceledConnection();
479         }
480 
481         TelephonyConnection connection =
482                 createConnectionFor(phone, unknownConnection,
483                         !unknownConnection.isIncoming() /* isOutgoing */,
484                         request.getAccountHandle(), request.getTelecomCallId(),
485                         request.getAddress());
486 
487         if (connection == null) {
488             return Connection.createCanceledConnection();
489         } else {
490             connection.updateState();
491             return connection;
492         }
493     }
494 
495     @Override
onConference(Connection connection1, Connection connection2)496     public void onConference(Connection connection1, Connection connection2) {
497         if (connection1 instanceof TelephonyConnection &&
498                 connection2 instanceof TelephonyConnection) {
499             ((TelephonyConnection) connection1).performConference(
500                 (TelephonyConnection) connection2);
501         }
502 
503     }
504 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)505     private void placeOutgoingConnection(
506             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
507         String number = connection.getAddress().getSchemeSpecificPart();
508 
509         com.android.internal.telephony.Connection originalConnection;
510         try {
511             originalConnection =
512                     phone.dial(number, null, request.getVideoState(), request.getExtras());
513         } catch (CallStateException e) {
514             Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
515             int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
516             if (e.getError() == CallStateException.ERROR_DISCONNECTED) {
517                 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE;
518             }
519             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
520                     cause, e.getMessage()));
521             return;
522         }
523 
524         if (originalConnection == null) {
525             int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
526             // On GSM phones, null connection means that we dialed an MMI code
527             if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
528                 Log.d(this, "dialed MMI code");
529                 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
530                 final Intent intent = new Intent(this, MMIDialogActivity.class);
531                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
532                         Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
533                 startActivity(intent);
534             }
535             Log.d(this, "placeOutgoingConnection, phone.dial returned null");
536             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
537                     telephonyDisconnectCause, "Connection is null"));
538         } else {
539             connection.setOriginalConnection(originalConnection);
540         }
541     }
542 
createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, Uri address)543     private TelephonyConnection createConnectionFor(
544             Phone phone,
545             com.android.internal.telephony.Connection originalConnection,
546             boolean isOutgoing,
547             PhoneAccountHandle phoneAccountHandle,
548             String telecomCallId,
549             Uri address) {
550         TelephonyConnection returnConnection = null;
551         int phoneType = phone.getPhoneType();
552         if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
553             returnConnection = new GsmConnection(originalConnection, telecomCallId);
554         } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
555             boolean allowsMute = allowsMute(phone);
556             returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer,
557                     allowsMute, isOutgoing, telecomCallId);
558         }
559         if (returnConnection != null) {
560             // Listen to Telephony specific callbacks from the connection
561             returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
562             returnConnection.setVideoPauseSupported(
563                     TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
564                             phoneAccountHandle));
565             boolean isEmergencyCall = (address != null && PhoneNumberUtils.isEmergencyNumber(
566                     address.getSchemeSpecificPart()));
567             returnConnection.setConferenceSupported(!isEmergencyCall
568                     && TelecomAccountRegistry.getInstance(this).isMergeCallSupported(
569                             phoneAccountHandle));
570         }
571         return returnConnection;
572     }
573 
isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)574     private boolean isOriginalConnectionKnown(
575             com.android.internal.telephony.Connection originalConnection) {
576         for (Connection connection : getAllConnections()) {
577             if (connection instanceof TelephonyConnection) {
578                 TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
579                 if (telephonyConnection.getOriginalConnection() == originalConnection) {
580                     return true;
581                 }
582             }
583         }
584         return false;
585     }
586 
getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency)587     private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
588         Phone chosenPhone = null;
589         int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
590         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
591             int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
592             chosenPhone = PhoneFactory.getPhone(phoneId);
593         }
594         // If this is an emergency call and the phone we originally planned to make this call
595         // with is not in service or was invalid, try to find one that is in service, using the
596         // default as a last chance backup.
597         if (isEmergency && (chosenPhone == null || ServiceState.STATE_IN_SERVICE != chosenPhone
598                 .getServiceState().getState())) {
599             Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
600                     + "or invalid for emergency call.", accountHandle);
601             chosenPhone = getFirstPhoneForEmergencyCall();
602             Log.d(this, "getPhoneForAccount: using subId: " +
603                     (chosenPhone == null ? "null" : chosenPhone.getSubId()));
604         }
605         return chosenPhone;
606     }
607 
608     /**
609      * Retrieves the most sensible Phone to use for an emergency call using the following Priority
610      *  list (for multi-SIM devices):
611      *  1) The User's SIM preference for Voice calling
612      *  2) The First Phone that is currently IN_SERVICE or is available for emergency calling
613      *  3) The First Phone that has a SIM card in it (Starting from Slot 0...N)
614      *  4) The Default Phone (Currently set as Slot 0)
615      */
getFirstPhoneForEmergencyCall()616     private Phone getFirstPhoneForEmergencyCall() {
617         Phone firstPhoneWithSim = null;
618 
619         // 1)
620         int phoneId = SubscriptionManager.getDefaultVoicePhoneId();
621         if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
622             Phone defaultPhone = PhoneFactory.getPhone(phoneId);
623             if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) {
624                 return defaultPhone;
625             }
626         }
627 
628         for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
629             Phone phone = PhoneFactory.getPhone(i);
630             if (phone == null)
631                 continue;
632             // 2)
633             if (isAvailableForEmergencyCalls(phone)) {
634                 // the slot has the radio on & state is in service.
635                 Log.d(this, "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i);
636                 return phone;
637             }
638             // 3)
639             if (firstPhoneWithSim == null && TelephonyManager.getDefault().hasIccCard(i)) {
640                 // The slot has a SIM card inserted, but is not in service, so keep track of this
641                 // Phone. Do not return because we want to make sure that none of the other Phones
642                 // are in service (because that is always faster).
643                 Log.d(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" + i);
644                 firstPhoneWithSim = phone;
645             }
646         }
647         // 4)
648         if (firstPhoneWithSim == null) {
649             // No SIMs inserted, get the default.
650             Log.d(this, "getFirstPhoneForEmergencyCall, return default phone");
651             return PhoneFactory.getDefaultPhone();
652         } else {
653             return firstPhoneWithSim;
654         }
655     }
656 
657     /**
658      * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
659      */
isAvailableForEmergencyCalls(Phone phone)660     private boolean isAvailableForEmergencyCalls(Phone phone) {
661         return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() ||
662                 phone.getServiceState().isEmergencyOnly();
663     }
664 
665     /**
666      * Determines if the connection should allow mute.
667      *
668      * @param phone The current phone.
669      * @return {@code True} if the connection should allow mute.
670      */
allowsMute(Phone phone)671     private boolean allowsMute(Phone phone) {
672         // For CDMA phones, check if we are in Emergency Callback Mode (ECM).  Mute is disallowed
673         // in ECM mode.
674         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
675             if (phone.isInEcm()) {
676                 return false;
677             }
678         }
679 
680         return true;
681     }
682 
683     @Override
removeConnection(Connection connection)684     public void removeConnection(Connection connection) {
685         super.removeConnection(connection);
686         if (connection instanceof TelephonyConnection) {
687             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
688             telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
689         }
690     }
691 
692     /**
693      * When a {@link TelephonyConnection} has its underlying original connection configured,
694      * we need to add it to the correct conference controller.
695      *
696      * @param connection The connection to be added to the controller
697      */
addConnectionToConferenceController(TelephonyConnection connection)698     public void addConnectionToConferenceController(TelephonyConnection connection) {
699         // TODO: Do we need to handle the case of the original connection changing
700         // and triggering this callback multiple times for the same connection?
701         // If that is the case, we might want to remove this connection from all
702         // conference controllers first before re-adding it.
703         if (connection.isImsConnection()) {
704             Log.d(this, "Adding IMS connection to conference controller: " + connection);
705             mImsConferenceController.add(connection);
706         } else {
707             int phoneType = connection.getCall().getPhone().getPhoneType();
708             if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
709                 Log.d(this, "Adding GSM connection to conference controller: " + connection);
710                 mTelephonyConferenceController.add(connection);
711             } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA &&
712                     connection instanceof CdmaConnection) {
713                 Log.d(this, "Adding CDMA connection to conference controller: " + connection);
714                 mCdmaConferenceController.add((CdmaConnection)connection);
715             }
716             Log.d(this, "Removing connection from IMS conference controller: " + connection);
717             mImsConferenceController.remove(connection);
718         }
719     }
720 
721     /**
722      * Create a new CDMA connection. CDMA connections have additional limitations when creating
723      * additional calls which are handled in this method.  Specifically, CDMA has a "FLASH" command
724      * that can be used for three purposes: merging a call, swapping unmerged calls, and adding
725      * a new outgoing call. The function of the flash command depends on the context of the current
726      * set of calls. This method will prevent an outgoing call from being made if it is not within
727      * the right circumstances to support adding a call.
728      */
checkAdditionalOutgoingCallLimits(Phone phone)729     private Connection checkAdditionalOutgoingCallLimits(Phone phone) {
730         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
731             // Check to see if any CDMA conference calls exist, and if they do, check them for
732             // limitations.
733             for (Conference conference : getAllConferences()) {
734                 if (conference instanceof CdmaConference) {
735                     CdmaConference cdmaConf = (CdmaConference) conference;
736 
737                     // If the CDMA conference has not been merged, add-call will not work, so fail
738                     // this request to add a call.
739                     if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
740                         return Connection.createFailedConnection(new DisconnectCause(
741                                     DisconnectCause.RESTRICTED,
742                                     null,
743                                     getResources().getString(R.string.callFailed_cdma_call_limit),
744                                     "merge-capable call exists, prevent flash command."));
745                     }
746                 }
747             }
748         }
749 
750         return null; // null means nothing went wrong, and call should continue.
751     }
752 
isTtyModeEnabled(Context context)753     private boolean isTtyModeEnabled(Context context) {
754         return (android.provider.Settings.Secure.getInt(
755                 context.getContentResolver(),
756                 android.provider.Settings.Secure.PREFERRED_TTY_MODE,
757                 TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
758     }
759 }
760