• 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.provider.Settings;
26 import android.telecom.Conference;
27 import android.telecom.Connection;
28 import android.telecom.ConnectionRequest;
29 import android.telecom.ConnectionService;
30 import android.telecom.DisconnectCause;
31 import android.telecom.PhoneAccount;
32 import android.telecom.PhoneAccountHandle;
33 import android.telecom.TelecomManager;
34 import android.telecom.VideoProfile;
35 import android.telephony.CarrierConfigManager;
36 import android.telephony.PhoneNumberUtils;
37 import android.telephony.RadioAccessFamily;
38 import android.telephony.ServiceState;
39 import android.telephony.SubscriptionManager;
40 import android.telephony.TelephonyManager;
41 import android.text.TextUtils;
42 import android.util.Pair;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.internal.telephony.Call;
46 import com.android.internal.telephony.CallStateException;
47 import com.android.internal.telephony.GsmCdmaPhone;
48 import com.android.internal.telephony.IccCard;
49 import com.android.internal.telephony.IccCardConstants;
50 import com.android.internal.telephony.Phone;
51 import com.android.internal.telephony.PhoneConstants;
52 import com.android.internal.telephony.PhoneFactory;
53 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
54 import com.android.internal.telephony.imsphone.ImsPhone;
55 import com.android.phone.MMIDialogActivity;
56 import com.android.phone.PhoneUtils;
57 import com.android.phone.R;
58 
59 import java.lang.ref.WeakReference;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.Collection;
63 import java.util.Collections;
64 import java.util.List;
65 import java.util.regex.Pattern;
66 
67 /**
68  * Service for making GSM and CDMA connections.
69  */
70 public class TelephonyConnectionService extends ConnectionService {
71 
72     // If configured, reject attempts to dial numbers matching this pattern.
73     private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
74             Pattern.compile("\\*228[0-9]{0,2}");
75 
76     private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy =
77             new TelephonyConnectionServiceProxy() {
78         @Override
79         public Collection<Connection> getAllConnections() {
80             return TelephonyConnectionService.this.getAllConnections();
81         }
82         @Override
83         public void addConference(TelephonyConference mTelephonyConference) {
84             TelephonyConnectionService.this.addConference(mTelephonyConference);
85         }
86         @Override
87         public void addConference(ImsConference mImsConference) {
88             TelephonyConnectionService.this.addConference(mImsConference);
89         }
90         @Override
91         public void removeConnection(Connection connection) {
92             TelephonyConnectionService.this.removeConnection(connection);
93         }
94         @Override
95         public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
96                                           Connection connection) {
97             TelephonyConnectionService.this
98                     .addExistingConnection(phoneAccountHandle, connection);
99         }
100         @Override
101         public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
102                 Connection connection, Conference conference) {
103             TelephonyConnectionService.this
104                     .addExistingConnection(phoneAccountHandle, connection, conference);
105         }
106         @Override
107         public void addConnectionToConferenceController(TelephonyConnection connection) {
108             TelephonyConnectionService.this.addConnectionToConferenceController(connection);
109         }
110     };
111 
112     private final TelephonyConferenceController mTelephonyConferenceController =
113             new TelephonyConferenceController(mTelephonyConnectionServiceProxy);
114     private final CdmaConferenceController mCdmaConferenceController =
115             new CdmaConferenceController(this);
116     private final ImsConferenceController mImsConferenceController =
117             new ImsConferenceController(TelecomAccountRegistry.getInstance(this),
118                     mTelephonyConnectionServiceProxy);
119 
120     private ComponentName mExpectedComponentName = null;
121     private EmergencyCallHelper mEmergencyCallHelper;
122     private EmergencyTonePlayer mEmergencyTonePlayer;
123 
124     // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
125     // already tried to connect with. There should be only one TelephonyConnection trying to place a
126     // call at one time. We also only access this cache from a TelephonyConnection that wishes to
127     // redial, so we use a WeakReference that will become stale once the TelephonyConnection is
128     // destroyed.
129     private Pair<WeakReference<TelephonyConnection>, List<Phone>> mEmergencyRetryCache;
130 
131     /**
132      * Keeps track of the status of a SIM slot.
133      */
134     private static class SlotStatus {
135         public int slotId;
136         // RAT capabilities
137         public int capabilities;
138         // By default, we will assume that the slots are not locked.
139         public boolean isLocked = false;
140 
SlotStatus(int slotId, int capabilities)141         public SlotStatus(int slotId, int capabilities) {
142             this.slotId = slotId;
143             this.capabilities = capabilities;
144         }
145     }
146 
147     // SubscriptionManager Proxy interface for testing
148     public interface SubscriptionManagerProxy {
getDefaultVoicePhoneId()149         int getDefaultVoicePhoneId();
getSimStateForSlotIdx(int slotId)150         int getSimStateForSlotIdx(int slotId);
getPhoneId(int subId)151         int getPhoneId(int subId);
152     }
153 
154     private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
155         @Override
156         public int getDefaultVoicePhoneId() {
157             return SubscriptionManager.getDefaultVoicePhoneId();
158         }
159 
160         @Override
161         public int getSimStateForSlotIdx(int slotId) {
162             return SubscriptionManager.getSimStateForSlotIndex(slotId);
163         }
164 
165         @Override
166         public int getPhoneId(int subId) {
167             return SubscriptionManager.getPhoneId(subId);
168         }
169     };
170 
171     // TelephonyManager Proxy interface for testing
172     public interface TelephonyManagerProxy {
getPhoneCount()173         int getPhoneCount();
hasIccCard(int slotId)174         boolean hasIccCard(int slotId);
175     }
176 
177     private TelephonyManagerProxy mTelephonyManagerProxy = new TelephonyManagerProxy() {
178         private final TelephonyManager sTelephonyManager = TelephonyManager.getDefault();
179 
180         @Override
181         public int getPhoneCount() {
182             return sTelephonyManager.getPhoneCount();
183         }
184 
185         @Override
186         public boolean hasIccCard(int slotId) {
187             return sTelephonyManager.hasIccCard(slotId);
188         }
189     };
190 
191     //PhoneFactory proxy interface for testing
192     public interface PhoneFactoryProxy {
getPhone(int index)193         Phone getPhone(int index);
getDefaultPhone()194         Phone getDefaultPhone();
getPhones()195         Phone[] getPhones();
196     }
197 
198     private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
199         @Override
200         public Phone getPhone(int index) {
201             return PhoneFactory.getPhone(index);
202         }
203 
204         @Override
205         public Phone getDefaultPhone() {
206             return PhoneFactory.getDefaultPhone();
207         }
208 
209         @Override
210         public Phone[] getPhones() {
211             return PhoneFactory.getPhones();
212         }
213     };
214 
215     @VisibleForTesting
setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)216     public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
217         mSubscriptionManagerProxy = proxy;
218     }
219 
220     @VisibleForTesting
setTelephonyManagerProxy(TelephonyManagerProxy proxy)221     public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) {
222         mTelephonyManagerProxy = proxy;
223     }
224 
225     @VisibleForTesting
setPhoneFactoryProxy(PhoneFactoryProxy proxy)226     public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
227         mPhoneFactoryProxy = proxy;
228     }
229 
230     /**
231      * A listener to actionable events specific to the TelephonyConnection.
232      */
233     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
234             new TelephonyConnection.TelephonyConnectionListener() {
235         @Override
236         public void onOriginalConnectionConfigured(TelephonyConnection c) {
237             addConnectionToConferenceController(c);
238         }
239 
240         @Override
241         public void onOriginalConnectionRetry(TelephonyConnection c) {
242             retryOutgoingOriginalConnection(c);
243         }
244     };
245 
246     @Override
onCreate()247     public void onCreate() {
248         super.onCreate();
249         Log.initLogging(this);
250         mExpectedComponentName = new ComponentName(this, this.getClass());
251         mEmergencyTonePlayer = new EmergencyTonePlayer(this);
252         TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
253     }
254 
255     @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)256     public Connection onCreateOutgoingConnection(
257             PhoneAccountHandle connectionManagerPhoneAccount,
258             final ConnectionRequest request) {
259         Log.i(this, "onCreateOutgoingConnection, request: " + request);
260 
261         Uri handle = request.getAddress();
262         if (handle == null) {
263             Log.d(this, "onCreateOutgoingConnection, handle is null");
264             return Connection.createFailedConnection(
265                     DisconnectCauseUtil.toTelecomDisconnectCause(
266                             android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
267                             "No phone number supplied"));
268         }
269 
270         String scheme = handle.getScheme();
271         String number;
272         if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
273             // TODO: We don't check for SecurityException here (requires
274             // CALL_PRIVILEGED permission).
275             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
276             if (phone == null) {
277                 Log.d(this, "onCreateOutgoingConnection, phone is null");
278                 return Connection.createFailedConnection(
279                         DisconnectCauseUtil.toTelecomDisconnectCause(
280                                 android.telephony.DisconnectCause.OUT_OF_SERVICE,
281                                 "Phone is null"));
282             }
283             number = phone.getVoiceMailNumber();
284             if (TextUtils.isEmpty(number)) {
285                 Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
286                 return Connection.createFailedConnection(
287                         DisconnectCauseUtil.toTelecomDisconnectCause(
288                                 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
289                                 "Voicemail scheme provided but no voicemail number set."));
290             }
291 
292             // Convert voicemail: to tel:
293             handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
294         } else {
295             if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
296                 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
297                 return Connection.createFailedConnection(
298                         DisconnectCauseUtil.toTelecomDisconnectCause(
299                                 android.telephony.DisconnectCause.INVALID_NUMBER,
300                                 "Handle scheme is not type tel"));
301             }
302 
303             number = handle.getSchemeSpecificPart();
304             if (TextUtils.isEmpty(number)) {
305                 Log.d(this, "onCreateOutgoingConnection, unable to parse number");
306                 return Connection.createFailedConnection(
307                         DisconnectCauseUtil.toTelecomDisconnectCause(
308                                 android.telephony.DisconnectCause.INVALID_NUMBER,
309                                 "Unable to parse number"));
310             }
311 
312             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
313             if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) {
314                 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number
315                 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and
316                 // when dialed could lock LTE SIMs to 3G if not prohibited..
317                 boolean disableActivation = false;
318                 CarrierConfigManager cfgManager = (CarrierConfigManager)
319                         phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
320                 if (cfgManager != null) {
321                     disableActivation = cfgManager.getConfigForSubId(phone.getSubId())
322                             .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL);
323                 }
324 
325                 if (disableActivation) {
326                     return Connection.createFailedConnection(
327                             DisconnectCauseUtil.toTelecomDisconnectCause(
328                                     android.telephony.DisconnectCause
329                                             .CDMA_ALREADY_ACTIVATED,
330                                     "Tried to dial *228"));
331                 }
332             }
333         }
334 
335         // Convert into emergency number if necessary
336         // This is required in some regions (e.g. Taiwan).
337         if (!PhoneNumberUtils.isLocalEmergencyNumber(this, number) &&
338                 PhoneNumberUtils.isConvertToEmergencyNumberEnabled()) {
339             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
340             // We only do the conversion if the phone is not in service. The un-converted
341             // emergency numbers will go to the correct destination when the phone is in-service,
342             // so they will only need the special emergency call setup when the phone is out of
343             // service.
344             if (phone == null || phone.getServiceState().getState()
345                     != ServiceState.STATE_IN_SERVICE) {
346                 String convertedNumber = PhoneNumberUtils.convertToEmergencyNumber(number);
347                 if (!TextUtils.equals(convertedNumber, number)) {
348                     Log.i(this, "onCreateOutgoingConnection, converted to emergency number");
349                     number = convertedNumber;
350                     handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
351                 }
352             }
353         }
354         final String numberToDial = number;
355 
356         final boolean isEmergencyNumber =
357                 PhoneNumberUtils.isLocalEmergencyNumber(this, numberToDial);
358 
359         final boolean isAirplaneModeOn = Settings.Global.getInt(getContentResolver(),
360                 Settings.Global.AIRPLANE_MODE_ON, 0) > 0;
361 
362         if (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn)) {
363             final Uri emergencyHandle = handle;
364             // By default, Connection based on the default Phone, since we need to return to Telecom
365             // now.
366             final int defaultPhoneType = mPhoneFactoryProxy.getDefaultPhone().getPhoneType();
367             final Connection emergencyConnection = getTelephonyConnection(request, numberToDial,
368                     isEmergencyNumber, emergencyHandle, mPhoneFactoryProxy.getDefaultPhone());
369             if (mEmergencyCallHelper == null) {
370                 mEmergencyCallHelper = new EmergencyCallHelper(this);
371             }
372             mEmergencyCallHelper.enableEmergencyCalling(new EmergencyCallStateListener.Callback() {
373                 @Override
374                 public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) {
375                     // Make sure the Call has not already been canceled by the user.
376                     if (emergencyConnection.getState() == Connection.STATE_DISCONNECTED) {
377                         Log.i(this, "Emergency call disconnected before the outgoing call was " +
378                                 "placed. Skipping emergency call placement.");
379                         return;
380                     }
381                     if (isRadioReady) {
382                         // Get the right phone object since the radio has been turned on
383                         // successfully.
384                         final Phone phone = getPhoneForAccount(request.getAccountHandle(),
385                                 isEmergencyNumber);
386                         // If the PhoneType of the Phone being used is different than the Default
387                         // Phone, then we need create a new Connection using that PhoneType and
388                         // replace it in Telecom.
389                         if (phone.getPhoneType() != defaultPhoneType) {
390                             Connection repConnection = getTelephonyConnection(request, numberToDial,
391                                     isEmergencyNumber, emergencyHandle, phone);
392                             // If there was a failure, the resulting connection will not be a
393                             // TelephonyConnection, so don't place the call, just return!
394                             if (repConnection instanceof TelephonyConnection) {
395                                 placeOutgoingConnection((TelephonyConnection) repConnection, phone,
396                                         request);
397                             }
398                             // Notify Telecom of the new Connection type.
399                             // TODO: Switch out the underlying connection instead of creating a new
400                             // one and causing UI Jank.
401                             addExistingConnection(PhoneUtils.makePstnPhoneAccountHandle(phone),
402                                     repConnection);
403                             // Remove the old connection from Telecom after.
404                             emergencyConnection.setDisconnected(
405                                     DisconnectCauseUtil.toTelecomDisconnectCause(
406                                             android.telephony.DisconnectCause.OUTGOING_CANCELED,
407                                             "Reconnecting outgoing Emergency Call."));
408                             emergencyConnection.destroy();
409                         } else {
410                             placeOutgoingConnection((TelephonyConnection) emergencyConnection,
411                                     phone, request);
412                         }
413                     } else {
414                         Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
415                         emergencyConnection.setDisconnected(
416                                 DisconnectCauseUtil.toTelecomDisconnectCause(
417                                         android.telephony.DisconnectCause.POWER_OFF,
418                                         "Failed to turn on radio."));
419                         emergencyConnection.destroy();
420                     }
421                 }
422             });
423             // Return the still unconnected GsmConnection and wait for the Radios to boot before
424             // connecting it to the underlying Phone.
425             return emergencyConnection;
426         } else {
427             if (!canAddCall() && !isEmergencyNumber) {
428                 Log.d(this, "onCreateOutgoingConnection, cannot add call .");
429                 return Connection.createFailedConnection(
430                         new DisconnectCause(DisconnectCause.ERROR,
431                                 getApplicationContext().getText(
432                                         R.string.incall_error_cannot_add_call),
433                                 getApplicationContext().getText(
434                                         R.string.incall_error_cannot_add_call),
435                                 "Add call restricted due to ongoing video call"));
436             }
437 
438             // Get the right phone object from the account data passed in.
439             final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
440             Connection resultConnection = getTelephonyConnection(request, numberToDial,
441                     isEmergencyNumber, handle, phone);
442             // If there was a failure, the resulting connection will not be a TelephonyConnection,
443             // so don't place the call!
444             if(resultConnection instanceof TelephonyConnection) {
445                 placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
446             }
447             return resultConnection;
448         }
449     }
450 
451     /**
452      * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
453      *      otherwise.
454      */
canAddCall()455     private boolean canAddCall() {
456         Collection<Connection> connections = getAllConnections();
457         for (Connection connection : connections) {
458             if (connection.getExtras() != null &&
459                     connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) {
460                 return false;
461             }
462         }
463         return true;
464     }
465 
getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)466     private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
467             boolean isEmergencyNumber, final Uri handle, Phone phone) {
468 
469         if (phone == null) {
470             final Context context = getApplicationContext();
471             if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) {
472                 // Check SIM card state before the outgoing call.
473                 // Start the SIM unlock activity if PIN_REQUIRED.
474                 final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone();
475                 final IccCard icc = defaultPhone.getIccCard();
476                 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN;
477                 if (icc != null) {
478                     simState = icc.getState();
479                 }
480                 if (simState == IccCardConstants.State.PIN_REQUIRED) {
481                     final String simUnlockUiPackage = context.getResources().getString(
482                             R.string.config_simUnlockUiPackage);
483                     final String simUnlockUiClass = context.getResources().getString(
484                             R.string.config_simUnlockUiClass);
485                     if (simUnlockUiPackage != null && simUnlockUiClass != null) {
486                         Intent simUnlockIntent = new Intent().setComponent(new ComponentName(
487                                 simUnlockUiPackage, simUnlockUiClass));
488                         simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
489                         try {
490                             context.startActivity(simUnlockIntent);
491                         } catch (ActivityNotFoundException exception) {
492                             Log.e(this, exception, "Unable to find SIM unlock UI activity.");
493                         }
494                     }
495                     return Connection.createFailedConnection(
496                             DisconnectCauseUtil.toTelecomDisconnectCause(
497                                     android.telephony.DisconnectCause.OUT_OF_SERVICE,
498                                     "SIM_STATE_PIN_REQUIRED"));
499                 }
500             }
501 
502             Log.d(this, "onCreateOutgoingConnection, phone is null");
503             return Connection.createFailedConnection(
504                     DisconnectCauseUtil.toTelecomDisconnectCause(
505                             android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null"));
506         }
507 
508         // Check both voice & data RAT to enable normal CS call,
509         // when voice RAT is OOS but Data RAT is present.
510         int state = phone.getServiceState().getState();
511         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
512             int dataNetType = phone.getServiceState().getDataNetworkType();
513             if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
514                     dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
515                 state = phone.getServiceState().getDataRegState();
516             }
517         }
518 
519         // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
520         // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
521         if (!isEmergencyNumber && phone.isInEcm()) {
522             boolean allowNonEmergencyCalls = true;
523             CarrierConfigManager cfgManager = (CarrierConfigManager)
524                     phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
525             if (cfgManager != null) {
526                 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId())
527                         .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL);
528             }
529 
530             if (!allowNonEmergencyCalls) {
531                 return Connection.createFailedConnection(
532                         DisconnectCauseUtil.toTelecomDisconnectCause(
533                                 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY,
534                                 "Cannot make non-emergency call in ECM mode."
535                         ));
536             }
537         }
538 
539         if (!isEmergencyNumber) {
540             switch (state) {
541                 case ServiceState.STATE_IN_SERVICE:
542                 case ServiceState.STATE_EMERGENCY_ONLY:
543                     break;
544                 case ServiceState.STATE_OUT_OF_SERVICE:
545                     if (phone.isUtEnabled() && number.endsWith("#")) {
546                         Log.d(this, "onCreateOutgoingConnection dial for UT");
547                         break;
548                     } else {
549                         return Connection.createFailedConnection(
550                                 DisconnectCauseUtil.toTelecomDisconnectCause(
551                                         android.telephony.DisconnectCause.OUT_OF_SERVICE,
552                                         "ServiceState.STATE_OUT_OF_SERVICE"));
553                     }
554                 case ServiceState.STATE_POWER_OFF:
555                     return Connection.createFailedConnection(
556                             DisconnectCauseUtil.toTelecomDisconnectCause(
557                                     android.telephony.DisconnectCause.POWER_OFF,
558                                     "ServiceState.STATE_POWER_OFF"));
559                 default:
560                     Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
561                     return Connection.createFailedConnection(
562                             DisconnectCauseUtil.toTelecomDisconnectCause(
563                                     android.telephony.DisconnectCause.OUTGOING_FAILURE,
564                                     "Unknown service state " + state));
565             }
566         }
567 
568         final Context context = getApplicationContext();
569         if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled(context) &&
570                 !isEmergencyNumber) {
571             return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
572                     android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED));
573         }
574 
575         // Check for additional limits on CDMA phones.
576         final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone);
577         if (failedConnection != null) {
578             return failedConnection;
579         }
580 
581         // Check roaming status to see if we should block custom call forwarding codes
582         if (blockCallForwardingNumberWhileRoaming(phone, number)) {
583             return Connection.createFailedConnection(
584                     DisconnectCauseUtil.toTelecomDisconnectCause(
585                             android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING,
586                             "Call forwarding while roaming"));
587         }
588 
589 
590         final TelephonyConnection connection =
591                 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
592                         request.getTelecomCallId(), request.getAddress(), request.getVideoState());
593         if (connection == null) {
594             return Connection.createFailedConnection(
595                     DisconnectCauseUtil.toTelecomDisconnectCause(
596                             android.telephony.DisconnectCause.OUTGOING_FAILURE,
597                             "Invalid phone type"));
598         }
599         connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
600         connection.setInitializing();
601         connection.setVideoState(request.getVideoState());
602 
603         return connection;
604     }
605 
606     @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)607     public Connection onCreateIncomingConnection(
608             PhoneAccountHandle connectionManagerPhoneAccount,
609             ConnectionRequest request) {
610         Log.i(this, "onCreateIncomingConnection, request: " + request);
611         // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
612         // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
613         PhoneAccountHandle accountHandle = request.getAccountHandle();
614         boolean isEmergency = false;
615         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
616                 accountHandle.getId())) {
617             Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " +
618                     "Treat as an Emergency Call.");
619             isEmergency = true;
620         }
621         Phone phone = getPhoneForAccount(accountHandle, isEmergency);
622         if (phone == null) {
623             return Connection.createFailedConnection(
624                     DisconnectCauseUtil.toTelecomDisconnectCause(
625                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
626                             "Phone is null"));
627         }
628 
629         Call call = phone.getRingingCall();
630         if (!call.getState().isRinging()) {
631             Log.i(this, "onCreateIncomingConnection, no ringing call");
632             return Connection.createFailedConnection(
633                     DisconnectCauseUtil.toTelecomDisconnectCause(
634                             android.telephony.DisconnectCause.INCOMING_MISSED,
635                             "Found no ringing call"));
636         }
637 
638         com.android.internal.telephony.Connection originalConnection =
639                 call.getState() == Call.State.WAITING ?
640                     call.getLatestConnection() : call.getEarliestConnection();
641         if (isOriginalConnectionKnown(originalConnection)) {
642             Log.i(this, "onCreateIncomingConnection, original connection already registered");
643             return Connection.createCanceledConnection();
644         }
645 
646         // We should rely on the originalConnection to get the video state.  The request coming
647         // from Telecom does not know the video state of the incoming call.
648         int videoState = originalConnection != null ? originalConnection.getVideoState() :
649                 VideoProfile.STATE_AUDIO_ONLY;
650 
651         Connection connection =
652                 createConnectionFor(phone, originalConnection, false /* isOutgoing */,
653                         request.getAccountHandle(), request.getTelecomCallId(),
654                         request.getAddress(), videoState);
655         if (connection == null) {
656             return Connection.createCanceledConnection();
657         } else {
658             return connection;
659         }
660     }
661 
662     /**
663      * Called by the {@link ConnectionService} when a newly created {@link Connection} has been
664      * added to the {@link ConnectionService} and sent to Telecom.  Here it is safe to send
665      * connection events.
666      *
667      * @param connection the {@link Connection}.
668      */
669     @Override
onCreateConnectionComplete(Connection connection)670     public void onCreateConnectionComplete(Connection connection) {
671         if (connection instanceof TelephonyConnection) {
672             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
673             maybeSendInternationalCallEvent(telephonyConnection);
674         }
675     }
676 
677     @Override
triggerConferenceRecalculate()678     public void triggerConferenceRecalculate() {
679         if (mTelephonyConferenceController.shouldRecalculate()) {
680             mTelephonyConferenceController.recalculate();
681         }
682     }
683 
684     @Override
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)685     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
686             ConnectionRequest request) {
687         Log.i(this, "onCreateUnknownConnection, request: " + request);
688         // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's
689         // Emergency PhoneAccount
690         PhoneAccountHandle accountHandle = request.getAccountHandle();
691         boolean isEmergency = false;
692         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
693                 accountHandle.getId())) {
694             Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " +
695                     "Treat as an Emergency Call.");
696             isEmergency = true;
697         }
698         Phone phone = getPhoneForAccount(accountHandle, isEmergency);
699         if (phone == null) {
700             return Connection.createFailedConnection(
701                     DisconnectCauseUtil.toTelecomDisconnectCause(
702                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
703                             "Phone is null"));
704         }
705         Bundle extras = request.getExtras();
706 
707         final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
708 
709         // Handle the case where an unknown connection has an IMS external call ID specified; we can
710         // skip the rest of the guesswork and just grad that unknown call now.
711         if (phone.getImsPhone() != null && extras != null &&
712                 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) {
713 
714             ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
715             ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
716             int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
717                     -1);
718 
719             if (externalCallTracker != null) {
720                 com.android.internal.telephony.Connection connection =
721                         externalCallTracker.getConnectionById(externalCallId);
722 
723                 if (connection != null) {
724                     allConnections.add(connection);
725                 }
726             }
727         }
728 
729         if (allConnections.isEmpty()) {
730             final Call ringingCall = phone.getRingingCall();
731             if (ringingCall.hasConnections()) {
732                 allConnections.addAll(ringingCall.getConnections());
733             }
734             final Call foregroundCall = phone.getForegroundCall();
735             if ((foregroundCall.getState() != Call.State.DISCONNECTED)
736                     && (foregroundCall.hasConnections())) {
737                 allConnections.addAll(foregroundCall.getConnections());
738             }
739             if (phone.getImsPhone() != null) {
740                 final Call imsFgCall = phone.getImsPhone().getForegroundCall();
741                 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall
742                         .hasConnections()) {
743                     allConnections.addAll(imsFgCall.getConnections());
744                 }
745             }
746             final Call backgroundCall = phone.getBackgroundCall();
747             if (backgroundCall.hasConnections()) {
748                 allConnections.addAll(phone.getBackgroundCall().getConnections());
749             }
750         }
751 
752         com.android.internal.telephony.Connection unknownConnection = null;
753         for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
754             if (!isOriginalConnectionKnown(telephonyConnection)) {
755                 unknownConnection = telephonyConnection;
756                 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection);
757                 break;
758             }
759         }
760 
761         if (unknownConnection == null) {
762             Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
763             return Connection.createCanceledConnection();
764         }
765 
766         // We should rely on the originalConnection to get the video state.  The request coming
767         // from Telecom does not know the video state of the unknown call.
768         int videoState = unknownConnection != null ? unknownConnection.getVideoState() :
769                 VideoProfile.STATE_AUDIO_ONLY;
770 
771         TelephonyConnection connection =
772                 createConnectionFor(phone, unknownConnection,
773                         !unknownConnection.isIncoming() /* isOutgoing */,
774                         request.getAccountHandle(), request.getTelecomCallId(),
775                         request.getAddress(), videoState);
776 
777         if (connection == null) {
778             return Connection.createCanceledConnection();
779         } else {
780             connection.updateState();
781             return connection;
782         }
783     }
784 
785     /**
786      * Conferences two connections.
787      *
788      * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has
789      * a limitation in that it can only specify conferenceables which are instances of
790      * {@link android.telecom.RemoteConnection}.  In the case of an {@link ImsConference}, the
791      * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge
792      * a {@link Conference} and a {@link Connection}.  As a result when, merging a
793      * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference}
794      * require merging a {@link ConferenceParticipantConnection} which is a child of the
795      * {@link Conference} with a {@link TelephonyConnection}.  The
796      * {@link ConferenceParticipantConnection} class does not have the capability to initiate a
797      * conference merge, so we need to call
798      * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or
799      * {@code connection2}, one of which is an instance of {@link TelephonyConnection}.
800      *
801      * @param connection1 A connection to merge into a conference call.
802      * @param connection2 A connection to merge into a conference call.
803      */
804     @Override
onConference(Connection connection1, Connection connection2)805     public void onConference(Connection connection1, Connection connection2) {
806         if (connection1 instanceof TelephonyConnection) {
807             ((TelephonyConnection) connection1).performConference(connection2);
808         } else if (connection2 instanceof TelephonyConnection) {
809             ((TelephonyConnection) connection2).performConference(connection1);
810         } else {
811             Log.w(this, "onConference - cannot merge connections " +
812                     "Connection1: %s, Connection2: %2", connection1, connection2);
813         }
814     }
815 
blockCallForwardingNumberWhileRoaming(Phone phone, String number)816     private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) {
817         if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) {
818             return false;
819         }
820         String[] blockPrefixes = null;
821         CarrierConfigManager cfgManager = (CarrierConfigManager)
822                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
823         if (cfgManager != null) {
824             blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray(
825                     CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY);
826         }
827 
828         if (blockPrefixes != null) {
829             for (String prefix : blockPrefixes) {
830                 if (number.startsWith(prefix)) {
831                     return true;
832                 }
833             }
834         }
835         return false;
836     }
837 
isRadioOn()838     private boolean isRadioOn() {
839         boolean result = false;
840         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
841             result |= phone.isRadioOn();
842         }
843         return result;
844     }
845 
makeCachedConnectionPhonePair( TelephonyConnection c)846     private Pair<WeakReference<TelephonyConnection>, List<Phone>> makeCachedConnectionPhonePair(
847             TelephonyConnection c) {
848         List<Phone> phones = new ArrayList<>(Arrays.asList(mPhoneFactoryProxy.getPhones()));
849         return new Pair<>(new WeakReference<>(c), phones);
850     }
851 
852     // Check the mEmergencyRetryCache to see if it contains the TelephonyConnection. If it doesn't,
853     // then it is stale. Create a new one!
updateCachedConnectionPhonePair(TelephonyConnection c)854     private void updateCachedConnectionPhonePair(TelephonyConnection c) {
855         if (mEmergencyRetryCache == null) {
856             Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache");
857             mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
858         } else {
859             // Check to see if old cache is stale. If it is, replace it
860             WeakReference<TelephonyConnection> cachedConnection = mEmergencyRetryCache.first;
861             if (cachedConnection.get() != c) {
862                 Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
863                 mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
864             }
865         }
866     }
867 
868     /**
869      * Returns the first Phone that has not been used yet to place the call. Any Phones that have
870      * been used to place a call will have already been removed from mEmergencyRetryCache.second.
871      * The phone that it excluded will be removed from mEmergencyRetryCache.second in this method.
872      * @param phoneToExclude The Phone object that will be removed from our cache of available
873      * phones.
874      * @return the first Phone that is available to be used to retry the call.
875      */
getPhoneForRedial(Phone phoneToExclude)876     private Phone getPhoneForRedial(Phone phoneToExclude) {
877         List<Phone> cachedPhones = mEmergencyRetryCache.second;
878         if (cachedPhones.contains(phoneToExclude)) {
879             Log.i(this, "getPhoneForRedial, removing Phone[" + phoneToExclude.getPhoneId() +
880                     "] from the available Phone cache.");
881             cachedPhones.remove(phoneToExclude);
882         }
883         return cachedPhones.isEmpty() ? null : cachedPhones.get(0);
884     }
885 
retryOutgoingOriginalConnection(TelephonyConnection c)886     private void retryOutgoingOriginalConnection(TelephonyConnection c) {
887         updateCachedConnectionPhonePair(c);
888         Phone newPhoneToUse = getPhoneForRedial(c.getPhone());
889         if (newPhoneToUse != null) {
890             int videoState = c.getVideoState();
891             Bundle connExtras = c.getExtras();
892             Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
893             c.clearOriginalConnection();
894             placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
895         } else {
896             // We have run out of Phones to use. Disconnect the call and destroy the connection.
897             Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting.");
898             c.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
899             c.clearOriginalConnection();
900             c.destroy();
901         }
902     }
903 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)904     private void placeOutgoingConnection(
905             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
906         placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
907     }
908 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)909     private void placeOutgoingConnection(
910             TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
911         String number = connection.getAddress().getSchemeSpecificPart();
912 
913         com.android.internal.telephony.Connection originalConnection = null;
914         try {
915             if (phone != null) {
916                 originalConnection = phone.dial(number, null, videoState, extras);
917             }
918         } catch (CallStateException e) {
919             Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
920             int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
921             if (e.getError() == CallStateException.ERROR_DISCONNECTED) {
922                 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE;
923             } else if (e.getError() == CallStateException.ERROR_POWER_OFF) {
924                 cause = android.telephony.DisconnectCause.POWER_OFF;
925             }
926             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
927                     cause, e.getMessage()));
928             return;
929         }
930 
931         if (originalConnection == null) {
932             int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
933             // On GSM phones, null connection means that we dialed an MMI code
934             if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
935                 Log.d(this, "dialed MMI code");
936                 int subId = phone.getSubId();
937                 Log.d(this, "subId: "+subId);
938                 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
939                 final Intent intent = new Intent(this, MMIDialogActivity.class);
940                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
941                         Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
942                 if (SubscriptionManager.isValidSubscriptionId(subId)) {
943                     intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
944                 }
945                 startActivity(intent);
946             }
947             Log.d(this, "placeOutgoingConnection, phone.dial returned null");
948             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
949                     telephonyDisconnectCause, "Connection is null"));
950         } else {
951             connection.setOriginalConnection(originalConnection);
952         }
953     }
954 
createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, Uri address, int videoState)955     private TelephonyConnection createConnectionFor(
956             Phone phone,
957             com.android.internal.telephony.Connection originalConnection,
958             boolean isOutgoing,
959             PhoneAccountHandle phoneAccountHandle,
960             String telecomCallId,
961             Uri address,
962             int videoState) {
963         TelephonyConnection returnConnection = null;
964         int phoneType = phone.getPhoneType();
965         if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
966             returnConnection = new GsmConnection(originalConnection, telecomCallId, isOutgoing);
967         } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
968             boolean allowsMute = allowsMute(phone);
969             returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer,
970                     allowsMute, isOutgoing, telecomCallId);
971         }
972         if (returnConnection != null) {
973             // Listen to Telephony specific callbacks from the connection
974             returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
975             returnConnection.setVideoPauseSupported(
976                     TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
977                             phoneAccountHandle));
978         }
979         return returnConnection;
980     }
981 
isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)982     private boolean isOriginalConnectionKnown(
983             com.android.internal.telephony.Connection originalConnection) {
984         for (Connection connection : getAllConnections()) {
985             if (connection instanceof TelephonyConnection) {
986                 TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
987                 if (telephonyConnection.getOriginalConnection() == originalConnection) {
988                     return true;
989                 }
990             }
991         }
992         return false;
993     }
994 
getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency)995     private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
996         Phone chosenPhone = null;
997         int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
998         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
999             int phoneId = mSubscriptionManagerProxy.getPhoneId(subId);
1000             chosenPhone = mPhoneFactoryProxy.getPhone(phoneId);
1001         }
1002         // If this is an emergency call and the phone we originally planned to make this call
1003         // with is not in service or was invalid, try to find one that is in service, using the
1004         // default as a last chance backup.
1005         if (isEmergency && (chosenPhone == null || ServiceState.STATE_IN_SERVICE != chosenPhone
1006                 .getServiceState().getState())) {
1007             Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
1008                     + "or invalid for emergency call.", accountHandle);
1009             chosenPhone = getFirstPhoneForEmergencyCall();
1010             Log.d(this, "getPhoneForAccount: using subId: " +
1011                     (chosenPhone == null ? "null" : chosenPhone.getSubId()));
1012         }
1013         return chosenPhone;
1014     }
1015 
1016     /**
1017      * Retrieves the most sensible Phone to use for an emergency call using the following Priority
1018      *  list (for multi-SIM devices):
1019      *  1) The User's SIM preference for Voice calling
1020      *  2) The First Phone that is currently IN_SERVICE or is available for emergency calling
1021      *  3) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs
1022      *     are locked, skip to condition 4).
1023      *  4) The Phone with more Capabilities.
1024      *  5) The First Phone that has a SIM card in it (Starting from Slot 0...N)
1025      *  6) The Default Phone (Currently set as Slot 0)
1026      */
1027     @VisibleForTesting
getFirstPhoneForEmergencyCall()1028     public Phone getFirstPhoneForEmergencyCall() {
1029         // 1)
1030         int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId();
1031         if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
1032             Phone defaultPhone = mPhoneFactoryProxy.getPhone(phoneId);
1033             if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) {
1034                 return defaultPhone;
1035             }
1036         }
1037 
1038         Phone firstPhoneWithSim = null;
1039         int phoneCount = mTelephonyManagerProxy.getPhoneCount();
1040         List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount);
1041         for (int i = 0; i < phoneCount; i++) {
1042             Phone phone = mPhoneFactoryProxy.getPhone(i);
1043             if (phone == null) {
1044                 continue;
1045             }
1046             // 2)
1047             if (isAvailableForEmergencyCalls(phone)) {
1048                 // the slot has the radio on & state is in service.
1049                 Log.i(this, "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i);
1050                 return phone;
1051             }
1052             // 4)
1053             // Store the RAF Capabilities for sorting later.
1054             int radioAccessFamily = phone.getRadioAccessFamily();
1055             SlotStatus status = new SlotStatus(i, radioAccessFamily);
1056             phoneSlotStatus.add(status);
1057             Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" +
1058                     Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i);
1059             // 3)
1060             // Report Slot's PIN/PUK lock status for sorting later.
1061             int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i);
1062             if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED ||
1063                     simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
1064                 status.isLocked = true;
1065             }
1066             // 5)
1067             if (firstPhoneWithSim == null && mTelephonyManagerProxy.hasIccCard(i)) {
1068                 // The slot has a SIM card inserted, but is not in service, so keep track of this
1069                 // Phone. Do not return because we want to make sure that none of the other Phones
1070                 // are in service (because that is always faster).
1071                 firstPhoneWithSim = phone;
1072                 Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" +
1073                         firstPhoneWithSim.getPhoneId());
1074             }
1075         }
1076         // 6)
1077         if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) {
1078             // No Phones available, get the default.
1079             Log.i(this, "getFirstPhoneForEmergencyCall, return default phone");
1080             return mPhoneFactoryProxy.getDefaultPhone();
1081         } else {
1082             // 4)
1083             final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId();
1084             final Phone firstOccupiedSlot = firstPhoneWithSim;
1085             if (!phoneSlotStatus.isEmpty()) {
1086                 // Only sort if there are enough elements to do so.
1087                 if (phoneSlotStatus.size() > 1) {
1088                     Collections.sort(phoneSlotStatus, (o1, o2) -> {
1089                         // First start by seeing if either of the phone slots are locked. If they
1090                         // are, then sort by non-locked SIM first. If they are both locked, sort
1091                         // by capability instead.
1092                         if (o1.isLocked && !o2.isLocked) {
1093                             return -1;
1094                         }
1095                         if (o2.isLocked && !o1.isLocked) {
1096                             return 1;
1097                         }
1098                         // sort by number of RadioAccessFamily Capabilities.
1099                         int compare = Integer.bitCount(o1.capabilities) -
1100                                 Integer.bitCount(o2.capabilities);
1101                         if (compare == 0) {
1102                             // Sort by highest RAF Capability if the number is the same.
1103                             compare = RadioAccessFamily.getHighestRafCapability(o1.capabilities) -
1104                                     RadioAccessFamily.getHighestRafCapability(o2.capabilities);
1105                             if (compare == 0) {
1106                                 if (firstOccupiedSlot != null) {
1107                                     // If the RAF capability is the same, choose based on whether or
1108                                     // not any of the slots are occupied with a SIM card (if both
1109                                     // are, always choose the first).
1110                                     if (o1.slotId == firstOccupiedSlot.getPhoneId()) {
1111                                         return 1;
1112                                     } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) {
1113                                         return -1;
1114                                     }
1115                                 } else {
1116                                     // No slots have SIMs detected in them, so weight the default
1117                                     // Phone Id greater than the others.
1118                                     if (o1.slotId == defaultPhoneId) {
1119                                         return 1;
1120                                     } else if (o2.slotId == defaultPhoneId) {
1121                                         return -1;
1122                                     }
1123                                 }
1124                             }
1125                         }
1126                         return compare;
1127                     });
1128                 }
1129                 int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId;
1130                 Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId +
1131                         "with highest capability");
1132                 return mPhoneFactoryProxy.getPhone(mostCapablePhoneId);
1133             } else {
1134                 // 5)
1135                 return firstPhoneWithSim;
1136             }
1137         }
1138     }
1139 
1140     /**
1141      * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
1142      */
isAvailableForEmergencyCalls(Phone phone)1143     private boolean isAvailableForEmergencyCalls(Phone phone) {
1144         return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() ||
1145                 phone.getServiceState().isEmergencyOnly();
1146     }
1147 
1148     /**
1149      * Determines if the connection should allow mute.
1150      *
1151      * @param phone The current phone.
1152      * @return {@code True} if the connection should allow mute.
1153      */
allowsMute(Phone phone)1154     private boolean allowsMute(Phone phone) {
1155         // For CDMA phones, check if we are in Emergency Callback Mode (ECM).  Mute is disallowed
1156         // in ECM mode.
1157         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1158             if (phone.isInEcm()) {
1159                 return false;
1160             }
1161         }
1162 
1163         return true;
1164     }
1165 
1166     @Override
removeConnection(Connection connection)1167     public void removeConnection(Connection connection) {
1168         super.removeConnection(connection);
1169         if (connection instanceof TelephonyConnection) {
1170             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1171             telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
1172         }
1173     }
1174 
1175     /**
1176      * When a {@link TelephonyConnection} has its underlying original connection configured,
1177      * we need to add it to the correct conference controller.
1178      *
1179      * @param connection The connection to be added to the controller
1180      */
addConnectionToConferenceController(TelephonyConnection connection)1181     public void addConnectionToConferenceController(TelephonyConnection connection) {
1182         // TODO: Need to revisit what happens when the original connection for the
1183         // TelephonyConnection changes.  If going from CDMA --> GSM (for example), the
1184         // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection.
1185         // The CDMA conference controller makes the assumption that it will only have CDMA
1186         // connections in it, while the other conference controllers aren't as restrictive.  Really,
1187         // when we go between CDMA and GSM we should replace the TelephonyConnection.
1188         if (connection.isImsConnection()) {
1189             Log.d(this, "Adding IMS connection to conference controller: " + connection);
1190             mImsConferenceController.add(connection);
1191             mTelephonyConferenceController.remove(connection);
1192             if (connection instanceof CdmaConnection) {
1193                 mCdmaConferenceController.remove((CdmaConnection) connection);
1194             }
1195         } else {
1196             int phoneType = connection.getCall().getPhone().getPhoneType();
1197             if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
1198                 Log.d(this, "Adding GSM connection to conference controller: " + connection);
1199                 mTelephonyConferenceController.add(connection);
1200                 if (connection instanceof CdmaConnection) {
1201                     mCdmaConferenceController.remove((CdmaConnection) connection);
1202                 }
1203             } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA &&
1204                     connection instanceof CdmaConnection) {
1205                 Log.d(this, "Adding CDMA connection to conference controller: " + connection);
1206                 mCdmaConferenceController.add((CdmaConnection) connection);
1207                 mTelephonyConferenceController.remove(connection);
1208             }
1209             Log.d(this, "Removing connection from IMS conference controller: " + connection);
1210             mImsConferenceController.remove(connection);
1211         }
1212     }
1213 
1214     /**
1215      * Create a new CDMA connection. CDMA connections have additional limitations when creating
1216      * additional calls which are handled in this method.  Specifically, CDMA has a "FLASH" command
1217      * that can be used for three purposes: merging a call, swapping unmerged calls, and adding
1218      * a new outgoing call. The function of the flash command depends on the context of the current
1219      * set of calls. This method will prevent an outgoing call from being made if it is not within
1220      * the right circumstances to support adding a call.
1221      */
checkAdditionalOutgoingCallLimits(Phone phone)1222     private Connection checkAdditionalOutgoingCallLimits(Phone phone) {
1223         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1224             // Check to see if any CDMA conference calls exist, and if they do, check them for
1225             // limitations.
1226             for (Conference conference : getAllConferences()) {
1227                 if (conference instanceof CdmaConference) {
1228                     CdmaConference cdmaConf = (CdmaConference) conference;
1229 
1230                     // If the CDMA conference has not been merged, add-call will not work, so fail
1231                     // this request to add a call.
1232                     if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1233                         return Connection.createFailedConnection(new DisconnectCause(
1234                                     DisconnectCause.RESTRICTED,
1235                                     null,
1236                                     getResources().getString(R.string.callFailed_cdma_call_limit),
1237                                     "merge-capable call exists, prevent flash command."));
1238                     }
1239                 }
1240             }
1241         }
1242 
1243         return null; // null means nothing went wrong, and call should continue.
1244     }
1245 
isTtyModeEnabled(Context context)1246     private boolean isTtyModeEnabled(Context context) {
1247         return (android.provider.Settings.Secure.getInt(
1248                 context.getContentResolver(),
1249                 android.provider.Settings.Secure.PREFERRED_TTY_MODE,
1250                 TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
1251     }
1252 
1253     /**
1254      * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is
1255      * dialing an international number.
1256      * @param telephonyConnection The connection.
1257      */
maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)1258     private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) {
1259         if (telephonyConnection == null || telephonyConnection.getPhone() == null ||
1260                 telephonyConnection.getPhone().getDefaultPhone() == null) {
1261             return;
1262         }
1263         Phone phone = telephonyConnection.getPhone().getDefaultPhone();
1264         if (phone instanceof GsmCdmaPhone) {
1265             GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone;
1266             if (telephonyConnection.isOutgoingCall() &&
1267                     gsmCdmaPhone.isNotificationOfWfcCallRequired(
1268                             telephonyConnection.getOriginalConnection().getOrigDialString())) {
1269                 // Send connection event to InCall UI to inform the user of the fact they
1270                 // are potentially placing an international call on WFC.
1271                 Log.i(this, "placeOutgoingConnection - sending international call on WFC " +
1272                         "confirmation event");
1273                 telephonyConnection.sendConnectionEvent(
1274                         TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null);
1275             }
1276         }
1277     }
1278 }
1279