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