• 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 static android.telephony.ims.ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED;
20 import static android.telephony.ims.ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL;
21 import static android.telephony.ims.ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.graphics.drawable.Icon;
29 import android.net.Uri;
30 import android.os.AsyncResult;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.Messenger;
36 import android.os.PersistableBundle;
37 import android.telecom.CallAudioState;
38 import android.telecom.CallDiagnostics;
39 import android.telecom.CallScreeningService;
40 import android.telecom.Conference;
41 import android.telecom.Connection;
42 import android.telecom.ConnectionService;
43 import android.telecom.PhoneAccount;
44 import android.telecom.PhoneAccountHandle;
45 import android.telecom.StatusHints;
46 import android.telecom.TelecomManager;
47 import android.telecom.VideoProfile;
48 import android.telephony.CarrierConfigManager;
49 import android.telephony.DisconnectCause;
50 import android.telephony.PhoneNumberUtils;
51 import android.telephony.ServiceState;
52 import android.telephony.ServiceState.RilRadioTechnology;
53 import android.telephony.SubscriptionManager;
54 import android.telephony.TelephonyManager;
55 import android.telephony.emergency.EmergencyNumber;
56 import android.telephony.ims.ImsCallProfile;
57 import android.telephony.ims.ImsReasonInfo;
58 import android.telephony.ims.ImsStreamMediaProfile;
59 import android.telephony.ims.RtpHeaderExtension;
60 import android.telephony.ims.RtpHeaderExtensionType;
61 import android.telephony.ims.feature.MmTelFeature;
62 import android.text.TextUtils;
63 import android.util.ArraySet;
64 import android.util.Pair;
65 
66 import com.android.ims.ImsCall;
67 import com.android.ims.ImsException;
68 import com.android.ims.internal.ConferenceParticipant;
69 import com.android.internal.annotations.VisibleForTesting;
70 import com.android.internal.os.SomeArgs;
71 import com.android.internal.telephony.Call;
72 import com.android.internal.telephony.CallFailCause;
73 import com.android.internal.telephony.CallStateException;
74 import com.android.internal.telephony.Connection.Capability;
75 import com.android.internal.telephony.Connection.PostDialListener;
76 import com.android.internal.telephony.Phone;
77 import com.android.internal.telephony.PhoneConstants;
78 import com.android.internal.telephony.d2d.Communicator;
79 import com.android.internal.telephony.d2d.DtmfAdapter;
80 import com.android.internal.telephony.d2d.DtmfTransport;
81 import com.android.internal.telephony.d2d.MessageTypeAndValueHelper;
82 import com.android.internal.telephony.d2d.RtpAdapter;
83 import com.android.internal.telephony.d2d.RtpTransport;
84 import com.android.internal.telephony.d2d.Timeouts;
85 import com.android.internal.telephony.d2d.TransportProtocol;
86 import com.android.internal.telephony.gsm.SuppServiceNotification;
87 import com.android.internal.telephony.imsphone.ImsPhone;
88 import com.android.internal.telephony.imsphone.ImsPhoneCall;
89 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
90 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
91 import com.android.phone.ImsUtil;
92 import com.android.phone.PhoneGlobals;
93 import com.android.phone.PhoneUtils;
94 import com.android.phone.R;
95 import com.android.phone.callcomposer.CallComposerPictureManager;
96 import com.android.phone.callcomposer.CallComposerPictureTransfer;
97 import com.android.telephony.Rlog;
98 
99 import java.util.ArrayList;
100 import java.util.Arrays;
101 import java.util.Collections;
102 import java.util.HashMap;
103 import java.util.List;
104 import java.util.Locale;
105 import java.util.Map;
106 import java.util.Objects;
107 import java.util.Set;
108 import java.util.concurrent.ConcurrentHashMap;
109 import java.util.concurrent.Executors;
110 import java.util.stream.Collectors;
111 
112 /**
113  * Base class for CDMA and GSM connections.
114  */
115 abstract class TelephonyConnection extends Connection implements Holdable, Communicator.Callback {
116     private static final String LOG_TAG = "TelephonyConnection";
117 
118     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
119     private static final int MSG_RINGBACK_TONE = 2;
120     private static final int MSG_HANDOVER_STATE_CHANGED = 3;
121     private static final int MSG_DISCONNECT = 4;
122     private static final int MSG_MULTIPARTY_STATE_CHANGED = 5;
123     private static final int MSG_CONFERENCE_MERGE_FAILED = 6;
124     private static final int MSG_SUPP_SERVICE_NOTIFY = 7;
125 
126     // the threshold used to compare mAudioCodecBitrateKbps and mAudioCodecBandwidth.
127     private static final float THRESHOLD = 0.01f;
128 
129     /**
130      * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their
131      * equivalents defined in {@link android.telecom.Connection}.
132      */
133     private static final Map<String, String> sExtrasMap = createExtrasMap();
134 
135     private static final int MSG_SET_VIDEO_STATE = 8;
136     private static final int MSG_SET_VIDEO_PROVIDER = 9;
137     private static final int MSG_SET_AUDIO_QUALITY = 10;
138     private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11;
139     private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12;
140     private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13;
141     private static final int MSG_ON_HOLD_TONE = 14;
142     private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15;
143     private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16;
144     private static final int MSG_HANGUP = 17;
145     private static final int MSG_SET_CALL_RADIO_TECH = 18;
146     private static final int MSG_ON_CONNECTION_EVENT = 19;
147     private static final int MSG_REDIAL_CONNECTION_CHANGED = 20;
148     private static final int MSG_REJECT = 21;
149     private static final int MSG_DTMF_DONE = 22;
150     private static final int MSG_MEDIA_ATTRIBUTES_CHANGED = 23;
151     private static final int MSG_ON_RTT_INITIATED = 24;
152     private static final int MSG_HOLD = 25;
153     private static final int MSG_UNHOLD = 26;
154 
155     private static final String JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN = "+81";
156     private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
157 
158     private List<Uri> mParticipants;
159     private boolean mIsAdhocConferenceCall;
160 
161     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
162         @Override
163         public void handleMessage(Message msg) {
164             switch (msg.what) {
165                 case MSG_PRECISE_CALL_STATE_CHANGED:
166                     Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
167                     updateState();
168                     break;
169                 case MSG_HANDOVER_STATE_CHANGED:
170                     // fall through
171                 case MSG_REDIAL_CONNECTION_CHANGED:
172                     String what = (msg.what == MSG_HANDOVER_STATE_CHANGED)
173                             ? "MSG_HANDOVER_STATE_CHANGED" : "MSG_REDIAL_CONNECTION_CHANGED";
174                     Log.i(TelephonyConnection.this, "Connection changed due to: %s", what);
175                     AsyncResult ar = (AsyncResult) msg.obj;
176                     com.android.internal.telephony.Connection connection =
177                          (com.android.internal.telephony.Connection) ar.result;
178                     onOriginalConnectionRedialed(connection);
179                     break;
180                 case MSG_RINGBACK_TONE:
181                     Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
182                     // TODO: This code assumes that there is only one connection in the foreground
183                     // call, in other words, it punts on network-mediated conference calling.
184                     if (getOriginalConnection() != getForegroundConnection()) {
185                         Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
186                                 "not foreground connection, skipping");
187                         return;
188                     }
189                     boolean ringback = (Boolean) ((AsyncResult) msg.obj).result;
190                     setRingbackRequested(ringback);
191                     notifyRingbackRequested(ringback);
192                     break;
193                 case MSG_DISCONNECT:
194                     updateState();
195                     break;
196                 case MSG_MULTIPARTY_STATE_CHANGED:
197                     boolean isMultiParty = (Boolean) msg.obj;
198                     Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
199                     mIsMultiParty = isMultiParty;
200                     if (isMultiParty) {
201                         notifyConferenceStarted();
202                     }
203                     break;
204                 case MSG_CONFERENCE_MERGE_FAILED:
205                     notifyConferenceMergeFailed();
206                     break;
207                 case MSG_SUPP_SERVICE_NOTIFY:
208                     Phone phone = getPhone();
209                     Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : "
210                             + (phone != null ? Integer.toString(phone.getPhoneId())
211                             : "null"));
212                     SuppServiceNotification mSsNotification = null;
213                     if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
214                         mSsNotification =
215                                 (SuppServiceNotification)((AsyncResult) msg.obj).result;
216                         if (mOriginalConnection != null) {
217                             handleSuppServiceNotification(mSsNotification);
218                         }
219                     }
220                     break;
221 
222                 case MSG_SET_VIDEO_STATE:
223                     int videoState = (int) msg.obj;
224                     setTelephonyVideoState(videoState);
225 
226                     // A change to the video state of the call can influence whether or not it
227                     // can be part of a conference, whether another call can be added, and
228                     // whether the call should have the HD audio property set.
229                     refreshConferenceSupported();
230                     refreshDisableAddCall();
231                     refreshHoldSupported();
232                     updateConnectionProperties();
233                     break;
234 
235                 case MSG_SET_VIDEO_PROVIDER:
236                     VideoProvider videoProvider = (VideoProvider) msg.obj;
237                     setTelephonyVideoProvider(videoProvider);
238                     break;
239 
240                 case MSG_SET_AUDIO_QUALITY:
241                     int audioQuality = (int) msg.obj;
242                     setAudioQuality(audioQuality);
243                     break;
244 
245                 case MSG_MEDIA_ATTRIBUTES_CHANGED:
246                     refreshCodec();
247                     break;
248 
249                 case MSG_SET_CONFERENCE_PARTICIPANTS:
250                     List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj;
251                     updateConferenceParticipants(participants);
252                     break;
253 
254                 case MSG_CONNECTION_EXTRAS_CHANGED:
255                     final Bundle extras = (Bundle) msg.obj;
256                     updateExtras(extras);
257                     break;
258 
259                 case MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES:
260                     setOriginalConnectionCapabilities(msg.arg1);
261                     break;
262 
263                 case MSG_ON_HOLD_TONE:
264                     AsyncResult asyncResult = (AsyncResult) msg.obj;
265                     Pair<com.android.internal.telephony.Connection, Boolean> heldInfo =
266                             (Pair<com.android.internal.telephony.Connection, Boolean>)
267                                     asyncResult.result;
268 
269                     // Determines if the hold tone is starting or stopping.
270                     boolean playTone = ((Boolean) (heldInfo.second)).booleanValue();
271 
272                     // Determine which connection the hold tone is stopping or starting for
273                     com.android.internal.telephony.Connection heldConnection = heldInfo.first;
274 
275                     // Only start or stop the hold tone if this is the connection which is starting
276                     // or stopping the hold tone.
277                     if (heldConnection == mOriginalConnection) {
278                         // If starting the hold tone, send a connection event to Telecom which will
279                         // cause it to play the on hold tone.
280                         if (playTone) {
281                             sendTelephonyConnectionEvent(EVENT_ON_HOLD_TONE_START, null);
282                         } else {
283                             sendTelephonyConnectionEvent(EVENT_ON_HOLD_TONE_END, null);
284                         }
285                     }
286                     break;
287 
288                 case MSG_CDMA_VOICE_PRIVACY_ON:
289                     Log.d(this, "MSG_CDMA_VOICE_PRIVACY_ON received");
290                     setCdmaVoicePrivacy(true);
291                     break;
292                 case MSG_CDMA_VOICE_PRIVACY_OFF:
293                     Log.d(this, "MSG_CDMA_VOICE_PRIVACY_OFF received");
294                     setCdmaVoicePrivacy(false);
295                     break;
296                 case MSG_HANGUP:
297                     int cause = (int) msg.obj;
298                     hangup(cause);
299                     break;
300                 case MSG_REJECT:
301                     int rejectReason = (int) msg.obj;
302                     reject(rejectReason);
303                     break;
304                 case MSG_DTMF_DONE:
305                     Log.i(this, "MSG_DTMF_DONE");
306                     break;
307 
308                 case MSG_SET_CALL_RADIO_TECH:
309                     int vrat = (int) msg.obj;
310                     // Check whether Wi-Fi call tech is changed, it means call radio tech is:
311                     //  a) changed from IWLAN to other value, or
312                     //  b) changed from other value to IWLAN.
313                     //
314                     // In other word, below conditions are all met:
315                     // 1) {@link #getCallRadioTech} is different from new vrat
316                     // 2) Current call radio technology indicates Wi-Fi call, i.e. {@link #isWifi}
317                     //    is true, or new vrat indicates Wi-Fi call.
318                     boolean isWifiTechChange = getCallRadioTech() != vrat
319                             && (isWifi() || vrat == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
320 
321                     // Step 1) Updates call radio tech firstly, so that afterwards Wi-Fi related
322                     // update actions are taken correctly.
323                     setCallRadioTech(vrat);
324 
325                     // Step 2) Handles Wi-Fi call tech change.
326                     if (isWifiTechChange) {
327                         updateConnectionProperties();
328                         updateStatusHints();
329                         refreshDisableAddCall();
330                     }
331                     break;
332                 case MSG_ON_CONNECTION_EVENT:
333                     SomeArgs args = (SomeArgs) msg.obj;
334                     try {
335                         sendTelephonyConnectionEvent((String) args.arg1, (Bundle) args.arg2);
336                     } finally {
337                         args.recycle();
338                     }
339                     break;
340                 case MSG_ON_RTT_INITIATED:
341                     if (mOriginalConnection != null) {
342                         // if mOriginalConnection is null, the properties will get set when
343                         // mOriginalConnection gets set.
344                         updateConnectionProperties();
345                         refreshConferenceSupported();
346                     }
347                     sendRttInitiationSuccess();
348                     break;
349                 case MSG_HOLD:
350                     performHold();
351                     break;
352                 case MSG_UNHOLD:
353                     performUnhold();
354                     break;
355             }
356         }
357     };
358 
359     private final Messenger mHandlerMessenger = new Messenger(mHandler);
360 
361     /**
362      * The underlying telephony Connection has been redialed on a different domain (CS or IMS).
363      * Track the new telephony Connection and set back up appropriate callbacks.
364      * @param connection The new telephony Connection associated with this TelephonyConnection.
365      */
366     @VisibleForTesting
onOriginalConnectionRedialed( com.android.internal.telephony.Connection connection)367     public void onOriginalConnectionRedialed(
368             com.android.internal.telephony.Connection connection) {
369         if (connection == null) {
370             setDisconnected(DisconnectCauseUtil
371                     .toTelecomDisconnectCause(DisconnectCause.OUT_OF_NETWORK,
372                             "handover failure, no connection"));
373             close();
374             return;
375         }
376         if (mOriginalConnection != null) {
377             if ((connection.getAddress() != null
378                     && mOriginalConnection.getAddress() != null
379                     && mOriginalConnection.getAddress().equals(connection.getAddress()))
380                     || connection.getState() == mOriginalConnection.getStateBeforeHandover()) {
381                 Log.i(TelephonyConnection.this, "Setting original connection after"
382                         + " handover or redial, current original connection="
383                         + mOriginalConnection.toString()
384                         + ", new original connection="
385                         + connection.toString());
386                 setOriginalConnection(connection);
387                 mWasImsConnection = false;
388                 if (mHangupDisconnectCause != DisconnectCause.NOT_VALID) {
389                     // A hangup request was initiated during the handover process, so
390                     // go ahead and initiate the hangup on the new connection.
391                     try {
392                         Log.i(TelephonyConnection.this, "user has tried to hangup "
393                                 + "during handover, retrying hangup.");
394                         connection.hangup();
395                     } catch (CallStateException e) {
396                         // Call state exception may be thrown if the connection was
397                         // already disconnected, so just log this case.
398                         Log.w(TelephonyConnection.this, "hangup during "
399                                 + "handover or redial resulted in an exception:" + e);
400                     }
401                 }
402             }
403         } else {
404             Log.w(TelephonyConnection.this, " mOriginalConnection==null --"
405                     + " invalid state (not cleaned up)");
406         }
407     }
408 
409     /**
410      * Handles {@link SuppServiceNotification}s pertinent to Telephony.
411      * @param ssn the notification.
412      */
handleSuppServiceNotification(SuppServiceNotification ssn)413     private void handleSuppServiceNotification(SuppServiceNotification ssn) {
414         Log.i(this, "handleSuppServiceNotification: type=%d, code=%d", ssn.notificationType,
415                 ssn.code);
416         if (ssn.notificationType == SuppServiceNotification.NOTIFICATION_TYPE_CODE_1
417                 && ssn.code == SuppServiceNotification.CODE_1_CALL_FORWARDED) {
418             sendTelephonyConnectionEvent(TelephonyManager.EVENT_CALL_FORWARDED, null);
419         }
420         sendSuppServiceNotificationEvent(ssn.notificationType, ssn.code);
421     }
422 
423     /**
424      * Sends a supplementary service notification connection event.
425      * This connection event includes the type and code, as well as a human readable message which
426      * is suitable for display to the user if the UI chooses to do so.
427      * @param type the {@link SuppServiceNotification#type}.
428      * @param code the {@link SuppServiceNotification#code}.
429      */
sendSuppServiceNotificationEvent(int type, int code)430     private void sendSuppServiceNotificationEvent(int type, int code) {
431         Bundle extras = new Bundle();
432         extras.putInt(TelephonyManager.EXTRA_NOTIFICATION_TYPE, type);
433         extras.putInt(TelephonyManager.EXTRA_NOTIFICATION_CODE, code);
434         extras.putCharSequence(TelephonyManager.EXTRA_NOTIFICATION_MESSAGE,
435                 getSuppServiceMessage(type, code));
436         sendTelephonyConnectionEvent(TelephonyManager.EVENT_SUPPLEMENTARY_SERVICE_NOTIFICATION,
437                 extras);
438     }
439 
440     /**
441      * Retrieves a human-readable message for a supplementary service notification.
442      * This message is suitable for display to the user.
443      * @param type the code group.
444      * @param code the code.
445      * @return A {@link CharSequence} containing the message, or {@code null} if none defined.
446      */
getSuppServiceMessage(int type, int code)447     private CharSequence getSuppServiceMessage(int type, int code) {
448         int messageId = -1;
449         if (type == SuppServiceNotification.NOTIFICATION_TYPE_CODE_1) {
450             switch (code) {
451                 case SuppServiceNotification.CODE_1_CALL_DEFLECTED:
452                     messageId = R.string.supp_service_notification_call_deflected;
453                     break;
454                 case SuppServiceNotification.CODE_1_CALL_FORWARDED:
455                     messageId = R.string.supp_service_notification_call_forwarded;
456                     break;
457                 case SuppServiceNotification.CODE_1_CALL_IS_WAITING:
458                     messageId = R.string.supp_service_notification_call_waiting;
459                     break;
460                 case SuppServiceNotification.CODE_1_CLIR_SUPPRESSION_REJECTED:
461                     messageId = R.string.supp_service_clir_suppression_rejected;
462                     break;
463                 case SuppServiceNotification.CODE_1_CUG_CALL:
464                     messageId = R.string.supp_service_closed_user_group_call;
465                     break;
466                 case SuppServiceNotification.CODE_1_INCOMING_CALLS_BARRED:
467                     messageId = R.string.supp_service_incoming_calls_barred;
468                     break;
469                 case SuppServiceNotification.CODE_1_OUTGOING_CALLS_BARRED:
470                     messageId = R.string.supp_service_outgoing_calls_barred;
471                     break;
472                 case SuppServiceNotification.CODE_1_SOME_CF_ACTIVE:
473                     // Intentional fall through.
474                 case SuppServiceNotification.CODE_1_UNCONDITIONAL_CF_ACTIVE:
475                     messageId = R.string.supp_service_call_forwarding_active;
476                     break;
477             }
478         } else if (type == SuppServiceNotification.NOTIFICATION_TYPE_CODE_2) {
479             switch (code) {
480                 case SuppServiceNotification.CODE_2_ADDITIONAL_CALL_FORWARDED:
481                     messageId = R.string.supp_service_additional_call_forwarded;
482                     break;
483                 case SuppServiceNotification.CODE_2_CALL_CONNECTED_ECT:
484                     messageId = R.string.supp_service_additional_ect_connected;
485                     break;
486                 case SuppServiceNotification.CODE_2_CALL_CONNECTING_ECT:
487                     messageId = R.string.supp_service_additional_ect_connecting;
488                     break;
489                 case SuppServiceNotification.CODE_2_CALL_ON_HOLD:
490                     messageId = R.string.supp_service_call_on_hold;
491                     break;
492                 case SuppServiceNotification.CODE_2_CALL_RETRIEVED:
493                     messageId = R.string.supp_service_call_resumed;
494                     break;
495                 case SuppServiceNotification.CODE_2_CUG_CALL:
496                     messageId = R.string.supp_service_closed_user_group_call;
497                     break;
498                 case SuppServiceNotification.CODE_2_DEFLECTED_CALL:
499                     messageId = R.string.supp_service_deflected_call;
500                     break;
501                 case SuppServiceNotification.CODE_2_FORWARDED_CALL:
502                     messageId = R.string.supp_service_forwarded_call;
503                     break;
504                 case SuppServiceNotification.CODE_2_MULTI_PARTY_CALL:
505                     messageId = R.string.supp_service_conference_call;
506                     break;
507                 case SuppServiceNotification.CODE_2_ON_HOLD_CALL_RELEASED:
508                     messageId = R.string.supp_service_held_call_released;
509                     break;
510             }
511         }
512         if (messageId != -1 && getPhone() != null && getPhone().getContext() != null) {
513             return getResourceText(messageId);
514         } else {
515             return null;
516         }
517     }
518 
519     @VisibleForTesting
getResourceText(int id)520     public CharSequence getResourceText(int id) {
521         Resources resources = SubscriptionManager.getResourcesForSubId(getPhone().getContext(),
522                 getPhone().getSubId());
523         return resources.getText(id);
524     }
525 
526     @VisibleForTesting
getResourceString(int id)527     public String getResourceString(int id) {
528         Resources resources = SubscriptionManager.getResourcesForSubId(getPhone().getContext(),
529                 getPhone().getSubId());
530         return resources.getString(id);
531     }
532 
533     /**
534      * @return {@code true} if carrier video conferencing is supported, {@code false} otherwise.
535      */
isCarrierVideoConferencingSupported()536     public boolean isCarrierVideoConferencingSupported() {
537         return mIsCarrierVideoConferencingSupported;
538     }
539 
540     /**
541      * A listener/callback mechanism that is specific communication from TelephonyConnections
542      * to TelephonyConnectionService (for now). It is more specific that Connection.Listener
543      * because it is only exposed in Telephony.
544      */
545     public abstract static class TelephonyConnectionListener {
onOriginalConnectionConfigured(TelephonyConnection c)546         public void onOriginalConnectionConfigured(TelephonyConnection c) {}
onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure)547         public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {}
onConferenceParticipantsChanged(Connection c, List<ConferenceParticipant> participants)548         public void onConferenceParticipantsChanged(Connection c,
549                 List<ConferenceParticipant> participants) {}
onConferenceStarted()550         public void onConferenceStarted() {}
onConferenceSupportedChanged(Connection c, boolean isConferenceSupported)551         public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
552 
onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities)553         public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {}
onConnectionEvent(Connection c, String event, Bundle extras)554         public void onConnectionEvent(Connection c, String event, Bundle extras) {}
onConnectionPropertiesChanged(Connection c, int connectionProperties)555         public void onConnectionPropertiesChanged(Connection c, int connectionProperties) {}
onExtrasChanged(Connection c, Bundle extras)556         public void onExtrasChanged(Connection c, Bundle extras) {}
onExtrasRemoved(Connection c, List<String> keys)557         public void onExtrasRemoved(Connection c, List<String> keys) {}
onStateChanged(android.telecom.Connection c, int state)558         public void onStateChanged(android.telecom.Connection c, int state) {}
onStatusHintsChanged(Connection c, StatusHints statusHints)559         public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
onDestroyed(Connection c)560         public void onDestroyed(Connection c) {}
onDisconnected(android.telecom.Connection c, android.telecom.DisconnectCause disconnectCause)561         public void onDisconnected(android.telecom.Connection c,
562                 android.telecom.DisconnectCause disconnectCause) {}
onVideoProviderChanged(android.telecom.Connection c, Connection.VideoProvider videoProvider)563         public void onVideoProviderChanged(android.telecom.Connection c,
564                 Connection.VideoProvider videoProvider) {}
onVideoStateChanged(android.telecom.Connection c, int videoState)565         public void onVideoStateChanged(android.telecom.Connection c, int videoState) {}
onRingbackRequested(Connection c, boolean ringback)566         public void onRingbackRequested(Connection c, boolean ringback) {}
567     }
568 
569     public static class D2DCallStateAdapter extends TelephonyConnectionListener {
570         private Communicator mCommunicator;
571 
D2DCallStateAdapter(Communicator communicator)572         D2DCallStateAdapter(Communicator communicator) {
573             mCommunicator = communicator;
574         }
575 
576         @Override
onStateChanged(android.telecom.Connection c, int state)577         public void onStateChanged(android.telecom.Connection c, int state) {
578             mCommunicator.onStateChanged(c.getTelecomCallId(), state);
579         }
580     }
581 
582     private final PostDialListener mPostDialListener = new PostDialListener() {
583         @Override
584         public void onPostDialWait() {
585             Log.v(TelephonyConnection.this, "onPostDialWait");
586             if (mOriginalConnection != null) {
587                 setPostDialWait(mOriginalConnection.getRemainingPostDialString());
588             }
589         }
590 
591         @Override
592         public void onPostDialChar(char c) {
593             Log.v(TelephonyConnection.this, "onPostDialChar: %s", c);
594             if (mOriginalConnection != null) {
595                 setNextPostDialChar(c);
596             }
597         }
598     };
599 
600     /**
601      * Listener for listening to events in the {@link com.android.internal.telephony.Connection}.
602      */
603     private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener =
604             new com.android.internal.telephony.Connection.ListenerBase() {
605         @Override
606         public void onVideoStateChanged(int videoState) {
607             mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget();
608         }
609 
610         /*
611          * The {@link com.android.internal.telephony.Connection} has reported a change in
612          * connection capability.
613          * @param capabilities bit mask containing voice or video or both capabilities.
614          */
615         @Override
616         public void onConnectionCapabilitiesChanged(int capabilities) {
617             mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES,
618                     capabilities, 0).sendToTarget();
619         }
620 
621         /**
622          * The {@link com.android.internal.telephony.Connection} has reported a change in the
623          * video call provider.
624          *
625          * @param videoProvider The video call provider.
626          */
627         @Override
628         public void onVideoProviderChanged(VideoProvider videoProvider) {
629             mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, videoProvider).sendToTarget();
630         }
631 
632         /**
633          * Used by {@link com.android.internal.telephony.Connection} to report a change for
634          * the call radio technology.
635          *
636          * @param vrat the RIL Voice Radio Technology used for current connection.
637          */
638         @Override
639         public void onCallRadioTechChanged(@RilRadioTechnology int vrat) {
640             mHandler.obtainMessage(MSG_SET_CALL_RADIO_TECH, vrat).sendToTarget();
641         }
642 
643         /**
644          * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
645          * audio quality for the current call.
646          *
647          * @param audioQuality The audio quality.
648          */
649         @Override
650         public void onAudioQualityChanged(int audioQuality) {
651             mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget();
652         }
653 
654         @Override
655         public void onMediaAttributesChanged() {
656             mHandler.obtainMessage(MSG_MEDIA_ATTRIBUTES_CHANGED).sendToTarget();
657         }
658 
659         /**
660          * Handles a change in the state of conference participant(s), as reported by the
661          * {@link com.android.internal.telephony.Connection}.
662          *
663          * @param participants The participant(s) which changed.
664          */
665         @Override
666         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
667             mHandler.obtainMessage(MSG_SET_CONFERENCE_PARTICIPANTS, participants).sendToTarget();
668         }
669 
670         /*
671          * Handles a change to the multiparty state for this connection.
672          *
673          * @param isMultiParty {@code true} if the call became multiparty, {@code false}
674          *      otherwise.
675          */
676         @Override
677         public void onMultipartyStateChanged(boolean isMultiParty) {
678             handleMultipartyStateChange(isMultiParty);
679         }
680 
681         /**
682          * Handles the event that the request to merge calls failed.
683          */
684         @Override
685         public void onConferenceMergedFailed() {
686             handleConferenceMergeFailed();
687         }
688 
689         @Override
690         public void onExtrasChanged(Bundle extras) {
691             mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, extras).sendToTarget();
692         }
693 
694         /**
695          * Handles the phone exiting ECM mode by updating the connection capabilities.  During an
696          * ongoing call, if ECM mode is exited, we will re-enable mute for CDMA calls.
697          */
698         @Override
699         public void onExitedEcmMode() {
700             handleExitedEcmMode();
701         }
702 
703         /**
704          * Called from {@link ImsPhoneCallTracker} when a request to pull an external call has
705          * failed.
706          * @param externalConnection
707          */
708         @Override
709         public void onCallPullFailed(com.android.internal.telephony.Connection externalConnection) {
710             if (externalConnection == null) {
711                 return;
712             }
713 
714             Log.i(this, "onCallPullFailed - pull failed; swapping back to call: %s",
715                     externalConnection);
716 
717             // Inform the InCallService of the fact that the call pull failed (it may choose to
718             // display a message informing the user of the pull failure).
719             sendTelephonyConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, null);
720 
721             // Swap the ImsPhoneConnection we used to do the pull for the ImsExternalConnection
722             // which originally represented the call.
723             setOriginalConnection(externalConnection);
724 
725             // Set our state to active again since we're no longer pulling.
726             setActiveInternal();
727         }
728 
729         /**
730          * Called from {@link ImsPhoneCallTracker} when a handover to WIFI has failed.
731          */
732         @Override
733         public void onHandoverToWifiFailed() {
734             sendTelephonyConnectionEvent(TelephonyManager.EVENT_HANDOVER_TO_WIFI_FAILED, null);
735         }
736 
737         /**
738          * Informs the {@link android.telecom.ConnectionService} of a connection event raised by the
739          * original connection.
740          * @param event The connection event.
741          * @param extras The extras.
742          */
743         @Override
744         public void onConnectionEvent(String event, Bundle extras) {
745             SomeArgs args = SomeArgs.obtain();
746             args.arg1 = event;
747             args.arg2 = extras;
748             if (EVENT_MERGE_COMPLETE.equals(event)){
749                 // To ensure the MERGE_COMPLETE event logs before the listeners are removed,
750                 // circumvent the handler by sending the connection event directly:
751                 sendTelephonyConnectionEvent(event, extras);
752             } else {
753                 mHandler.obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget();
754             }
755         }
756 
757         @Override
758         public void onRttModifyRequestReceived() {
759             sendRemoteRttRequest();
760         }
761 
762         @Override
763         public void onRttModifyResponseReceived(int status) {
764             updateConnectionProperties();
765             refreshConferenceSupported();
766             if (status == RttModifyStatus.SESSION_MODIFY_REQUEST_SUCCESS) {
767                 sendRttInitiationSuccess();
768             } else {
769                 sendRttInitiationFailure(status);
770             }
771         }
772 
773         @Override
774         public void onDisconnect(int cause) {
775             Log.i(this, "onDisconnect: callId=%s, cause=%s", getTelecomCallId(),
776                     DisconnectCause.toString(cause));
777             mHandler.obtainMessage(MSG_DISCONNECT).sendToTarget();
778         }
779 
780         @Override
781         public void onRttInitiated() {
782             Log.i(TelephonyConnection.this, "onRttInitiated: callId=%s", getTelecomCallId());
783             // Post RTT initiation to the Handler associated with this TelephonyConnection.
784             // This avoids a race condition where a call starts as RTT but ConnectionService call to
785             // handleCreateConnectionComplete happens AFTER the RTT status is reported to Telecom.
786             mHandler.obtainMessage(MSG_ON_RTT_INITIATED).sendToTarget();
787         }
788 
789         @Override
790         public void onRttTerminated() {
791             updateConnectionProperties();
792             refreshConferenceSupported();
793             sendRttSessionRemotelyTerminated();
794         }
795 
796         @Override
797         public void onOriginalConnectionReplaced(
798                 com.android.internal.telephony.Connection newConnection) {
799             Log.i(TelephonyConnection.this, "onOriginalConnectionReplaced; newConn=%s",
800                     newConnection);
801             setOriginalConnection(newConnection);
802         }
803 
804         @Override
805         public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall) {
806             setIsNetworkIdentifiedEmergencyCall(isEmergencyCall);
807         }
808 
809         /**
810          * Indicates data from an RTP header extension has been received from the network.
811          * @param extensionData The extension data.
812          */
813         @Override
814         public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData) {
815             if (mRtpTransport == null) {
816                 return;
817             }
818             Log.i(this, "onReceivedRtpHeaderExtensions: received %d extensions",
819                     extensionData.size());
820             mRtpTransport.onRtpHeaderExtensionsReceived(extensionData);
821         }
822 
823         @Override
824         public void onReceivedDtmfDigit(char digit) {
825             if (mDtmfTransport == null) {
826                 return;
827             }
828             Log.i(this, "onReceivedDtmfDigit: digit=%c", digit);
829             mDtmfTransport.onDtmfReceived(digit);
830         }
831 
832         @Override
833         public void onAudioModeIsVoipChanged(int imsAudioHandler) {
834             boolean isVoip = imsAudioHandler == MmTelFeature.AUDIO_HANDLER_ANDROID;
835             Log.i(this, "onAudioModeIsVoipChanged isVoip =" + isVoip);
836             setAudioModeIsVoip(isVoip);
837         }
838     };
839 
840     private TelephonyConnectionService mTelephonyConnectionService;
841     protected com.android.internal.telephony.Connection mOriginalConnection;
842     private Phone mPhoneForEvents;
843     private Call.State mConnectionState = Call.State.IDLE;
844     private Bundle mOriginalConnectionExtras = new Bundle();
845     private boolean mIsStateOverridden = false;
846     private Call.State mOriginalConnectionState = Call.State.IDLE;
847     private Call.State mConnectionOverriddenState = Call.State.IDLE;
848     private RttTextStream mRttTextStream = null;
849 
850     private boolean mWasImsConnection;
851     private boolean mWasCrossSim;
852 
853     /**
854      * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected.
855      */
856     private boolean mIsMultiParty = false;
857 
858     /**
859      * The {@link com.android.internal.telephony.Connection} capabilities associated with the
860      * current {@link #mOriginalConnection}.
861      */
862     private int mOriginalConnectionCapabilities;
863 
864     /**
865      * Determines the audio quality is high for the {@link TelephonyConnection}.
866      * This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to
867      * indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
868      */
869     private boolean mHasHighDefAudio;
870 
871     /**
872      * Indicates that the connection should be treated as an emergency call because the
873      * number dialed matches an internal list of emergency numbers. Does not guarantee whether
874      * the network will treat the call as an emergency call.
875      */
876     private boolean mTreatAsEmergencyCall;
877 
878     /**
879      * Indicates whether the network has identified this call as an emergency call.  Where
880      * {@link #mTreatAsEmergencyCall} is based on comparing dialed numbers to a list of known
881      * emergency numbers, this property is based on whether the network itself has identified the
882      * call as an emergency call (which can be the case for an incoming call from emergency
883      * services).
884      */
885     private boolean mIsNetworkIdentifiedEmergencyCall;
886 
887     /**
888      * For video calls, indicates whether the outgoing video for the call can be paused using
889      * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
890      */
891     private boolean mIsVideoPauseSupported;
892 
893     /**
894      * Indicates whether this connection supports being a part of a conference..
895      */
896     private boolean mIsConferenceSupported;
897 
898     /**
899      * Indicates whether managing conference call is supported after this connection being
900      * a part of a IMS conference.
901      */
902     private boolean mIsManageImsConferenceCallSupported;
903 
904     /**
905      * Indicates whether the carrier supports video conferencing; captures the current state of the
906      * carrier config
907      * {@link android.telephony.CarrierConfigManager#KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL}.
908      */
909     private boolean mIsCarrierVideoConferencingSupported;
910 
911     /**
912      * Indicates whether or not this connection has CDMA Enhanced Voice Privacy enabled.
913      */
914     private boolean mIsCdmaVoicePrivacyEnabled;
915 
916     /**
917      * Indicates whether the connection can be held. This filed combined with the state of the
918      * connection can determine whether {@link Connection#CAPABILITY_HOLD} should be added to the
919      * connection.
920      */
921     private boolean mIsHoldable;
922 
923     /**
924      * Indicates whether TTY is enabled; used to determine whether a call is VT capable.
925      */
926     private boolean mIsTtyEnabled;
927 
928     /**
929      * Indicates whether this call is using assisted dialing.
930      */
931     private boolean mIsUsingAssistedDialing;
932 
933     /**
934      * Indicates whether this connection supports showing preciese call failed cause.
935      */
936     private boolean mShowPreciseFailedCause;
937 
938     /**
939      * Provides a DisconnectCause associated with a hang up request.
940      */
941     private int mHangupDisconnectCause = DisconnectCause.NOT_VALID;
942 
943     /**
944      * Provides a means for a {@link Communicator} to be informed of call state changes.
945      */
946     private D2DCallStateAdapter mD2DCallStateAdapter;
947 
948     private RtpTransport mRtpTransport;
949 
950     private DtmfTransport mDtmfTransport;
951 
952     /**
953      * Facilitates device to device communication.
954      */
955     private Communicator mCommunicator;
956 
957     /**
958      * Listeners to our TelephonyConnection specific callbacks
959      */
960     private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
961             new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
962 
963     private Integer mEmergencyServiceCategory = null;
964 
TelephonyConnection(com.android.internal.telephony.Connection originalConnection, String callId, @android.telecom.Call.Details.CallDirection int callDirection)965     protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
966             String callId, @android.telecom.Call.Details.CallDirection int callDirection) {
967         setCallDirection(callDirection);
968         setTelecomCallId(callId);
969         if (originalConnection != null) {
970             setOriginalConnection(originalConnection);
971         }
972     }
973 
974     @VisibleForTesting
TelephonyConnection()975     protected TelephonyConnection() {
976         // Do nothing
977     }
978 
979     @Override
onCallEvent(String event, Bundle extras)980     public void onCallEvent(String event, Bundle extras) {
981         switch (event) {
982             case Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE:
983                 // A Device to device message is being sent by a CallDiagnosticService.
984                 handleOutgoingDeviceToDeviceMessage(extras);
985                 break;
986             default:
987                 break;
988         }
989 
990     }
991     /**
992      * Creates a clone of the current {@link TelephonyConnection}.
993      *
994      * @return The clone.
995      */
cloneConnection()996     public abstract TelephonyConnection cloneConnection();
997 
998     @Override
onCallAudioStateChanged(CallAudioState audioState)999     public void onCallAudioStateChanged(CallAudioState audioState) {
1000         // TODO: update TTY mode.
1001         if (getPhone() != null) {
1002             getPhone().setEchoSuppressionEnabled();
1003         }
1004     }
1005 
1006     @Override
onStateChanged(int state)1007     public void onStateChanged(int state) {
1008         Log.v(this, "onStateChanged, state: " + Connection.stateToString(state));
1009         updateStatusHints();
1010     }
1011 
1012     @Override
onDisconnect()1013     public void onDisconnect() {
1014         Log.v(this, "onDisconnect");
1015         mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.LOCAL).sendToTarget();
1016     }
1017 
1018     /**
1019      * Notifies this Connection of a request to disconnect a participant of the conference managed
1020      * by the connection.
1021      *
1022      * @param endpoint the {@link Uri} of the participant to disconnect.
1023      */
1024     @Override
onDisconnectConferenceParticipant(Uri endpoint)1025     public void onDisconnectConferenceParticipant(Uri endpoint) {
1026         Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
1027 
1028         if (mOriginalConnection == null) {
1029             return;
1030         }
1031 
1032         mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
1033     }
1034 
1035     @Override
onSeparate()1036     public void onSeparate() {
1037         Log.v(this, "onSeparate");
1038         if (mOriginalConnection != null) {
1039             try {
1040                 mOriginalConnection.separate();
1041             } catch (CallStateException e) {
1042                 Log.e(this, e, "Call to Connection.separate failed with exception");
1043             }
1044         }
1045     }
1046 
1047     @Override
onAddConferenceParticipants(List<Uri> participants)1048     public void onAddConferenceParticipants(List<Uri> participants) {
1049         performAddConferenceParticipants(participants);
1050     }
1051 
1052     @Override
onAbort()1053     public void onAbort() {
1054         Log.v(this, "onAbort");
1055         mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.LOCAL).sendToTarget();
1056     }
1057 
1058     @Override
onHold()1059     public void onHold() {
1060         mHandler.obtainMessage(MSG_HOLD).sendToTarget();
1061     }
1062 
1063     @Override
onUnhold()1064     public void onUnhold() {
1065         mHandler.obtainMessage(MSG_UNHOLD).sendToTarget();
1066     }
1067 
1068     @Override
onAnswer(int videoState)1069     public void onAnswer(int videoState) {
1070         performAnswer(videoState);
1071     }
1072 
1073     @Override
onDeflect(Uri address)1074     public void onDeflect(Uri address) {
1075         Log.v(this, "onDeflect");
1076         if (mOriginalConnection != null && isValidRingingCall()) {
1077             if (address == null) {
1078                 Log.w(this, "call deflect address uri is null");
1079                 return;
1080             }
1081             String scheme = address.getScheme();
1082             String deflectNumber = "";
1083             String uriString = address.getSchemeSpecificPart();
1084             if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
1085                 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
1086                     Log.w(this, "onDeflect, address scheme is not of type tel instead: " +
1087                             scheme);
1088                     return;
1089                 }
1090                 if (PhoneNumberUtils.isUriNumber(uriString)) {
1091                     Log.w(this, "Invalid deflect address. Not a legal PSTN number.");
1092                     return;
1093                 }
1094                 deflectNumber = PhoneNumberUtils.convertAndStrip(uriString);
1095                 if (TextUtils.isEmpty(deflectNumber)) {
1096                     Log.w(this, "Empty deflect number obtained from address uri");
1097                     return;
1098                 }
1099             } else {
1100                 Log.w(this, "Cannot deflect to voicemail uri");
1101                 return;
1102             }
1103 
1104             try {
1105                 mOriginalConnection.deflect(deflectNumber);
1106             } catch (CallStateException e) {
1107                 Log.e(this, e, "Failed to deflect call.");
1108             }
1109         }
1110     }
1111 
1112     @Override
onReject()1113     public void onReject() {
1114         performReject(android.telecom.Call.REJECT_REASON_DECLINED);
1115     }
1116 
1117     @Override
onReject(@ndroid.telecom.Call.RejectReason int rejectReason)1118     public void onReject(@android.telecom.Call.RejectReason int rejectReason) {
1119         performReject(rejectReason);
1120     }
1121 
performReject(int rejectReason)1122     public void performReject(int rejectReason) {
1123         Log.v(this, "performReject");
1124         if (isValidRingingCall()) {
1125             mHandler.obtainMessage(MSG_REJECT, rejectReason)
1126                     .sendToTarget();
1127         }
1128         super.onReject();
1129     }
1130 
1131     @Override
onTransfer(Uri number, boolean isConfirmationRequired)1132     public void onTransfer(Uri number, boolean isConfirmationRequired) {
1133         Log.v(this, "onTransfer");
1134         if (mOriginalConnection != null) {
1135             if (number == null) {
1136                 Log.w(this, "call transfer uri is null");
1137                 return;
1138             }
1139             String scheme = number.getScheme();
1140             String transferNumber = "";
1141             String uriString = number.getSchemeSpecificPart();
1142             if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
1143                 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
1144                     Log.w(this, "onTransfer, number scheme is not of type tel instead: "
1145                             + scheme);
1146                     return;
1147                 }
1148                 if (PhoneNumberUtils.isUriNumber(uriString)) {
1149                     Log.w(this, "Invalid transfer address. Not a legal PSTN number.");
1150                     return;
1151                 }
1152                 transferNumber = PhoneNumberUtils.convertAndStrip(uriString);
1153                 if (TextUtils.isEmpty(transferNumber)) {
1154                     Log.w(this, "Empty transfer number obtained from uri");
1155                     return;
1156                 }
1157             } else {
1158                 Log.w(this, "Cannot transfer to voicemail uri");
1159                 return;
1160             }
1161 
1162             try {
1163                 mOriginalConnection.transfer(transferNumber, isConfirmationRequired);
1164             } catch (CallStateException e) {
1165                 Log.e(this, e, "Failed to transfer call.");
1166             }
1167         }
1168     }
1169 
1170     @Override
onTransfer(Connection otherConnection)1171     public void onTransfer(Connection otherConnection) {
1172         Log.v(this, "onConsultativeTransfer");
1173         if (mOriginalConnection != null && (otherConnection instanceof TelephonyConnection)) {
1174             try {
1175                 mOriginalConnection.consultativeTransfer(
1176                         ((TelephonyConnection) otherConnection).getOriginalConnection());
1177             } catch (CallStateException e) {
1178                 Log.e(this, e, "Failed to transfer call.");
1179             }
1180         }
1181     }
1182 
1183     @Override
onPostDialContinue(boolean proceed)1184     public void onPostDialContinue(boolean proceed) {
1185         Log.v(this, "onPostDialContinue, proceed: " + proceed);
1186         if (mOriginalConnection != null) {
1187             if (proceed) {
1188                 mOriginalConnection.proceedAfterWaitChar();
1189             } else {
1190                 mOriginalConnection.cancelPostDial();
1191             }
1192         }
1193     }
1194 
1195     /**
1196      * Handles requests to pull an external call.
1197      */
1198     @Override
onPullExternalCall()1199     public void onPullExternalCall() {
1200         if ((getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) !=
1201                 Connection.PROPERTY_IS_EXTERNAL_CALL) {
1202             Log.w(this, "onPullExternalCall - cannot pull non-external call");
1203             return;
1204         }
1205 
1206         if (mOriginalConnection != null) {
1207             mOriginalConnection.pullExternalCall();
1208         }
1209     }
1210 
1211     @Override
onStartRtt(RttTextStream textStream)1212     public void onStartRtt(RttTextStream textStream) {
1213         if (isImsConnection()) {
1214             ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
1215             if (originalConnection.isRttEnabledForCall()) {
1216                 originalConnection.setCurrentRttTextStream(textStream);
1217             } else {
1218                 originalConnection.startRtt(textStream);
1219             }
1220         } else {
1221             Log.w(this, "onStartRtt - not in IMS, so RTT cannot be enabled.");
1222         }
1223     }
1224 
1225     @Override
onStopRtt()1226     public void onStopRtt() {
1227         if (isImsConnection()) {
1228             ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
1229             if (originalConnection.isRttEnabledForCall()) {
1230                 originalConnection.stopRtt();
1231             } else {
1232                 Log.w(this, "onStopRtt - not in RTT call, ignoring");
1233             }
1234         } else {
1235             Log.w(this, "onStopRtt - not in IMS, ignoring");
1236         }
1237     }
1238 
1239     @Override
onCallFilteringCompleted(CallFilteringCompletionInfo callFilteringCompletionInfo)1240     public void onCallFilteringCompleted(CallFilteringCompletionInfo callFilteringCompletionInfo) {
1241         // Check what the call screening service has to say, if it's a system dialer.
1242         boolean isAllowedToDisplayPicture;
1243         String callScreeningPackage =
1244                 callFilteringCompletionInfo.getCallScreeningComponent() == null
1245                         ? null
1246                         : callFilteringCompletionInfo.getCallScreeningComponent().getPackageName();
1247         boolean isResponseFromSystemDialer =
1248                 Objects.equals(getPhone().getContext()
1249                         .getSystemService(TelecomManager.class).getSystemDialerPackage(),
1250                         callScreeningPackage);
1251         CallScreeningService.CallResponse callScreeningResponse =
1252                 callFilteringCompletionInfo.getCallResponse();
1253 
1254         if (isResponseFromSystemDialer && callScreeningResponse != null
1255                 && callScreeningResponse.getCallComposerAttachmentsToShow() >= 0) {
1256             isAllowedToDisplayPicture = (callScreeningResponse.getCallComposerAttachmentsToShow()
1257                     & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PICTURE) != 0;
1258         } else {
1259             isAllowedToDisplayPicture = callFilteringCompletionInfo.isInContacts();
1260         }
1261 
1262         if (isImsConnection()) {
1263             ImsPhone imsPhone = (getPhone() instanceof ImsPhone) ? (ImsPhone) getPhone() : null;
1264             if (imsPhone != null
1265                     && imsPhone.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON
1266                     && !callFilteringCompletionInfo.isBlocked() && isAllowedToDisplayPicture) {
1267                 ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
1268                 ImsCallProfile profile = originalConnection.getImsCall().getCallProfile();
1269                 String serverUrl = CallComposerPictureManager.sTestMode
1270                         ? CallComposerPictureManager.FAKE_SERVER_URL
1271                         : profile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL);
1272                 if (profile != null
1273                         && !TextUtils.isEmpty(serverUrl)) {
1274                     CallComposerPictureManager manager = CallComposerPictureManager
1275                             .getInstance(getPhone().getContext(), getPhone().getSubId());
1276                     manager.handleDownloadFromServer(new CallComposerPictureTransfer.Factory() {},
1277                             serverUrl,
1278                             (result) -> {
1279                                 if (result.first != null) {
1280                                     Bundle newExtras = new Bundle();
1281                                     newExtras.putParcelable(TelecomManager.EXTRA_PICTURE_URI,
1282                                             result.first);
1283                                     putTelephonyExtras(newExtras);
1284                                 } else {
1285                                     Log.i(this, "Call composer picture download:"
1286                                             + " error=" + result.second);
1287                                     Bundle newExtras = new Bundle();
1288                                     newExtras.putBoolean(TelecomManager.EXTRA_HAS_PICTURE, false);
1289                                     putTelephonyExtras(newExtras);
1290                                 }
1291                             });
1292                 }
1293             }
1294         }
1295     }
1296 
1297     @Override
handleRttUpgradeResponse(RttTextStream textStream)1298     public void handleRttUpgradeResponse(RttTextStream textStream) {
1299         if (!isImsConnection()) {
1300             Log.w(this, "handleRttUpgradeResponse - not in IMS, so RTT cannot be enabled.");
1301             return;
1302         }
1303         ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
1304         originalConnection.sendRttModifyResponse(textStream);
1305     }
1306 
performAnswer(int videoState)1307     public void performAnswer(int videoState) {
1308         Log.v(this, "performAnswer");
1309         if (isValidRingingCall() && getPhone() != null) {
1310             try {
1311                 mTelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
1312                         getPhoneAccountHandle());
1313                 getPhone().acceptCall(videoState);
1314             } catch (CallStateException e) {
1315                 Log.e(this, e, "Failed to accept call.");
1316             }
1317         }
1318     }
1319 
performHold()1320     public void performHold() {
1321         Log.v(this, "performHold");
1322         // TODO: Can dialing calls be put on hold as well since they take up the
1323         // foreground call slot?
1324         if (Call.State.ACTIVE == mConnectionState) {
1325             Log.v(this, "Holding active call");
1326             try {
1327                 Phone phone = mOriginalConnection.getCall().getPhone();
1328 
1329                 Call ringingCall = phone.getRingingCall();
1330 
1331                 // Although the method says switchHoldingAndActive, it eventually calls a RIL method
1332                 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
1333                 // a call on hold while a call-waiting call exists, it'll end up accepting the
1334                 // call-waiting call, which is bad if that was not the user's intention. We are
1335                 // cheating here and simply skipping it because we know any attempt to hold a call
1336                 // while a call-waiting call is happening is likely a request from Telecom prior to
1337                 // accepting the call-waiting call.
1338                 // TODO: Investigate a better solution. It would be great here if we
1339                 // could "fake" hold by silencing the audio and microphone streams for this call
1340                 // instead of actually putting it on hold.
1341                 if (ringingCall.getState() != Call.State.WAITING) {
1342                     // New behavior for IMS -- don't use the clunky switchHoldingAndActive logic.
1343                     if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
1344                         ImsPhone imsPhone = (ImsPhone) phone;
1345                         imsPhone.holdActiveCall();
1346                         mTelephonyConnectionService.maybeUnholdCallsOnOtherSubs(
1347                                 getPhoneAccountHandle());
1348                         return;
1349                     }
1350                     phone.switchHoldingAndActive();
1351                 }
1352 
1353                 // TODO: Cdma calls are slightly different.
1354             } catch (CallStateException e) {
1355                 Log.e(this, e, "Exception occurred while trying to put call on hold.");
1356             }
1357         } else {
1358             Log.w(this, "Cannot put a call that is not currently active on hold.");
1359         }
1360     }
1361 
performUnhold()1362     public void performUnhold() {
1363         Log.v(this, "performUnhold");
1364         if (Call.State.HOLDING == mConnectionState) {
1365             try {
1366                 Phone phone = mOriginalConnection.getCall().getPhone();
1367                 // New behavior for IMS -- don't use the clunky switchHoldingAndActive logic.
1368                 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
1369                     ImsPhone imsPhone = (ImsPhone) phone;
1370                     imsPhone.unholdHeldCall();
1371                     return;
1372                 }
1373                 // Here's the deal--Telephony hold/unhold is weird because whenever there exists
1374                 // more than one call, one of them must always be active. In other words, if you
1375                 // have an active call and holding call, and you put the active call on hold, it
1376                 // will automatically activate the holding call. This is weird with how Telecom
1377                 // sends its commands. When a user opts to "unhold" a background call, telecom
1378                 // issues hold commands to all active calls, and then the unhold command to the
1379                 // background call. This means that we get two commands...each of which reduces to
1380                 // switchHoldingAndActive(). The result is that they simply cancel each other out.
1381                 // To fix this so that it works well with telecom we add a minor hack. If we
1382                 // have one telephony call, everything works as normally expected. But if we have
1383                 // two or more calls, we will ignore all requests to "unhold" knowing that the hold
1384                 // requests already do what we want. If you've read up to this point, I'm very sorry
1385                 // that we are doing this. I didn't think of a better solution that wouldn't also
1386                 // make the Telecom APIs very ugly.
1387 
1388                 if (!hasMultipleTopLevelCalls()) {
1389                     mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
1390                 } else {
1391                     Log.i(this, "Skipping unhold command for %s", this);
1392                 }
1393             } catch (CallStateException e) {
1394                 Log.e(this, e, "Exception occurred while trying to release call from hold.");
1395             }
1396         } else {
1397             Log.w(this, "Cannot release a call that is not already on hold from hold.");
1398         }
1399     }
1400 
performConference(Connection otherConnection)1401     public void performConference(Connection otherConnection) {
1402         Log.d(this, "performConference - %s", this);
1403         if (getPhone() != null) {
1404             try {
1405                 // We dont use the "other" connection because there is no concept of that in the
1406                 // implementation of calls inside telephony. Basically, you can "conference" and it
1407                 // will conference with the background call.  We know that otherConnection is the
1408                 // background call because it would never have called setConferenceableConnections()
1409                 // otherwise.
1410                 getPhone().conference();
1411             } catch (CallStateException e) {
1412                 Log.e(this, e, "Failed to conference call.");
1413             }
1414         }
1415     }
1416 
getAddConferenceParticipants(List<Uri> participants)1417     private String[] getAddConferenceParticipants(List<Uri> participants) {
1418         String[] addConfParticipants = new String[participants.size()];
1419         int i = 0;
1420         for (Uri participant : participants) {
1421            addConfParticipants[i] = participant.getSchemeSpecificPart();
1422            i++;
1423         }
1424         return addConfParticipants;
1425     }
1426 
performAddConferenceParticipants(List<Uri> participants)1427     public void performAddConferenceParticipants(List<Uri> participants) {
1428         Log.v(this, "performAddConferenceParticipants");
1429         if (mOriginalConnection.getCall() instanceof ImsPhoneCall) {
1430             ImsPhoneCall imsPhoneCall = (ImsPhoneCall)mOriginalConnection.getCall();
1431             try {
1432                 imsPhoneCall.getImsCall().inviteParticipants(
1433                         getAddConferenceParticipants(participants));
1434             } catch(ImsException e) {
1435                 Log.e(this, e, "failed to add conference participants");
1436             }
1437         }
1438     }
1439 
1440     /**
1441      * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based
1442      * capabilities.
1443      */
buildConnectionCapabilities()1444     protected int buildConnectionCapabilities() {
1445         int callCapabilities = 0;
1446         if (mOriginalConnection != null && mOriginalConnection.isIncoming()) {
1447             callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO;
1448         }
1449         if (!shouldTreatAsEmergencyCall() && isImsConnection() && canHoldImsCalls()) {
1450             callCapabilities |= CAPABILITY_SUPPORT_HOLD;
1451             if (mIsHoldable && (getState() == STATE_ACTIVE || getState() == STATE_HOLDING)) {
1452                 callCapabilities |= CAPABILITY_HOLD;
1453             }
1454         }
1455 
1456         Log.d(this, "buildConnectionCapabilities: isHoldable = "
1457                 + mIsHoldable + " State = " + getState() + " capabilities = " + callCapabilities);
1458 
1459         return callCapabilities;
1460     }
1461 
updateConnectionCapabilities()1462     protected final void updateConnectionCapabilities() {
1463         int newCapabilities = buildConnectionCapabilities();
1464 
1465         newCapabilities = applyOriginalConnectionCapabilities(newCapabilities);
1466         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO,
1467                 mIsVideoPauseSupported && isVideoCapable());
1468         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL,
1469                 isExternalConnection() && isPullable());
1470         newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
1471         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_SUPPORT_DEFLECT,
1472                 isImsConnection() && canDeflectImsCalls());
1473 
1474         newCapabilities = applyAddParticipantCapabilities(newCapabilities);
1475         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_TRANSFER_CONSULTATIVE,
1476                 isImsConnection() && canConsultativeTransfer());
1477         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_TRANSFER,
1478                 isImsConnection() && canTransferToNumber());
1479 
1480         if (getConnectionCapabilities() != newCapabilities) {
1481             setConnectionCapabilities(newCapabilities);
1482             notifyConnectionCapabilitiesChanged(newCapabilities);
1483         }
1484     }
1485 
buildConnectionProperties()1486     protected int buildConnectionProperties() {
1487         int connectionProperties = 0;
1488 
1489         // If the phone is in ECM mode, mark the call to indicate that the callback number should be
1490         // shown.
1491         Phone phone = getPhone();
1492         if (phone != null && phone.isInEcm()) {
1493             connectionProperties |= PROPERTY_EMERGENCY_CALLBACK_MODE;
1494         }
1495 
1496         return connectionProperties;
1497     }
1498 
1499     /**
1500      * Updates the properties of the connection.
1501      */
updateConnectionProperties()1502     protected final void updateConnectionProperties() {
1503         int newProperties = buildConnectionProperties();
1504 
1505         newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO,
1506                 hasHighDefAudioProperty());
1507         newProperties = changeBitmask(newProperties, PROPERTY_WIFI, isWifi() && !isCrossSimCall());
1508         newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL,
1509                 isExternalConnection());
1510         newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY,
1511                 mIsCdmaVoicePrivacyEnabled);
1512         newProperties = changeBitmask(newProperties, PROPERTY_ASSISTED_DIALING,
1513                 mIsUsingAssistedDialing);
1514         newProperties = changeBitmask(newProperties, PROPERTY_IS_RTT, isRtt());
1515         newProperties = changeBitmask(newProperties, PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
1516                 isNetworkIdentifiedEmergencyCall());
1517         newProperties = changeBitmask(newProperties, PROPERTY_IS_ADHOC_CONFERENCE,
1518                 isAdhocConferenceCall());
1519         newProperties = changeBitmask(newProperties, PROPERTY_CROSS_SIM,
1520                 isCrossSimCall());
1521 
1522         if (getConnectionProperties() != newProperties) {
1523             setTelephonyConnectionProperties(newProperties);
1524         }
1525     }
1526 
setTelephonyConnectionProperties(int newProperties)1527     public void setTelephonyConnectionProperties(int newProperties) {
1528         setConnectionProperties(newProperties);
1529         notifyConnectionPropertiesChanged(newProperties);
1530     }
1531 
updateAddress()1532     protected final void updateAddress() {
1533         updateConnectionCapabilities();
1534         updateConnectionProperties();
1535         if (mOriginalConnection != null) {
1536             Uri address;
1537             if (isShowingOriginalDialString()
1538                     && mOriginalConnection.getOrigDialString() != null) {
1539                 address = getAddressFromNumber(mOriginalConnection.getOrigDialString());
1540             } else if (isNeededToFormatIncomingNumberForJp()) {
1541                 address = getAddressFromNumber(
1542                         formatIncomingNumberForJp(mOriginalConnection.getAddress()));
1543             } else {
1544                 address = getAddressFromNumber(mOriginalConnection.getAddress());
1545             }
1546             int presentation = mOriginalConnection.getNumberPresentation();
1547             if (!Objects.equals(address, getAddress()) ||
1548                     presentation != getAddressPresentation()) {
1549                 Log.v(this, "updateAddress, address changed");
1550                 if ((getConnectionProperties() & PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0) {
1551                     address = null;
1552                 }
1553                 setAddress(address, presentation);
1554             }
1555 
1556             String name = filterCnapName(mOriginalConnection.getCnapName());
1557             int namePresentation = mOriginalConnection.getCnapNamePresentation();
1558             if (!Objects.equals(name, getCallerDisplayName()) ||
1559                     namePresentation != getCallerDisplayNamePresentation()) {
1560                 Log.v(this, "updateAddress, caller display name changed");
1561                 setCallerDisplayName(name, namePresentation);
1562             }
1563 
1564             TelephonyManager tm = (TelephonyManager) getPhone().getContext()
1565                     .getSystemService(Context.TELEPHONY_SERVICE);
1566             if (tm.isEmergencyNumber(mOriginalConnection.getAddress())) {
1567                 mTreatAsEmergencyCall = true;
1568             }
1569 
1570             // Changing the address of the connection can change whether it is an emergency call or
1571             // not, which can impact whether it can be part of a conference.
1572             refreshConferenceSupported();
1573         }
1574     }
1575 
onRemovedFromCallService()1576     void onRemovedFromCallService() {
1577         // Subclass can override this to do cleanup.
1578     }
1579 
registerForCallEvents(Phone phone)1580     public void registerForCallEvents(Phone phone) {
1581         if (mPhoneForEvents == phone) {
1582             Log.i(this, "registerForCallEvents - same phone requested for"
1583                     + "registration, ignoring.");
1584             return;
1585         }
1586         Log.i(this, "registerForCallEvents; phone=%s", phone);
1587         // Only one Phone should be registered for events at a time.
1588         unregisterForCallEvents();
1589         phone.registerForPreciseCallStateChanged(mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
1590         phone.registerForHandoverStateChanged(mHandler, MSG_HANDOVER_STATE_CHANGED, null);
1591         phone.registerForRedialConnectionChanged(mHandler, MSG_REDIAL_CONNECTION_CHANGED, null);
1592         phone.registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
1593         phone.registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
1594         phone.registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
1595         phone.registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null);
1596         phone.registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null);
1597         mPhoneForEvents = phone;
1598     }
1599 
setOriginalConnection(com.android.internal.telephony.Connection originalConnection)1600     void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
1601         Log.i(this, "setOriginalConnection: TelephonyConnection, originalConnection: "
1602                 + originalConnection);
1603         if (mOriginalConnection != null && originalConnection != null
1604                && !originalConnection.isIncoming()
1605                && originalConnection.getOrigDialString() == null
1606                && isShowingOriginalDialString()) {
1607             Log.i(this, "new original dial string is null, convert to: "
1608                    +  mOriginalConnection.getOrigDialString());
1609             originalConnection.restoreDialedNumberAfterConversion(
1610                     mOriginalConnection.getOrigDialString());
1611         }
1612 
1613         clearOriginalConnection();
1614         mOriginalConnectionExtras.clear();
1615         mOriginalConnection = originalConnection;
1616         mOriginalConnection.setTelecomCallId(getTelecomCallId());
1617         registerForCallEvents(getPhone());
1618 
1619         mOriginalConnection.addPostDialListener(mPostDialListener);
1620         mOriginalConnection.addListener(mOriginalConnectionListener);
1621 
1622         // Set video state and capabilities
1623         setTelephonyVideoState(mOriginalConnection.getVideoState());
1624         setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities());
1625         setIsNetworkIdentifiedEmergencyCall(mOriginalConnection.isNetworkIdentifiedEmergencyCall());
1626         setIsAdhocConferenceCall(mOriginalConnection.isAdhocConference());
1627         setAudioModeIsVoip(mOriginalConnection.getAudioModeIsVoip());
1628         setTelephonyVideoProvider(mOriginalConnection.getVideoProvider());
1629         setAudioQuality(mOriginalConnection.getAudioQuality());
1630         setTechnologyTypeExtra();
1631 
1632         setCallRadioTech(mOriginalConnection.getCallRadioTech());
1633 
1634         // Post update of extras to the handler; extras are updated via the handler to ensure thread
1635         // safety. The Extras Bundle is cloned in case the original extras are modified while they
1636         // are being added to mOriginalConnectionExtras in updateExtras.
1637         Bundle connExtras = mOriginalConnection.getConnectionExtras();
1638             mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null :
1639                     new Bundle(connExtras)).sendToTarget();
1640 
1641         TelephonyManager tm = (TelephonyManager) getPhone().getContext()
1642                 .getSystemService(Context.TELEPHONY_SERVICE);
1643         if (tm.isEmergencyNumber(mOriginalConnection.getAddress())) {
1644             mTreatAsEmergencyCall = true;
1645         }
1646         // Propagate VERSTAT for IMS calls.
1647         setCallerNumberVerificationStatus(mOriginalConnection.getNumberVerificationStatus());
1648 
1649         if (isImsConnection()) {
1650             mWasImsConnection = true;
1651         }
1652         if (originalConnection instanceof ImsPhoneConnection) {
1653             maybeConfigureDeviceToDeviceCommunication();
1654         }
1655         mIsMultiParty = mOriginalConnection.isMultiparty();
1656 
1657         Bundle extrasToPut = new Bundle();
1658         // Also stash the number verification status in a hidden extra key in the connection.
1659         // We do this because a RemoteConnection DOES NOT include a getNumberVerificationStatus
1660         // method and we need to be able to pass the number verification status up to Telecom
1661         // despite the missing pathway in the RemoteConnectionService API surface.
1662         extrasToPut.putInt(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS,
1663                 mOriginalConnection.getNumberVerificationStatus());
1664         List<String> extrasToRemove = new ArrayList<>();
1665         if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) {
1666             extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
1667         } else {
1668             extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL);
1669         }
1670 
1671         if (shouldSetDisableAddCallExtra()) {
1672             extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL, true);
1673         } else {
1674             extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL);
1675         }
1676 
1677         if (mOriginalConnection != null) {
1678             ArrayList<String> forwardedNumber = mOriginalConnection.getForwardedNumber();
1679             if (forwardedNumber != null) {
1680                 extrasToPut.putStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER,
1681                         forwardedNumber);
1682             }
1683         }
1684 
1685         putTelephonyExtras(extrasToPut);
1686         removeTelephonyExtras(extrasToRemove);
1687 
1688         // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
1689         // should be executed *after* the above setters have run.
1690         updateState();
1691         if (mOriginalConnection == null) {
1692             Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " +
1693                     originalConnection);
1694         }
1695 
1696         fireOnOriginalConnectionConfigured();
1697     }
1698 
1699     /**
1700      * Filters the CNAP name to not include a list of names that are unhelpful to the user for
1701      * Caller ID purposes.
1702      */
filterCnapName(final String cnapName)1703     private String filterCnapName(final String cnapName) {
1704         if (cnapName == null) {
1705             return null;
1706         }
1707         PersistableBundle carrierConfig = getCarrierConfig();
1708         String[] filteredCnapNames = null;
1709         if (carrierConfig != null) {
1710             filteredCnapNames = carrierConfig.getStringArray(
1711                     CarrierConfigManager.KEY_FILTERED_CNAP_NAMES_STRING_ARRAY);
1712         }
1713         if (filteredCnapNames != null) {
1714             long cnapNameMatches = Arrays.asList(filteredCnapNames)
1715                     .stream()
1716                     .filter(filteredCnapName -> filteredCnapName.equals(
1717                             cnapName.toUpperCase(Locale.ROOT)))
1718                     .count();
1719             if (cnapNameMatches > 0) {
1720                 Log.i(this, "filterCnapName: Filtered CNAP Name: " + cnapName);
1721                 return "";
1722             }
1723         }
1724         return cnapName;
1725     }
1726 
1727     /**
1728      * Sets the EXTRA_CALL_TECHNOLOGY_TYPE extra on the connection to report back to Telecom.
1729      */
setTechnologyTypeExtra()1730     private void setTechnologyTypeExtra() {
1731         if (getPhone() != null) {
1732             Bundle newExtras = getExtras();
1733             if (newExtras == null) {
1734                 newExtras = new Bundle();
1735             }
1736             newExtras.putInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType());
1737             putTelephonyExtras(newExtras);
1738         }
1739     }
1740 
refreshHoldSupported()1741     private void refreshHoldSupported() {
1742        if (mOriginalConnection == null) {
1743            Log.w(this, "refreshHoldSupported org conn is null");
1744            return;
1745        }
1746 
1747        if (!mOriginalConnection.shouldAllowHoldingVideoCall() && canHoldImsCalls() !=
1748                ((getConnectionCapabilities() & (CAPABILITY_HOLD | CAPABILITY_SUPPORT_HOLD)) != 0)) {
1749            updateConnectionCapabilities();
1750        }
1751     }
1752 
refreshDisableAddCall()1753     private void refreshDisableAddCall() {
1754         if (shouldSetDisableAddCallExtra()) {
1755             Bundle newExtras = getExtras();
1756             if (newExtras == null) {
1757                 newExtras = new Bundle();
1758             }
1759             newExtras.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL, true);
1760             putTelephonyExtras(newExtras);
1761         } else {
1762             removeExtras(Connection.EXTRA_DISABLE_ADD_CALL);
1763         }
1764     }
1765 
refreshCodec()1766     private void refreshCodec() {
1767         boolean changed = false;
1768         Bundle newExtras = getExtras();
1769         if (newExtras == null) {
1770             newExtras = new Bundle();
1771         }
1772         int newCodecType;
1773         if (isImsConnection()) {
1774             newCodecType = transformCodec(getOriginalConnection().getAudioCodec());
1775         } else {
1776             // For SRVCC, report AUDIO_CODEC_NONE.
1777             newCodecType = Connection.AUDIO_CODEC_NONE;
1778         }
1779         int oldCodecType = newExtras.getInt(Connection.EXTRA_AUDIO_CODEC,
1780                 Connection.AUDIO_CODEC_NONE);
1781         if (newCodecType != oldCodecType) {
1782             newExtras.putInt(Connection.EXTRA_AUDIO_CODEC, newCodecType);
1783             Log.i(this, "refreshCodec: codec changed; old=%d, new=%d", oldCodecType, newCodecType);
1784             changed = true;
1785         }
1786         if (isImsConnection()) {
1787             float newBitrate = getOriginalConnection().getAudioCodecBitrateKbps();
1788             float oldBitrate = newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, 0.0f);
1789             if (Math.abs(newBitrate - oldBitrate) > THRESHOLD) {
1790                 newExtras.putFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, newBitrate);
1791                 Log.i(this, "refreshCodec: bitrate changed; old=%f, new=%f", oldBitrate,
1792                         newBitrate);
1793                 changed = true;
1794             }
1795 
1796             float newBandwidth = getOriginalConnection().getAudioCodecBandwidthKhz();
1797             float oldBandwidth = newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ,
1798                     0.0f);
1799             if (Math.abs(newBandwidth - oldBandwidth) > THRESHOLD) {
1800                 newExtras.putFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ, newBandwidth);
1801                 Log.i(this, "refreshCodec: bandwidth changed; old=%f, new=%f", oldBandwidth,
1802                         newBandwidth);
1803                 changed = true;
1804             }
1805         } else {
1806             ArrayList<String> toRemove = new ArrayList<>();
1807             toRemove.add(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS);
1808             toRemove.add(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ);
1809             removeTelephonyExtras(toRemove);
1810         }
1811 
1812         if (changed) {
1813             Log.i(this, "refreshCodec: Codec:"
1814                     + newExtras.getInt(Connection.EXTRA_AUDIO_CODEC, Connection.AUDIO_CODEC_NONE)
1815                     + ", Bitrate:"
1816                     + newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, 0.0f)
1817                     + ", Bandwidth:"
1818                     + newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ, 0.0f));
1819             putTelephonyExtras(newExtras);
1820         }
1821     }
1822 
transformCodec(int codec)1823     private int transformCodec(int codec) {
1824         switch (codec) {
1825             case ImsStreamMediaProfile.AUDIO_QUALITY_NONE:
1826                 return Connection.AUDIO_CODEC_NONE;
1827             case ImsStreamMediaProfile.AUDIO_QUALITY_AMR:
1828                 return Connection.AUDIO_CODEC_AMR;
1829             case ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB:
1830                 return Connection.AUDIO_CODEC_AMR_WB;
1831             case ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K:
1832                 return Connection.AUDIO_CODEC_QCELP13K;
1833             case ImsStreamMediaProfile.AUDIO_QUALITY_EVRC:
1834                 return Connection.AUDIO_CODEC_EVRC;
1835             case ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B:
1836                 return Connection.AUDIO_CODEC_EVRC_B;
1837             case ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB:
1838                 return Connection.AUDIO_CODEC_EVRC_WB;
1839             case ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW:
1840                 return Connection.AUDIO_CODEC_EVRC_NW;
1841             case ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR:
1842                 return Connection.AUDIO_CODEC_GSM_EFR;
1843             case ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR:
1844                 return Connection.AUDIO_CODEC_GSM_FR;
1845             case ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR:
1846                 return Connection.AUDIO_CODEC_GSM_HR;
1847             case ImsStreamMediaProfile.AUDIO_QUALITY_G711U:
1848                 return Connection.AUDIO_CODEC_G711U;
1849             case ImsStreamMediaProfile.AUDIO_QUALITY_G723:
1850                 return Connection.AUDIO_CODEC_G723;
1851             case ImsStreamMediaProfile.AUDIO_QUALITY_G711A:
1852                 return Connection.AUDIO_CODEC_G711A;
1853             case ImsStreamMediaProfile.AUDIO_QUALITY_G722:
1854                 return Connection.AUDIO_CODEC_G722;
1855             case ImsStreamMediaProfile.AUDIO_QUALITY_G711AB:
1856                 return Connection.AUDIO_CODEC_G711AB;
1857             case ImsStreamMediaProfile.AUDIO_QUALITY_G729:
1858                 return Connection.AUDIO_CODEC_G729;
1859             case ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB:
1860                 return Connection.AUDIO_CODEC_EVS_NB;
1861             case ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB:
1862                 return Connection.AUDIO_CODEC_EVS_WB;
1863             case ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB:
1864                 return Connection.AUDIO_CODEC_EVS_SWB;
1865             case ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB:
1866                 return Connection.AUDIO_CODEC_EVS_FB;
1867             default:
1868                 return Connection.AUDIO_CODEC_NONE;
1869         }
1870     }
1871 
shouldSetDisableAddCallExtra()1872     private boolean shouldSetDisableAddCallExtra() {
1873         if (mOriginalConnection == null) {
1874             return false;
1875         }
1876         boolean carrierShouldAllowAddCall = mOriginalConnection.shouldAllowAddCallDuringVideoCall();
1877         if (carrierShouldAllowAddCall) {
1878             return false;
1879         }
1880         Phone phone = getPhone();
1881         if (phone == null) {
1882             return false;
1883         }
1884         boolean isCurrentVideoCall = false;
1885         boolean wasVideoCall = false;
1886         boolean isVowifiEnabled = false;
1887         if (phone instanceof ImsPhone) {
1888             ImsPhoneCall foregroundCall = ((ImsPhone) phone).getForegroundCall();
1889             if (foregroundCall != null) {
1890                 ImsCall call = foregroundCall.getImsCall();
1891                 if (call != null) {
1892                     isCurrentVideoCall = call.isVideoCall();
1893                     wasVideoCall = call.wasVideoCall();
1894                 }
1895             }
1896 
1897             isVowifiEnabled = isWfcEnabled(phone);
1898         }
1899 
1900         if (isCurrentVideoCall) {
1901             return true;
1902         } else if (wasVideoCall && isWifi() && !isVowifiEnabled) {
1903             return true;
1904         }
1905         return false;
1906     }
1907 
hasHighDefAudioProperty()1908     private boolean hasHighDefAudioProperty() {
1909         if (!mHasHighDefAudio) {
1910             return false;
1911         }
1912 
1913         boolean isVideoCall = VideoProfile.isVideo(getVideoState());
1914 
1915         PersistableBundle b = getCarrierConfig();
1916         boolean canWifiCallsBeHdAudio =
1917                 b != null && b.getBoolean(CarrierConfigManager.KEY_WIFI_CALLS_CAN_BE_HD_AUDIO);
1918         boolean canVideoCallsBeHdAudio =
1919                 b != null && b.getBoolean(CarrierConfigManager.KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO);
1920         boolean canGsmCdmaCallsBeHdAudio =
1921                 b != null && b.getBoolean(CarrierConfigManager.KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO);
1922         boolean shouldDisplayHdAudio =
1923                 b != null && b.getBoolean(CarrierConfigManager.KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL);
1924 
1925         if (!shouldDisplayHdAudio) {
1926             return false;
1927         }
1928 
1929         if (isGsmCdmaConnection() && !canGsmCdmaCallsBeHdAudio) {
1930             return false;
1931         }
1932 
1933         if (isVideoCall && !canVideoCallsBeHdAudio) {
1934             return false;
1935         }
1936 
1937         if (isWifi() && !canWifiCallsBeHdAudio) {
1938             return false;
1939         }
1940 
1941         return true;
1942     }
1943 
1944     /**
1945      * @return The address's to which this Connection is currently communicating.
1946      */
getParticipants()1947     public final @Nullable List<Uri> getParticipants() {
1948         return mParticipants;
1949     }
1950 
1951     /**
1952      * Sets the value of the {@link #getParticipants()} property.
1953      *
1954      * @param address The participant address's.
1955      */
setParticipants(@ullable List<Uri> address)1956     public final void setParticipants(@Nullable List<Uri> address) {
1957         mParticipants = address;
1958     }
1959 
1960     /**
1961      * @return true if connection is adhocConference call else false.
1962      */
isAdhocConferenceCall()1963     public final boolean isAdhocConferenceCall() {
1964         return mIsAdhocConferenceCall;
1965     }
1966 
1967     /**
1968      * Sets the value of the {@link #isAdhocConferenceCall()} property.
1969      *
1970      * @param isAdhocConferenceCall represents if the call is adhoc conference call or not.
1971      */
setIsAdhocConferenceCall(boolean isAdhocConferenceCall)1972     public void setIsAdhocConferenceCall(boolean isAdhocConferenceCall) {
1973         mIsAdhocConferenceCall = isAdhocConferenceCall;
1974         updateConnectionProperties();
1975     }
1976 
canHoldImsCalls()1977     private boolean canHoldImsCalls() {
1978         PersistableBundle b = getCarrierConfig();
1979         // Return true if the CarrierConfig is unavailable
1980         return (!doesDeviceRespectHoldCarrierConfig() || b == null ||
1981                 b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL)) &&
1982                 ((mOriginalConnection != null && mOriginalConnection.shouldAllowHoldingVideoCall())
1983                 || !VideoProfile.isVideo(getVideoState()));
1984     }
1985 
isConferenceHosted()1986     private boolean isConferenceHosted() {
1987         boolean isHosted = false;
1988         if (getTelephonyConnectionService() != null) {
1989             for (Conference current : getTelephonyConnectionService().getAllConferences()) {
1990                 if (current instanceof ImsConference) {
1991                     ImsConference other = (ImsConference) current;
1992                     if (getState() == current.getState()) {
1993                         continue;
1994                     }
1995                     if (other.isConferenceHost()) {
1996                         isHosted = true;
1997                         break;
1998                     }
1999                 }
2000             }
2001         }
2002         return isHosted;
2003     }
2004 
isAddParticipantCapable()2005     private boolean isAddParticipantCapable() {
2006         // not add participant capable for non ims phones
2007         if (getPhone() == null || getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
2008             return false;
2009         }
2010 
2011         if (!getCarrierConfig()
2012                 .getBoolean(CarrierConfigManager.KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL)) {
2013             return false;
2014         }
2015 
2016         boolean isCapable = !mTreatAsEmergencyCall && (mConnectionState == Call.State.ACTIVE ||
2017                 mConnectionState == Call.State.HOLDING);
2018 
2019         // add participant capable if current connection is a host connection or
2020         // if conference is not hosted on the device
2021         isCapable = isCapable && ((mOriginalConnection != null &&
2022                 mOriginalConnection.isConferenceHost()) ||
2023                 !isConferenceHosted());
2024 
2025         /**
2026           * For individual IMS calls, if the extra for remote conference support is
2027           *     - indicated, then consider the same for add participant capability
2028           *     - not indicated, then the add participant capability is same as before.
2029           */
2030         if (isCapable && (mOriginalConnection != null) && !mIsMultiParty) {
2031             // In case OEMs are still using deprecated value, read it and use it as default value.
2032             boolean isCapableFromDeprecatedExtra = mOriginalConnectionExtras.getBoolean(
2033                     ImsCallProfile.EXTRA_CONFERENCE_AVAIL, isCapable);
2034             isCapable = mOriginalConnectionExtras.getBoolean(
2035                     ImsCallProfile.EXTRA_EXTENDING_TO_CONFERENCE_SUPPORTED,
2036                     isCapableFromDeprecatedExtra);
2037         }
2038         return isCapable;
2039     }
2040 
2041     /**
2042      * Applies the add participant capabilities to the {@code CallCapabilities} bit-mask.
2043      *
2044      * @param callCapabilities The {@code CallCapabilities} bit-mask.
2045      * @return The capabilities with the add participant capabilities applied.
2046      */
applyAddParticipantCapabilities(int callCapabilities)2047     private int applyAddParticipantCapabilities(int callCapabilities) {
2048         int currentCapabilities = callCapabilities;
2049         if (isAddParticipantCapable()) {
2050             currentCapabilities = changeBitmask(currentCapabilities,
2051                     Connection.CAPABILITY_ADD_PARTICIPANT, true);
2052         } else {
2053             currentCapabilities = changeBitmask(currentCapabilities,
2054                     Connection.CAPABILITY_ADD_PARTICIPANT, false);
2055         }
2056         return currentCapabilities;
2057     }
2058 
2059     @VisibleForTesting
getCarrierConfig()2060     public @NonNull PersistableBundle getCarrierConfig() {
2061         Phone phone = getPhone();
2062         if (phone == null) {
2063             Log.w(this,
2064                     "getCarrierConfig: phone is null. Returning CarrierConfigManager"
2065                             + ".getDefaultConfig()");
2066             return CarrierConfigManager.getDefaultConfig();
2067         }
2068 
2069         // potential null returned from .getCarrierConfigForSubId() and method guarantees non-null.
2070         // hence, need for try/finally block
2071         PersistableBundle pb = null;
2072         try {
2073             pb = PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
2074         } catch (Exception e) {
2075             Log.e(this, e,
2076                     "getCarrierConfig: caught Exception when calling "
2077                             + "PhoneGlobals.getCarrierConfigForSubId(phone.getSubId()). Returning "
2078                             + "CarrierConfigManager.getDefaultConfig()");
2079         } finally {
2080             if (pb == null) {
2081                 pb = CarrierConfigManager.getDefaultConfig();
2082             }
2083         }
2084         return pb;
2085     }
2086 
2087     @VisibleForTesting
isRttMergeSupported(@onNull PersistableBundle pb)2088     public boolean isRttMergeSupported(@NonNull PersistableBundle pb) {
2089         return pb.getBoolean(CarrierConfigManager.KEY_ALLOW_MERGING_RTT_CALLS_BOOL);
2090     }
2091 
canDeflectImsCalls()2092     private boolean canDeflectImsCalls() {
2093         return getCarrierConfig().getBoolean(
2094                 CarrierConfigManager.KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL)
2095                 && isValidRingingCall();
2096     }
2097 
isCallTransferSupported()2098     private boolean isCallTransferSupported() {
2099         return getCarrierConfig().getBoolean(
2100                 CarrierConfigManager.KEY_CARRIER_ALLOW_TRANSFER_IMS_CALL_BOOL);
2101     }
2102 
canTransfer(TelephonyConnection c)2103     private boolean canTransfer(TelephonyConnection c) {
2104         com.android.internal.telephony.Connection connection = c.getOriginalConnection();
2105         return (connection != null && !connection.isMultiparty()
2106                 && (c.getState() == STATE_ACTIVE || c.getState() == STATE_HOLDING));
2107     }
2108 
canTransferToNumber()2109     private boolean canTransferToNumber() {
2110         if (!isCallTransferSupported()) {
2111             return false;
2112         }
2113         return canTransfer(this);
2114     }
2115 
canConsultativeTransfer()2116     private boolean canConsultativeTransfer() {
2117         if (!isCallTransferSupported()) {
2118             return false;
2119         }
2120         if (!canTransfer(this)) {
2121             return false;
2122         }
2123         boolean canConsultativeTransfer = false;
2124         if (getTelephonyConnectionService() != null) {
2125             for (Connection current : getTelephonyConnectionService().getAllConnections()) {
2126                 if (current != this && current instanceof TelephonyConnection) {
2127                     TelephonyConnection other = (TelephonyConnection) current;
2128                     if (canTransfer(other)) {
2129                         canConsultativeTransfer = true;
2130                         break;
2131                     }
2132                 }
2133             }
2134         }
2135         return canConsultativeTransfer;
2136     }
2137 
2138     /**
2139      * Determines if the device will respect the value of the
2140      * {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} configuration option.
2141      *
2142      * @return {@code false} if the device always supports holding IMS calls, {@code true} if it
2143      *      will use {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} to determine if
2144      *      hold is supported.
2145      */
doesDeviceRespectHoldCarrierConfig()2146     private boolean doesDeviceRespectHoldCarrierConfig() {
2147         Phone phone = getPhone();
2148         if (phone == null) {
2149             return true;
2150         }
2151         return phone.getContext().getResources().getBoolean(
2152                 com.android.internal.R.bool.config_device_respects_hold_carrier_config);
2153     }
2154 
2155     /**
2156      * Whether the connection should be treated as an emergency.
2157      * @return {@code true} if the connection should be treated as an emergency call based
2158      * on the number dialed, {@code false} otherwise.
2159      */
shouldTreatAsEmergencyCall()2160     protected boolean shouldTreatAsEmergencyCall() {
2161         return mTreatAsEmergencyCall;
2162     }
2163 
2164     /**
2165      * Sets whether to treat this call as an emergency call or not.
2166      * @param shouldTreatAsEmergencyCall
2167      */
2168     @VisibleForTesting
setShouldTreatAsEmergencyCall(boolean shouldTreatAsEmergencyCall)2169     public void setShouldTreatAsEmergencyCall(boolean shouldTreatAsEmergencyCall) {
2170         mTreatAsEmergencyCall = shouldTreatAsEmergencyCall;
2171     }
2172 
2173     /**
2174      * Un-sets the underlying radio connection.
2175      */
clearOriginalConnection()2176     void clearOriginalConnection() {
2177         if (mOriginalConnection != null) {
2178             Log.i(this, "clearOriginalConnection; clearing=%s", mOriginalConnection);
2179             unregisterForCallEvents();
2180             mOriginalConnection.removePostDialListener(mPostDialListener);
2181             mOriginalConnection.removeListener(mOriginalConnectionListener);
2182             mOriginalConnection = null;
2183         }
2184     }
2185 
unregisterForCallEvents()2186     public void unregisterForCallEvents() {
2187         if (mPhoneForEvents == null) return;
2188         mPhoneForEvents.unregisterForPreciseCallStateChanged(mHandler);
2189         mPhoneForEvents.unregisterForRingbackTone(mHandler);
2190         mPhoneForEvents.unregisterForHandoverStateChanged(mHandler);
2191         mPhoneForEvents.unregisterForRedialConnectionChanged(mHandler);
2192         mPhoneForEvents.unregisterForDisconnect(mHandler);
2193         mPhoneForEvents.unregisterForSuppServiceNotification(mHandler);
2194         mPhoneForEvents.unregisterForOnHoldTone(mHandler);
2195         mPhoneForEvents.unregisterForInCallVoicePrivacyOn(mHandler);
2196         mPhoneForEvents.unregisterForInCallVoicePrivacyOff(mHandler);
2197         mPhoneForEvents = null;
2198     }
2199 
2200     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
hangup(int telephonyDisconnectCode)2201     public void hangup(int telephonyDisconnectCode) {
2202         if (mOriginalConnection != null) {
2203             mHangupDisconnectCause = telephonyDisconnectCode;
2204             try {
2205                 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
2206                 // connection.hangup(). Without this change, the party originating the call
2207                 // will not get sent to voicemail if the user opts to reject the call.
2208                 if (isValidRingingCall()) {
2209                     Call call = getCall();
2210                     if (call != null) {
2211                         call.hangup();
2212                     } else {
2213                         Log.w(this, "Attempting to hangup a connection without backing call.");
2214                     }
2215                 } else {
2216                     // We still prefer to call connection.hangup() for non-ringing calls
2217                     // in order to support hanging-up specific calls within a conference call.
2218                     // If we invoked call.hangup() while in a conference, we would end up
2219                     // hanging up the entire conference call instead of the specific connection.
2220                     mOriginalConnection.hangup();
2221                 }
2222             } catch (CallStateException e) {
2223                 Log.e(this, e, "Call to Connection.hangup failed with exception");
2224             }
2225         } else {
2226             mTelephonyConnectionService.onLocalHangup(this);
2227             if (getState() == STATE_DISCONNECTED) {
2228                 Log.i(this, "hangup called on an already disconnected call!");
2229                 close();
2230             } else {
2231                 // There are a few cases where mOriginalConnection has not been set yet. For
2232                 // example, when the radio has to be turned on to make an emergency call,
2233                 // mOriginalConnection could not be set for many seconds.
2234                 setTelephonyConnectionDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
2235                         android.telephony.DisconnectCause.LOCAL,
2236                         "Local Disconnect before connection established."));
2237                 close();
2238             }
2239         }
2240     }
2241 
reject(@ndroid.telecom.Call.RejectReason int rejectReason)2242     protected void reject(@android.telecom.Call.RejectReason int rejectReason) {
2243         if (mOriginalConnection != null) {
2244             mHangupDisconnectCause = android.telephony.DisconnectCause.INCOMING_REJECTED;
2245             try {
2246                 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
2247                 // connection.hangup(). Without this change, the party originating the call
2248                 // will not get sent to voicemail if the user opts to reject the call.
2249                 if (isValidRingingCall()) {
2250                     Call call = getCall();
2251                     if (call != null) {
2252                         call.hangup(rejectReason);
2253                     } else {
2254                         Log.w(this, "Attempting to hangup a connection without backing call.");
2255                     }
2256                 } else {
2257                     // We still prefer to call connection.hangup() for non-ringing calls
2258                     // in order to support hanging-up specific calls within a conference call.
2259                     // If we invoked call.hangup() while in a conference, we would end up
2260                     // hanging up the entire conference call instead of the specific connection.
2261                     mOriginalConnection.hangup();
2262                 }
2263             } catch (CallStateException e) {
2264                 Log.e(this, e, "Call to Connection.hangup failed with exception");
2265             }
2266         } else {
2267             if (getState() == STATE_DISCONNECTED) {
2268                 Log.i(this, "hangup called on an already disconnected call!");
2269                 close();
2270             } else {
2271                 // There are a few cases where mOriginalConnection has not been set yet. For
2272                 // example, when the radio has to be turned on to make an emergency call,
2273                 // mOriginalConnection could not be set for many seconds.
2274                 setTelephonyConnectionDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
2275                         android.telephony.DisconnectCause.LOCAL,
2276                         "Local Disconnect before connection established."));
2277                 close();
2278             }
2279         }
2280     }
2281 
getOriginalConnection()2282     com.android.internal.telephony.Connection getOriginalConnection() {
2283         return mOriginalConnection;
2284     }
2285 
getCall()2286     protected Call getCall() {
2287         if (mOriginalConnection != null) {
2288             return mOriginalConnection.getCall();
2289         }
2290         return null;
2291     }
2292 
getPhone()2293     Phone getPhone() {
2294         Call call = getCall();
2295         if (call != null) {
2296             return call.getPhone();
2297         }
2298         return null;
2299     }
2300 
hasMultipleTopLevelCalls()2301     private boolean hasMultipleTopLevelCalls() {
2302         int numCalls = 0;
2303         Phone phone = getPhone();
2304         if (phone != null) {
2305             if (!phone.getRingingCall().isIdle()) {
2306                 numCalls++;
2307             }
2308             if (!phone.getForegroundCall().isIdle()) {
2309                 numCalls++;
2310             }
2311             if (!phone.getBackgroundCall().isIdle()) {
2312                 numCalls++;
2313             }
2314         }
2315         return numCalls > 1;
2316     }
2317 
getForegroundConnection()2318     private com.android.internal.telephony.Connection getForegroundConnection() {
2319         if (getPhone() != null) {
2320             return getPhone().getForegroundCall().getEarliestConnection();
2321         }
2322         return null;
2323     }
2324 
2325      /**
2326      * Checks for and returns the list of conference participants
2327      * associated with this connection.
2328      */
getConferenceParticipants()2329     public List<ConferenceParticipant> getConferenceParticipants() {
2330         if (mOriginalConnection == null) {
2331             Log.w(this, "Null mOriginalConnection, cannot get conf participants.");
2332             return null;
2333         }
2334         return mOriginalConnection.getConferenceParticipants();
2335     }
2336 
2337     /**
2338      * Checks to see the original connection corresponds to an active incoming call. Returns false
2339      * if there is no such actual call, or if the associated call is not incoming (See
2340      * {@link Call.State#isRinging}).
2341      */
isValidRingingCall()2342     private boolean isValidRingingCall() {
2343         if (getPhone() == null) {
2344             Log.v(this, "isValidRingingCall, phone is null");
2345             return false;
2346         }
2347 
2348         Call ringingCall = getPhone().getRingingCall();
2349         if (!ringingCall.getState().isRinging()) {
2350             Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
2351             return false;
2352         }
2353 
2354         if (ringingCall.getEarliestConnection() != mOriginalConnection) {
2355             Log.v(this, "isValidRingingCall, ringing call connection does not match");
2356             return false;
2357         }
2358 
2359         Log.v(this, "isValidRingingCall, returning true");
2360         return true;
2361     }
2362 
2363     // Make sure the extras being passed into this method is a COPY of the original extras Bundle.
2364     // We do not want the extras to be cleared or modified during mOriginalConnectionExtras.putAll
2365     // below.
updateExtras(Bundle extras)2366     protected void updateExtras(Bundle extras) {
2367         if (mOriginalConnection != null) {
2368             if (extras != null) {
2369                 // Check if extras have changed and need updating.
2370                 if (!areBundlesEqual(mOriginalConnectionExtras, extras)) {
2371                     if (Log.DEBUG) {
2372                         Log.d(TelephonyConnection.this, "Updating extras:");
2373                         for (String key : extras.keySet()) {
2374                             Object value = extras.get(key);
2375                             if (value instanceof String) {
2376                                 Log.d(this, "updateExtras Key=" + Rlog.pii(LOG_TAG, key)
2377                                         + " value=" + Rlog.pii(LOG_TAG, value));
2378                             }
2379                         }
2380                     }
2381                     mOriginalConnectionExtras.clear();
2382 
2383                     mOriginalConnectionExtras.putAll(extras);
2384 
2385                     // Remap any string extras that have a remapping defined.
2386                     for (String key : mOriginalConnectionExtras.keySet()) {
2387                         if (sExtrasMap.containsKey(key)) {
2388                             String newKey = sExtrasMap.get(key);
2389                             mOriginalConnectionExtras.putString(newKey, extras.getString(key));
2390                             mOriginalConnectionExtras.remove(key);
2391                         }
2392                     }
2393 
2394                     // Ensure extras are propagated to Telecom.
2395                     putTelephonyExtras(mOriginalConnectionExtras);
2396                     // If extras contain Conference support information,
2397                     // then ensure capabilities are updated.
2398                     if (mOriginalConnectionExtras.containsKey(
2399                             ImsCallProfile.EXTRA_EXTENDING_TO_CONFERENCE_SUPPORTED)
2400                             || mOriginalConnectionExtras.containsKey(
2401                                 ImsCallProfile.EXTRA_CONFERENCE_AVAIL)) {
2402                         updateConnectionCapabilities();
2403                     }
2404                     // If extras contain or contained Cross Sim information,
2405                     // then ensure connection properties are updated and propagated to Telecom.
2406                     // Also, update the status hints in the case the call has
2407                     // has moved from cross sim call back to wifi
2408                     mWasCrossSim |= mOriginalConnectionExtras.containsKey(
2409                                 ImsCallProfile.EXTRA_IS_CROSS_SIM_CALL);
2410                     if (mWasCrossSim) {
2411                         updateStatusHints();
2412                         updateConnectionProperties();
2413                     }
2414                 } else {
2415                     Log.d(this, "Extras update not required");
2416                 }
2417             } else {
2418                 Log.d(this, "updateExtras extras: " + Rlog.pii(LOG_TAG, extras));
2419             }
2420         }
2421     }
2422 
areBundlesEqual(Bundle extras, Bundle newExtras)2423     private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
2424         if (extras == null || newExtras == null) {
2425             return extras == newExtras;
2426         }
2427 
2428         if (extras.size() != newExtras.size()) {
2429             return false;
2430         }
2431 
2432         for(String key : extras.keySet()) {
2433             if (key != null) {
2434                 final Object value = extras.get(key);
2435                 final Object newValue = newExtras.get(key);
2436                 if (!Objects.equals(value, newValue)) {
2437                     return false;
2438                 }
2439             }
2440         }
2441         return true;
2442     }
2443 
setStateOverride(Call.State state)2444     void setStateOverride(Call.State state) {
2445         mIsStateOverridden = true;
2446         mConnectionOverriddenState = state;
2447         // Need to keep track of the original connection's state before override.
2448         mOriginalConnectionState = mOriginalConnection.getState();
2449         updateStateInternal();
2450     }
2451 
resetStateOverride()2452     void resetStateOverride() {
2453         mIsStateOverridden = false;
2454         updateStateInternal();
2455     }
2456 
updateStateInternal()2457     void updateStateInternal() {
2458         if (mOriginalConnection == null) {
2459             return;
2460         }
2461         Call.State newState;
2462         // If the state is overridden and the state of the original connection hasn't changed since,
2463         // then we continue in the overridden state, else we go to the original connection's state.
2464         if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) {
2465             newState = mConnectionOverriddenState;
2466         } else {
2467             newState = mOriginalConnection.getState();
2468         }
2469         int cause = mOriginalConnection.getDisconnectCause();
2470         Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState,
2471                 getTelecomCallId());
2472 
2473         if (mConnectionState != newState) {
2474             mConnectionState = newState;
2475             switch (newState) {
2476                 case IDLE:
2477                     break;
2478                 case ACTIVE:
2479                     setActiveInternal();
2480                     break;
2481                 case HOLDING:
2482                     setTelephonyConnectionOnHold();
2483                     break;
2484                 case DIALING:
2485                 case ALERTING:
2486                     if (mOriginalConnection != null && mOriginalConnection.isPulledCall()) {
2487                         setTelephonyConnectionPulling();
2488                     } else {
2489                         setTelephonyConnectionDialing();
2490                     }
2491                     break;
2492                 case INCOMING:
2493                 case WAITING:
2494                     setTelephonyConnectionRinging();
2495                     break;
2496                 case DISCONNECTED:
2497                     if (mTelephonyConnectionService != null) {
2498                         ImsReasonInfo reasonInfo = null;
2499                         if (isImsConnection()) {
2500                             ImsPhoneConnection imsPhoneConnection =
2501                                     (ImsPhoneConnection) mOriginalConnection;
2502                             reasonInfo = imsPhoneConnection.getImsReasonInfo();
2503                             if (reasonInfo != null) {
2504                                 int reasonCode = reasonInfo.getCode();
2505                                 int extraCode = reasonInfo.getExtraCode();
2506                                 if ((reasonCode == CODE_SIP_ALTERNATE_EMERGENCY_CALL)
2507                                         || (reasonCode == CODE_LOCAL_CALL_CS_RETRY_REQUIRED
2508                                                 && extraCode == EXTRA_CODE_CALL_RETRY_EMERGENCY)) {
2509                                     EmergencyNumber numberInfo =
2510                                             imsPhoneConnection.getEmergencyNumberInfo();
2511                                     if (numberInfo != null) {
2512                                         mEmergencyServiceCategory =
2513                                                 numberInfo.getEmergencyServiceCategoryBitmask();
2514                                     } else {
2515                                         Log.i(this, "mEmergencyServiceCategory no EmergencyNumber");
2516                                     }
2517 
2518                                     if (mEmergencyServiceCategory != null) {
2519                                         Log.i(this, "mEmergencyServiceCategory="
2520                                                 + mEmergencyServiceCategory);
2521                                     }
2522                                 }
2523                             }
2524                         }
2525 
2526                         if (mTelephonyConnectionService.maybeReselectDomain(this,
2527                                   mOriginalConnection.getPreciseDisconnectCause(), reasonInfo)) {
2528                             clearOriginalConnection();
2529                             break;
2530                         }
2531                     }
2532 
2533                     if (shouldTreatAsEmergencyCall()
2534                             && (cause
2535                             == android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
2536                             || cause
2537                             == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE)) {
2538                         // We can get into a situation where the radio wants us to redial the
2539                         // same emergency call on the other available slot. This will not set
2540                         // the state to disconnected and will instead tell the
2541                         // TelephonyConnectionService to
2542                         // create a new originalConnection using the new Slot.
2543                         fireOnOriginalConnectionRetryDial(cause
2544                                 == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE);
2545                     } else {
2546                         int preciseDisconnectCause = CallFailCause.NOT_VALID;
2547                         if (mShowPreciseFailedCause) {
2548                             preciseDisconnectCause =
2549                                     mOriginalConnection.getPreciseDisconnectCause();
2550                         }
2551                         int disconnectCause = mOriginalConnection.getDisconnectCause();
2552                         if ((mHangupDisconnectCause != DisconnectCause.NOT_VALID)
2553                                 && (mHangupDisconnectCause != disconnectCause)) {
2554                             Log.i(LOG_TAG, "setDisconnected: override cause: " + disconnectCause
2555                                     + " -> " + mHangupDisconnectCause);
2556                             disconnectCause = mHangupDisconnectCause;
2557                         }
2558                         ImsReasonInfo imsReasonInfo = null;
2559                         if (isImsConnection()) {
2560                             ImsPhoneConnection imsPhoneConnection =
2561                                     (ImsPhoneConnection) mOriginalConnection;
2562                             imsReasonInfo = imsPhoneConnection.getImsReasonInfo();
2563                         }
2564                         setTelephonyConnectionDisconnected(
2565                                 DisconnectCauseUtil.toTelecomDisconnectCause(
2566                                         disconnectCause,
2567                                         preciseDisconnectCause,
2568                                         mOriginalConnection.getVendorDisconnectCause(),
2569                                         getPhone().getPhoneId(), imsReasonInfo));
2570                         close();
2571                     }
2572                     break;
2573                 case DISCONNECTING:
2574                     break;
2575             }
2576 
2577             if (mCommunicator != null) {
2578                 mCommunicator.onStateChanged(getTelecomCallId(), getState());
2579             }
2580         }
2581     }
2582 
updateState()2583     void updateState() {
2584         if (mOriginalConnection == null) {
2585             return;
2586         }
2587 
2588         updateStateInternal();
2589         updateStatusHints();
2590         updateConnectionCapabilities();
2591         updateConnectionProperties();
2592         updateAddress();
2593         updateMultiparty();
2594         refreshDisableAddCall();
2595         refreshCodec();
2596     }
2597 
2598     /**
2599      * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
2600      */
updateMultiparty()2601     private void updateMultiparty() {
2602         if (mOriginalConnection == null) {
2603             return;
2604         }
2605 
2606         if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
2607             mIsMultiParty = mOriginalConnection.isMultiparty();
2608 
2609             if (mIsMultiParty) {
2610                 notifyConferenceStarted();
2611             }
2612         }
2613     }
2614 
2615     /**
2616      * Handles a failure when merging calls into a conference.
2617      * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()}
2618      * listener.
2619      */
handleConferenceMergeFailed()2620     private void handleConferenceMergeFailed(){
2621         mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget();
2622     }
2623 
2624     /**
2625      * Handles requests to update the multiparty state received via the
2626      * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)}
2627      * listener.
2628      * <p>
2629      * Note: We post this to the mHandler to ensure that if a conference must be created as a
2630      * result of the multiparty state change, the conference creation happens on the correct
2631      * thread.  This ensures that the thread check in
2632      * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)}
2633      * does not fire.
2634      *
2635      * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise.
2636      */
handleMultipartyStateChange(boolean isMultiParty)2637     private void handleMultipartyStateChange(boolean isMultiParty) {
2638         Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
2639         mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget();
2640     }
2641 
setActiveInternal()2642     private void setActiveInternal() {
2643         if (getState() == STATE_ACTIVE) {
2644             Log.w(this, "Should not be called if this is already ACTIVE");
2645             return;
2646         }
2647 
2648         // When we set a call to active, we need to make sure that there are no other active
2649         // calls. However, the ordering of state updates to connections can be non-deterministic
2650         // since all connections register for state changes on the phone independently.
2651         // To "optimize", we check here to see if there already exists any active calls.  If so,
2652         // we issue an update for those calls first to make sure we only have one top-level
2653         // active call.
2654         if (getTelephonyConnectionService() != null) {
2655             for (Connection current : getTelephonyConnectionService().getAllConnections()) {
2656                 if (current != this && current instanceof TelephonyConnection) {
2657                     TelephonyConnection other = (TelephonyConnection) current;
2658                     if (other.getState() == STATE_ACTIVE) {
2659                         other.updateState();
2660                     }
2661                 }
2662             }
2663         }
2664         setTelephonyConnectionActive();
2665     }
2666 
close()2667     public void close() {
2668         Log.v(this, "close");
2669         clearOriginalConnection();
2670         destroy();
2671         if (mTelephonyConnectionService != null) {
2672             removeTelephonyConnectionListener(
2673                     mTelephonyConnectionService.getTelephonyConnectionListener());
2674         }
2675         notifyDestroyed();
2676     }
2677 
2678     /**
2679      * Determines if the current connection is video capable.
2680      *
2681      * A connection is deemed to be video capable if the original connection capabilities state that
2682      * both local and remote video is supported.
2683      *
2684      * @return {@code true} if the connection is video capable, {@code false} otherwise.
2685      */
isVideoCapable()2686     private boolean isVideoCapable() {
2687         return (mOriginalConnectionCapabilities & Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
2688                 == Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL
2689                 && (mOriginalConnectionCapabilities & Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL)
2690                 == Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL;
2691     }
2692 
2693     /**
2694      * Determines if the current connection is an external connection.
2695      *
2696      * A connection is deemed to be external if the original connection capabilities state that it
2697      * is.
2698      *
2699      * @return {@code true} if the connection is external, {@code false} otherwise.
2700      */
isExternalConnection()2701     private boolean isExternalConnection() {
2702         return (mOriginalConnectionCapabilities
2703                 & Capability.IS_EXTERNAL_CONNECTION) == Capability.IS_EXTERNAL_CONNECTION;
2704     }
2705 
2706     /**
2707      * Determines if the current connection has RTT enabled.
2708      */
isRtt()2709     private boolean isRtt() {
2710         return mOriginalConnection != null
2711                 && mOriginalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS
2712                 && mOriginalConnection instanceof ImsPhoneConnection
2713                 && ((ImsPhoneConnection) mOriginalConnection).isRttEnabledForCall();
2714     }
2715 
2716     /**
2717      * Determines if the current connection is cross sim calling
2718      */
isCrossSimCall()2719     private boolean isCrossSimCall() {
2720         return mOriginalConnection != null
2721                 && mOriginalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS
2722                 && mOriginalConnection instanceof ImsPhoneConnection
2723                 && ((ImsPhoneConnection) mOriginalConnection).isCrossSimCall();
2724     }
2725 
2726     /**
2727      * Determines if the current connection is pullable.
2728      *
2729      * A connection is deemed to be pullable if the original connection capabilities state that it
2730      * is.
2731      *
2732      * @return {@code true} if the connection is pullable, {@code false} otherwise.
2733      */
isPullable()2734     private boolean isPullable() {
2735         return (mOriginalConnectionCapabilities & Capability.IS_EXTERNAL_CONNECTION)
2736                 == Capability.IS_EXTERNAL_CONNECTION
2737                 && (mOriginalConnectionCapabilities & Capability.IS_PULLABLE)
2738                 == Capability.IS_PULLABLE;
2739     }
2740 
2741     /**
2742      * Sets whether or not CDMA enhanced call privacy is enabled for this connection.
2743      */
setCdmaVoicePrivacy(boolean isEnabled)2744     private void setCdmaVoicePrivacy(boolean isEnabled) {
2745         if(mIsCdmaVoicePrivacyEnabled != isEnabled) {
2746             mIsCdmaVoicePrivacyEnabled = isEnabled;
2747             updateConnectionProperties();
2748         }
2749     }
2750 
2751     /**
2752      * Applies capabilities specific to conferences termination to the
2753      * {@code ConnectionCapabilities} bit-mask.
2754      *
2755      * @param capabilities The {@code ConnectionCapabilities} bit-mask.
2756      * @return The capabilities with the IMS conference capabilities applied.
2757      */
applyConferenceTerminationCapabilities(int capabilities)2758     private int applyConferenceTerminationCapabilities(int capabilities) {
2759         int currentCapabilities = capabilities;
2760 
2761         // An IMS call cannot be individually disconnected or separated from its parent conference.
2762         // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
2763         if (!mWasImsConnection) {
2764             currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
2765             currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
2766         }
2767 
2768         return currentCapabilities;
2769     }
2770 
2771     /**
2772      * Stores the new original connection capabilities, and applies them to the current connection,
2773      * notifying any listeners as necessary.
2774      *
2775      * @param connectionCapabilities The original connection capabilties.
2776      */
setOriginalConnectionCapabilities(int connectionCapabilities)2777     public void setOriginalConnectionCapabilities(int connectionCapabilities) {
2778         mOriginalConnectionCapabilities = connectionCapabilities;
2779         updateConnectionCapabilities();
2780         updateConnectionProperties();
2781     }
2782 
2783     /**
2784      * Called to apply the capabilities present in the {@link #mOriginalConnection} to this
2785      * {@link Connection}.  Provides a mapping between the capabilities present in the original
2786      * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in
2787      * this {@link Connection}.
2788      *
2789      * @param capabilities The capabilities bitmask from the {@link Connection}.
2790      * @return the capabilities bitmask with the original connection capabilities remapped and
2791      *      applied.
2792      */
applyOriginalConnectionCapabilities(int capabilities)2793     public int applyOriginalConnectionCapabilities(int capabilities) {
2794         // We only support downgrading to audio if both the remote and local side support
2795         // downgrading to audio.
2796         int supportsDowngrade = Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL
2797                 | Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE;
2798         boolean supportsDowngradeToAudio =
2799                 (mOriginalConnectionCapabilities & supportsDowngrade) == supportsDowngrade;
2800         capabilities = changeBitmask(capabilities,
2801                 CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio);
2802 
2803         capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
2804                 (mOriginalConnectionCapabilities & Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL)
2805                         == Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
2806 
2807         boolean isLocalVideoSupported = (mOriginalConnectionCapabilities
2808                 & Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
2809                 == Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL && !mIsTtyEnabled;
2810         capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
2811                 isLocalVideoSupported);
2812 
2813         capabilities = changeBitmask(capabilities, CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT,
2814                 (mOriginalConnectionCapabilities & Capability.SUPPORTS_RTT_REMOTE)
2815                 == Capability.SUPPORTS_RTT_REMOTE);
2816 
2817         return capabilities;
2818     }
2819 
2820     /**
2821      * Whether the call is using wifi.
2822      */
isWifi()2823     boolean isWifi() {
2824         return getCallRadioTech() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
2825     }
2826 
2827     /**
2828      * Sets whether this call has been identified by the network as an emergency call.
2829      * @param isNetworkIdentifiedEmergencyCall {@code true} if the network has identified this call
2830      * as an emergency call, {@code false} otherwise.
2831      */
setIsNetworkIdentifiedEmergencyCall(boolean isNetworkIdentifiedEmergencyCall)2832     public void setIsNetworkIdentifiedEmergencyCall(boolean isNetworkIdentifiedEmergencyCall) {
2833         Log.d(this, "setIsNetworkIdentifiedEmergencyCall; callId=%s, "
2834                 + "isNetworkIdentifiedEmergencyCall=%b", getTelecomCallId(),
2835                 isNetworkIdentifiedEmergencyCall);
2836         mIsNetworkIdentifiedEmergencyCall = isNetworkIdentifiedEmergencyCall;
2837         updateConnectionProperties();
2838     }
2839 
2840     /**
2841      * @return {@code true} if the network has identified this call as an emergency call,
2842      * {@code false} otherwise.
2843      */
isNetworkIdentifiedEmergencyCall()2844     public boolean isNetworkIdentifiedEmergencyCall() {
2845         return mIsNetworkIdentifiedEmergencyCall;
2846     }
2847 
2848     /**
2849      * @return {@code true} if this is an outgoing call, {@code false} otherwise.
2850      */
isOutgoingCall()2851     public boolean isOutgoingCall() {
2852         return getCallDirection() == android.telecom.Call.Details.DIRECTION_OUTGOING;
2853     }
2854 
2855     /**
2856      * Sets the current call audio quality. Used during rebuild of the properties
2857      * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
2858      *
2859      * @param audioQuality The audio quality.
2860      */
setAudioQuality(int audioQuality)2861     public void setAudioQuality(int audioQuality) {
2862         mHasHighDefAudio = audioQuality ==
2863                 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION;
2864         updateConnectionProperties();
2865     }
2866 
resetStateForConference()2867     void resetStateForConference() {
2868         if (getState() == Connection.STATE_HOLDING) {
2869             resetStateOverride();
2870         }
2871     }
2872 
setHoldingForConference()2873     boolean setHoldingForConference() {
2874         if (getState() == Connection.STATE_ACTIVE) {
2875             setStateOverride(Call.State.HOLDING);
2876             return true;
2877         }
2878         return false;
2879     }
2880 
setRttTextStream(RttTextStream s)2881     public void setRttTextStream(RttTextStream s) {
2882         mRttTextStream = s;
2883     }
2884 
getRttTextStream()2885     public RttTextStream getRttTextStream() {
2886         return mRttTextStream;
2887     }
2888 
2889     /**
2890      * For video calls, sets whether this connection supports pausing the outgoing video for the
2891      * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
2892      *
2893      * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise.
2894      */
setVideoPauseSupported(boolean isVideoPauseSupported)2895     public void setVideoPauseSupported(boolean isVideoPauseSupported) {
2896         mIsVideoPauseSupported = isVideoPauseSupported;
2897     }
2898 
2899     /**
2900      * @return {@code true} if this connection supports pausing the outgoing video using the
2901      * {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
2902      */
getVideoPauseSupported()2903     public boolean getVideoPauseSupported() {
2904         return mIsVideoPauseSupported;
2905     }
2906 
2907     /**
2908      * Sets whether this connection supports conference calling.
2909      * @param isConferenceSupported {@code true} if conference calling is supported by this
2910      *                                         connection, {@code false} otherwise.
2911      */
setConferenceSupported(boolean isConferenceSupported)2912     public void setConferenceSupported(boolean isConferenceSupported) {
2913         mIsConferenceSupported = isConferenceSupported;
2914     }
2915 
2916     /**
2917      * @return {@code true} if this connection supports merging calls into a conference.
2918      */
isConferenceSupported()2919     public boolean isConferenceSupported() {
2920         return mIsConferenceSupported;
2921     }
2922 
2923     /**
2924      * Sets whether managing conference call is supported after this connection being a part of a
2925      * Ims conference.
2926      *
2927      * @param isManageImsConferenceCallSupported {@code true} if manage conference calling is
2928      *        supported after this connection being a part of a IMS conference,
2929      *        {@code false} otherwise.
2930      */
setManageImsConferenceCallSupported(boolean isManageImsConferenceCallSupported)2931     public void setManageImsConferenceCallSupported(boolean isManageImsConferenceCallSupported) {
2932         mIsManageImsConferenceCallSupported = isManageImsConferenceCallSupported;
2933     }
2934 
2935     /**
2936      * @return {@code true} if manage conference calling is supported after this connection being a
2937      * part of a IMS conference.
2938      */
isManageImsConferenceCallSupported()2939     public boolean isManageImsConferenceCallSupported() {
2940         return mIsManageImsConferenceCallSupported;
2941     }
2942 
2943     /**
2944      * Sets whether this connection supports showing precise call disconnect cause.
2945      * @param showPreciseFailedCause  {@code true} if showing precise call
2946      * disconnect cause is supported by this connection, {@code false} otherwise.
2947      */
setShowPreciseFailedCause(boolean showPreciseFailedCause)2948     public void setShowPreciseFailedCause(boolean showPreciseFailedCause) {
2949         mShowPreciseFailedCause = showPreciseFailedCause;
2950     }
2951 
2952     /**
2953      * Sets whether TTY is enabled or not.
2954      * @param isTtyEnabled
2955      */
setTtyEnabled(boolean isTtyEnabled)2956     public void setTtyEnabled(boolean isTtyEnabled) {
2957         mIsTtyEnabled = isTtyEnabled;
2958         updateConnectionCapabilities();
2959     }
2960 
2961     /**
2962      * Whether the original connection is an IMS connection.
2963      * @return {@code True} if the original connection is an IMS connection, {@code false}
2964      *     otherwise.
2965      */
isImsConnection()2966     protected boolean isImsConnection() {
2967         com.android.internal.telephony.Connection originalConnection = getOriginalConnection();
2968 
2969         return originalConnection != null
2970                 && originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS
2971                 && originalConnection instanceof ImsPhoneConnection;
2972     }
2973 
2974     /**
2975      * Whether the original connection is an GSM/CDMA connection.
2976      * @return {@code True} if the original connection is an GSM/CDMA connection, {@code false}
2977      *     otherwise.
2978      */
isGsmCdmaConnection()2979     protected boolean isGsmCdmaConnection() {
2980         Phone phone = getPhone();
2981         if (phone != null) {
2982             switch (phone.getPhoneType()) {
2983                 case PhoneConstants.PHONE_TYPE_GSM:
2984                 case PhoneConstants.PHONE_TYPE_CDMA:
2985                     return true;
2986                 default:
2987                     return false;
2988             }
2989         }
2990         return false;
2991     }
2992 
2993     /**
2994      * Whether the original connection was ever an IMS connection, either before or now.
2995      * @return {@code True} if the original connection was ever an IMS connection, {@code false}
2996      *     otherwise.
2997      */
wasImsConnection()2998     public boolean wasImsConnection() {
2999         return mWasImsConnection;
3000     }
3001 
getIsUsingAssistedDialing()3002     boolean getIsUsingAssistedDialing() {
3003         return mIsUsingAssistedDialing;
3004     }
3005 
setIsUsingAssistedDialing(Boolean isUsingAssistedDialing)3006     void setIsUsingAssistedDialing(Boolean isUsingAssistedDialing) {
3007         mIsUsingAssistedDialing = isUsingAssistedDialing;
3008         updateConnectionProperties();
3009     }
3010 
getAddressFromNumber(String number)3011     private static Uri getAddressFromNumber(String number) {
3012         // Address can be null for blocked calls.
3013         if (number == null) {
3014             number = "";
3015         }
3016         return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
3017     }
3018 
3019     /**
3020      * Changes a capabilities bit-mask to add or remove a capability.
3021      *
3022      * @param bitmask The bit-mask.
3023      * @param bitfield The bit-field to change.
3024      * @param enabled Whether the bit-field should be set or removed.
3025      * @return The bit-mask with the bit-field changed.
3026      */
changeBitmask(int bitmask, int bitfield, boolean enabled)3027     private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
3028         if (enabled) {
3029             return bitmask | bitfield;
3030         } else {
3031             return bitmask & ~bitfield;
3032         }
3033     }
3034 
updateStatusHints()3035     private void updateStatusHints() {
3036         if (isWifi() && !isCrossSimCall() && getPhone() != null) {
3037             int labelId = isValidRingingCall()
3038                     ? R.string.status_hint_label_incoming_wifi_call
3039                     : R.string.status_hint_label_wifi_call;
3040 
3041             Context context = getPhone().getContext();
3042             setTelephonyStatusHints(new StatusHints(
3043                     getResourceString(labelId),
3044                     Icon.createWithResource(
3045                             context, R.drawable.ic_signal_wifi_4_bar_24dp),
3046                     null /* extras */));
3047         } else {
3048             setTelephonyStatusHints(null);
3049         }
3050     }
3051 
3052     /**
3053      * Register a listener for {@link TelephonyConnection} specific triggers.
3054      * @param l The instance of the listener to add
3055      * @return The connection being listened to
3056      */
addTelephonyConnectionListener(TelephonyConnectionListener l)3057     public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
3058         mTelephonyListeners.add(l);
3059         // If we already have an original connection, let's call back immediately.
3060         // This would be the case for incoming calls.
3061         if (mOriginalConnection != null) {
3062             fireOnOriginalConnectionConfigured();
3063         }
3064         return this;
3065     }
3066 
3067     /**
3068      * Remove a listener for {@link TelephonyConnection} specific triggers.
3069      * @param l The instance of the listener to remove
3070      * @return The connection being listened to
3071      */
removeTelephonyConnectionListener( TelephonyConnectionListener l)3072     public final TelephonyConnection removeTelephonyConnectionListener(
3073             TelephonyConnectionListener l) {
3074         if (l != null) {
3075             mTelephonyListeners.remove(l);
3076         }
3077         return this;
3078     }
3079 
3080     @Override
setHoldable(boolean isHoldable)3081     public void setHoldable(boolean isHoldable) {
3082         mIsHoldable = isHoldable;
3083         updateConnectionCapabilities();
3084     }
3085 
3086     @Override
isChildHoldable()3087     public boolean isChildHoldable() {
3088         return getConference() != null;
3089     }
3090 
isHoldable()3091     public boolean isHoldable() {
3092         return mIsHoldable;
3093     }
3094 
3095     /**
3096      * Fire a callback to the various listeners for when the original connection is
3097      * set in this {@link TelephonyConnection}
3098      */
fireOnOriginalConnectionConfigured()3099     private final void fireOnOriginalConnectionConfigured() {
3100         for (TelephonyConnectionListener l : mTelephonyListeners) {
3101             l.onOriginalConnectionConfigured(this);
3102         }
3103     }
3104 
fireOnOriginalConnectionRetryDial(boolean isPermanentFailure)3105     private final void fireOnOriginalConnectionRetryDial(boolean isPermanentFailure) {
3106         for (TelephonyConnectionListener l : mTelephonyListeners) {
3107             l.onOriginalConnectionRetry(this, isPermanentFailure);
3108         }
3109     }
3110 
3111     /**
3112      * Handles exiting ECM mode.
3113      */
handleExitedEcmMode()3114     protected void handleExitedEcmMode() {
3115         updateConnectionProperties();
3116     }
3117 
3118     /**
3119      * Determines whether the connection supports conference calling.  A connection supports
3120      * conference calling if it:
3121      * 1. Is not an emergency call.
3122      * 2. Carrier supports conference calls.
3123      * 3. If call is a video call, carrier supports video conference calls.
3124      * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls.
3125      */
3126     @VisibleForTesting
refreshConferenceSupported()3127     void refreshConferenceSupported() {
3128         boolean isVideoCall = VideoProfile.isVideo(getVideoState());
3129         Phone phone = getPhone();
3130         if (phone == null) {
3131             Log.w(this, "refreshConferenceSupported = false; phone is null");
3132             if (isConferenceSupported()) {
3133                 setConferenceSupported(false);
3134                 notifyConferenceSupportedChanged(false);
3135             }
3136             return;
3137         }
3138 
3139         boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
3140         boolean isVoWifiEnabled = false;
3141         if (isIms) {
3142             isVoWifiEnabled = isWfcEnabled(phone);
3143         }
3144         PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
3145                 .makePstnPhoneAccountHandle(phone.getDefaultPhone())
3146                 : PhoneUtils.makePstnPhoneAccountHandle(phone);
3147         TelecomAccountRegistry telecomAccountRegistry = getTelecomAccountRegistry(
3148                 getPhone().getContext());
3149         boolean isConferencingSupported = telecomAccountRegistry
3150                 .isMergeCallSupported(phoneAccountHandle);
3151         boolean isImsConferencingSupported = telecomAccountRegistry
3152                 .isMergeImsCallSupported(phoneAccountHandle);
3153         mIsCarrierVideoConferencingSupported = telecomAccountRegistry
3154                 .isVideoConferencingSupported(phoneAccountHandle);
3155         boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry
3156                 .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle);
3157         ImsCall imsCall = isImsConnection()
3158                 ? ((ImsPhoneConnection) getOriginalConnection()).getImsCall()
3159                 : null;
3160         CarrierConfigManager configManager = (CarrierConfigManager) phone.getContext()
3161                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
3162         boolean downGradedVideoCall = false;
3163         if (configManager != null) {
3164             PersistableBundle config = configManager.getConfigForSubId(phone.getSubId());
3165             if (config != null) {
3166                 downGradedVideoCall = config.getBoolean(
3167                         CarrierConfigManager.KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL);
3168             }
3169         }
3170 
3171         Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isImsConfSupp=%b, " +
3172                 "isVidConfSupp=%b, isMergeOfWifiAllowed=%b, " +
3173                 "isWifi=%b, isVoWifiEnabled=%b",
3174                 isConferencingSupported, isImsConferencingSupported,
3175                 mIsCarrierVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff,
3176                 isWifi(), isVoWifiEnabled);
3177         boolean isConferenceSupported = true;
3178         if (mTreatAsEmergencyCall) {
3179             isConferenceSupported = false;
3180             Log.d(this, "refreshConferenceSupported = false; emergency call");
3181         } else if (isRtt() && !isRttMergeSupported(getCarrierConfig())) {
3182             isConferenceSupported = false;
3183             Log.d(this, "refreshConferenceSupported = false; rtt call");
3184         } else if (!isConferencingSupported || isIms && !isImsConferencingSupported) {
3185             isConferenceSupported = false;
3186             Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf.");
3187         } else if (isVideoCall && !mIsCarrierVideoConferencingSupported) {
3188             isConferenceSupported = false;
3189             Log.d(this, "refreshConferenceSupported = false; video conf not supported.");
3190         } else if ((imsCall != null) && (imsCall.wasVideoCall() && downGradedVideoCall)
3191                 && !mIsCarrierVideoConferencingSupported) {
3192             isConferenceSupported = false;
3193             Log.d(this,
3194                     "refreshConferenceSupported = false;"
3195                             + " video conf not supported for downgraded audio call.");
3196         } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) {
3197             isConferenceSupported = false;
3198             Log.d(this,
3199                     "refreshConferenceSupported = false; can't merge wifi calls when voWifi off.");
3200         } else {
3201             Log.d(this, "refreshConferenceSupported = true.");
3202         }
3203 
3204         if (isConferenceSupported != isConferenceSupported()) {
3205             setConferenceSupported(isConferenceSupported);
3206             notifyConferenceSupportedChanged(isConferenceSupported);
3207         }
3208     }
3209 
3210     @VisibleForTesting
isWfcEnabled(Phone phone)3211     boolean isWfcEnabled(Phone phone) {
3212         return ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
3213     }
3214 
3215     /**
3216      * Provides a mapping from extras keys which may be found in the
3217      * {@link com.android.internal.telephony.Connection} to their equivalents defined in
3218      * {@link android.telecom.Connection}.
3219      *
3220      * @return Map containing key mappings.
3221      */
createExtrasMap()3222     private static Map<String, String> createExtrasMap() {
3223         Map<String, String> result = new HashMap<String, String>();
3224         result.put(ImsCallProfile.EXTRA_CHILD_NUMBER,
3225                 android.telecom.Connection.EXTRA_CHILD_ADDRESS);
3226         result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT,
3227                 android.telecom.Connection.EXTRA_CALL_SUBJECT);
3228         result.put(ImsCallProfile.EXTRA_ADDITIONAL_SIP_INVITE_FIELDS,
3229                 android.telecom.Connection.EXTRA_SIP_INVITE);
3230         return Collections.unmodifiableMap(result);
3231     }
3232 
isShowingOriginalDialString()3233     private boolean isShowingOriginalDialString() {
3234         boolean showOrigDialString = false;
3235         Phone phone = getPhone();
3236         if (phone != null && (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA)
3237                 && !mOriginalConnection.isIncoming()) {
3238             showOrigDialString = getCarrierConfig().getBoolean(CarrierConfigManager
3239                     .KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL);
3240             Log.d(this, "showOrigDialString: " + showOrigDialString);
3241         }
3242         return showOrigDialString;
3243     }
3244 
3245     /**
3246      * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
3247      * use in log statements.
3248      *
3249      * @return String representation of the connection.
3250      */
3251     @Override
toString()3252     public String toString() {
3253         StringBuilder sb = new StringBuilder();
3254         sb.append("[TelephonyConnection objId:");
3255         sb.append(System.identityHashCode(this));
3256         sb.append(" telecomCallID:");
3257         sb.append(getTelecomCallId());
3258         sb.append(" type:");
3259         if (isImsConnection()) {
3260             sb.append("ims");
3261         } else if (this instanceof com.android.services.telephony.GsmConnection) {
3262             sb.append("gsm");
3263         } else if (this instanceof CdmaConnection) {
3264             sb.append("cdma");
3265         }
3266         sb.append(" state:");
3267         sb.append(Connection.stateToString(getState()));
3268         sb.append(" capabilities:");
3269         sb.append(capabilitiesToString(getConnectionCapabilities()));
3270         sb.append(" properties:");
3271         sb.append(propertiesToString(getConnectionProperties()));
3272         sb.append(" address:");
3273         sb.append(Rlog.pii(LOG_TAG, getAddress()));
3274         sb.append(" originalConnection:");
3275         sb.append(mOriginalConnection);
3276         sb.append(" partOfConf:");
3277         if (getConference() == null) {
3278             sb.append("N");
3279         } else {
3280             sb.append("Y");
3281         }
3282         sb.append(" confSupported:");
3283         sb.append(mIsConferenceSupported ? "Y" : "N");
3284         sb.append(" isAdhocConf:");
3285         sb.append(isAdhocConferenceCall() ? "Y" : "N");
3286         sb.append("]");
3287         return sb.toString();
3288     }
3289 
setTelephonyConnectionService(TelephonyConnectionService connectionService)3290     public final void setTelephonyConnectionService(TelephonyConnectionService connectionService) {
3291         mTelephonyConnectionService = connectionService;
3292     }
3293 
getTelephonyConnectionService()3294     public final TelephonyConnectionService getTelephonyConnectionService() {
3295         return mTelephonyConnectionService;
3296     }
3297 
3298     /**
3299      * Set this {@link TelephonyConnection} to an active state.
3300      * <p>
3301      * Note: This should be used instead of {@link #setActive()} to ensure listeners are notified.
3302      */
setTelephonyConnectionActive()3303     public void setTelephonyConnectionActive() {
3304         setActive();
3305         notifyStateChanged(getState());
3306     }
3307 
3308     /**
3309      * Set this {@link TelephonyConnection} to a ringing state.
3310      * <p>
3311      * Note: This should be used instead of {@link #setRinging()} to ensure listeners are notified.
3312      */
setTelephonyConnectionRinging()3313     public void setTelephonyConnectionRinging() {
3314         setRinging();
3315         notifyStateChanged(getState());
3316     }
3317 
3318     /**
3319      * Set this {@link TelephonyConnection} to an initializing state.
3320      * <p>
3321      * Note: This should be used instead of {@link #setInitializing()} to ensure listeners are
3322      * notified.
3323      */
setTelephonyConnectionInitializing()3324     public void setTelephonyConnectionInitializing() {
3325         setInitializing();
3326         notifyStateChanged(getState());
3327     }
3328 
3329     /**
3330      * Set this {@link TelephonyConnection} to a dialing state.
3331      * <p>
3332      * Note: This should be used instead of {@link #setDialing()} to ensure listeners are notified.
3333      */
setTelephonyConnectionDialing()3334     public void setTelephonyConnectionDialing() {
3335         setDialing();
3336         notifyStateChanged(getState());
3337     }
3338 
3339     /**
3340      * Set this {@link TelephonyConnection} to a pulling state.
3341      * <p>
3342      * Note: This should be used instead of {@link #setPulling()} to ensure listeners are notified.
3343      */
setTelephonyConnectionPulling()3344     public void setTelephonyConnectionPulling() {
3345         setPulling();
3346         notifyStateChanged(getState());
3347     }
3348 
3349     /**
3350      * Set this {@link TelephonyConnection} to a held state.
3351      * <p>
3352      * Note: This should be used instead of {@link #setOnHold()} to ensure listeners are notified.
3353      */
setTelephonyConnectionOnHold()3354     public void setTelephonyConnectionOnHold() {
3355         setOnHold();
3356         notifyStateChanged(getState());
3357     }
3358 
3359     /**
3360      * Set this {@link TelephonyConnection} to a disconnected state.
3361      * <p>
3362      * Note: This should be used instead of
3363      * {@link #setDisconnected(android.telecom.DisconnectCause)} to ensure listeners are notified.
3364      */
setTelephonyConnectionDisconnected(@onNull android.telecom.DisconnectCause disconnectCause)3365     public void setTelephonyConnectionDisconnected(@NonNull
3366             android.telecom.DisconnectCause disconnectCause) {
3367         setDisconnected(disconnectCause);
3368         notifyDisconnected(disconnectCause);
3369         notifyStateChanged(getState());
3370     }
3371 
3372     /**
3373      * Sends a connection event for this {@link TelephonyConnection}.
3374      * <p>
3375      * Note: This should be used instead of {@link #sendConnectionEvent(String, Bundle)} to ensure
3376      * listeners are notified.
3377      */
sendTelephonyConnectionEvent(@onNull String event, @Nullable Bundle extras)3378     public void sendTelephonyConnectionEvent(@NonNull String event, @Nullable Bundle extras) {
3379         sendConnectionEvent(event, extras);
3380         notifyTelephonyConnectionEvent(event, extras);
3381     }
3382 
3383     /**
3384      * Sets the extras associated with this {@link TelephonyConnection}.
3385      * <p>
3386      * Note: This should be used instead of {@link #putExtras(Bundle)} to ensure listeners are
3387      * notified.
3388      */
putTelephonyExtras(@onNull Bundle extras)3389     public void putTelephonyExtras(@NonNull Bundle extras) {
3390         putExtras(extras);
3391         notifyPutExtras(extras);
3392     }
3393 
3394     /**
3395      * Removes the specified extras associated with this {@link TelephonyConnection}.
3396      * <p>
3397      * Note: This should be used instead of {@link #removeExtras(String...)} to ensure listeners are
3398      * notified.
3399      */
removeTelephonyExtras(@onNull List<String> keys)3400     public void removeTelephonyExtras(@NonNull List<String> keys) {
3401         removeExtras(keys);
3402         notifyRemoveExtras(keys);
3403     }
3404 
3405     /**
3406      * Sets the video state associated with this {@link TelephonyConnection}.
3407      * <p>
3408      * Note: This should be used instead of {@link #setVideoState(int)} to ensure listeners are
3409      * notified.
3410      * @param videoState The new video state. Valid values:
3411      *                   {@link VideoProfile#STATE_AUDIO_ONLY},
3412      *                   {@link VideoProfile#STATE_BIDIRECTIONAL},
3413      *                   {@link VideoProfile#STATE_TX_ENABLED},
3414      *                   {@link VideoProfile#STATE_RX_ENABLED}.
3415      */
setTelephonyVideoState(int videoState)3416     public void setTelephonyVideoState(int videoState) {
3417         setVideoState(videoState);
3418         notifyVideoStateChanged(videoState);
3419     }
3420 
3421     /**
3422      * Sets the video provider associated with this {@link TelephonyConnection}.
3423      * <p>
3424      * Note: This should be used instead of {@link #setVideoProvider(VideoProvider)} to ensure
3425      * listeners are notified.
3426      */
setTelephonyVideoProvider(@ullable VideoProvider videoProvider)3427     public void setTelephonyVideoProvider(@Nullable VideoProvider videoProvider) {
3428         setVideoProvider(videoProvider);
3429         notifyVideoProviderChanged(videoProvider);
3430     }
3431 
3432     /**
3433      * Sets the status hints associated with this {@link TelephonyConnection}.
3434      * <p>
3435      * Note: This should be used instead of {@link #setStatusHints(StatusHints)} to ensure listeners
3436      * are notified.
3437      */
setTelephonyStatusHints(@ullable StatusHints statusHints)3438     public void setTelephonyStatusHints(@Nullable StatusHints statusHints) {
3439         setStatusHints(statusHints);
3440         notifyStatusHintsChanged(statusHints);
3441     }
3442 
3443     /**
3444      * Sets RIL voice radio technology used for current connection.
3445      * <p>
3446      * This property is set by the Telephony {@link ConnectionService}.
3447      *
3448      * @param vrat the RIL Voice Radio Technology used for current connection,
3449      *             see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
3450      */
setCallRadioTech(@ilRadioTechnology int vrat)3451     public final void setCallRadioTech(@RilRadioTechnology int vrat) {
3452         Bundle extras = getExtras();
3453         if (extras == null) {
3454             extras = new Bundle();
3455         }
3456         extras.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE,
3457                 ServiceState.rilRadioTechnologyToNetworkType(vrat));
3458         putExtras(extras);
3459         // Propagates the call radio technology to its parent {@link android.telecom.Conference}
3460         // This action only covers non-IMS CS conference calls.
3461         // For IMS PS call conference call, it can be updated via its host connection
3462         // {@link #Listener.onExtrasChanged} event.
3463         if (getConference() != null) {
3464             Bundle newExtras = new Bundle();
3465             newExtras.putInt(
3466                     TelecomManager.EXTRA_CALL_NETWORK_TYPE,
3467                     ServiceState.rilRadioTechnologyToNetworkType(vrat));
3468             getConference().putExtras(newExtras);
3469         }
3470     }
3471 
3472     /**
3473      * Returns RIL voice radio technology used for current connection.
3474      * <p>
3475      * Used by the Telephony {@link ConnectionService}.
3476      *
3477      * @return the RIL voice radio technology used for current connection,
3478      *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
3479      */
getCallRadioTech()3480     public final @RilRadioTechnology int getCallRadioTech() {
3481         int voiceNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
3482         Bundle extras = getExtras();
3483         if (extras != null) {
3484             voiceNetworkType = extras.getInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE,
3485                     TelephonyManager.NETWORK_TYPE_UNKNOWN);
3486         }
3487         return ServiceState.networkTypeToRilRadioTechnology(voiceNetworkType);
3488     }
3489 
3490     /**
3491      * Notifies {@link TelephonyConnectionListener}s of a change to conference participant data
3492      * received via the {@link ImsConference} (i.e. conference event package).
3493      *
3494      * @param conferenceParticipants The participants.
3495      */
updateConferenceParticipants( @onNull List<ConferenceParticipant> conferenceParticipants)3496     private void updateConferenceParticipants(
3497             @NonNull List<ConferenceParticipant> conferenceParticipants) {
3498         for (TelephonyConnectionListener l : mTelephonyListeners) {
3499             l.onConferenceParticipantsChanged(this, conferenceParticipants);
3500         }
3501     }
3502 
3503     /**
3504      * Where device to device communication is available and this is an IMS call, configures the
3505      * D2D communication infrastructure for operation.
3506      */
maybeConfigureDeviceToDeviceCommunication()3507     private void maybeConfigureDeviceToDeviceCommunication() {
3508         if (!getPhone().getContext().getResources().getBoolean(
3509                 R.bool.config_use_device_to_device_communication)) {
3510             Log.i(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
3511             notifyD2DAvailabilityChanged(false);
3512             return;
3513         }
3514         if (!isImsConnection()) {
3515             Log.i(this, "maybeConfigureDeviceToDeviceCommunication: not an IMS connection.");
3516             if (mCommunicator != null) {
3517                 mCommunicator = null;
3518             }
3519             notifyD2DAvailabilityChanged(false);
3520             return;
3521         }
3522         if (mTreatAsEmergencyCall || mIsNetworkIdentifiedEmergencyCall) {
3523             Log.i(this, "maybeConfigureDeviceToDeviceCommunication: emergency call; no D2D");
3524             notifyD2DAvailabilityChanged(false);
3525             return;
3526         }
3527 
3528         ArrayList<TransportProtocol> supportedTransports = new ArrayList<>(2);
3529 
3530         if (supportsD2DUsingRtp()) {
3531             Log.i(this, "maybeConfigureDeviceToDeviceCommunication: carrier supports RTP.");
3532             // Implement abstracted out RTP functionality the RTP transport depends on.
3533             RtpAdapter rtpAdapter = new RtpAdapter() {
3534                 @Override
3535                 public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
3536                     ImsPhoneConnection originalConnection =
3537                             (ImsPhoneConnection) mOriginalConnection;
3538                     return originalConnection.getAcceptedRtpHeaderExtensions();
3539                 }
3540 
3541                 @Override
3542                 public void sendRtpHeaderExtensions(
3543                         @NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
3544                     Log.i(TelephonyConnection.this, "sendRtpHeaderExtensions: sending: %s",
3545                             rtpHeaderExtensions.stream()
3546                                     .map(r -> r.toString())
3547                                     .collect(Collectors.joining(",")));
3548                     ImsPhoneConnection originalConnection =
3549                             (ImsPhoneConnection) mOriginalConnection;
3550                     originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
3551                 }
3552             };
3553             mRtpTransport = new RtpTransport(rtpAdapter, null /* TODO: not needed yet */, mHandler,
3554                     supportsSdpNegotiationOfRtpHeaderExtensions());
3555             supportedTransports.add(mRtpTransport);
3556         }
3557         if (supportsD2DUsingDtmf()) {
3558             Log.i(this, "maybeConfigureDeviceToDeviceCommunication: carrier supports DTMF.");
3559             DtmfAdapter dtmfAdapter = digit -> {
3560                 Log.i(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
3561                 ImsPhoneConnection originalConnection =
3562                         (ImsPhoneConnection) mOriginalConnection;
3563                 Message dtmfComplete = mHandler.obtainMessage(MSG_DTMF_DONE);
3564                 dtmfComplete.replyTo = mHandlerMessenger;
3565                 originalConnection.getImsCall().sendDtmf(digit, dtmfComplete);
3566             };
3567             ContentResolver cr = getPhone().getContext().getContentResolver();
3568             mDtmfTransport = new DtmfTransport(dtmfAdapter, new Timeouts.Adapter(cr),
3569                     Executors.newSingleThreadScheduledExecutor());
3570             supportedTransports.add(mDtmfTransport);
3571         }
3572         if (supportedTransports.size() > 0) {
3573             mCommunicator = new Communicator(supportedTransports, this);
3574             mD2DCallStateAdapter = new D2DCallStateAdapter(mCommunicator);
3575             addTelephonyConnectionListener(mD2DCallStateAdapter);
3576         } else {
3577             Log.i(this, "maybeConfigureDeviceToDeviceCommunication: no transports; disabled.");
3578             notifyD2DAvailabilityChanged(false);
3579         }
3580     }
3581 
3582     /**
3583      * Notifies upper layers of the availability of D2D communication.
3584      * @param isAvailable {@code true} if D2D is available, {@code false} otherwise.
3585      */
notifyD2DAvailabilityChanged(boolean isAvailable)3586     private void notifyD2DAvailabilityChanged(boolean isAvailable) {
3587         Bundle extras = new Bundle();
3588         extras.putBoolean(Connection.EXTRA_IS_DEVICE_TO_DEVICE_COMMUNICATION_AVAILABLE,
3589                 isAvailable);
3590         putTelephonyExtras(extras);
3591     }
3592 
3593     /**
3594      * @return The D2D communication class, or {@code null} if not set up.
3595      */
getCommunicator()3596     public @Nullable Communicator getCommunicator() {
3597         return mCommunicator;
3598     }
3599 
3600     /**
3601      * Called by {@link Communicator} associated with this {@link TelephonyConnection} when there
3602      * are incoming device-to-device messages received.
3603      * @param messages the incoming messages.
3604      */
3605     @Override
onMessagesReceived(@onNull Set<Communicator.Message> messages)3606     public void onMessagesReceived(@NonNull Set<Communicator.Message> messages) {
3607         Log.i(this, "onMessagesReceived: got d2d messages: %s", messages);
3608         // Send connection events up to Telecom so that we can relay the messages to a valid
3609         // CallDiagnosticService which is bound.
3610         for (Communicator.Message msg : messages) {
3611             Integer dcMsgType = MessageTypeAndValueHelper.MSG_TYPE_TO_DC_MSG_TYPE.getValue(
3612                     msg.getType());
3613             if (dcMsgType == null) {
3614                 // Invalid msg type, skip.
3615                 continue;
3616             }
3617 
3618             Integer dcMsgValue;
3619             switch (msg.getType()) {
3620                 case CallDiagnostics.MESSAGE_CALL_AUDIO_CODEC:
3621                     dcMsgValue = MessageTypeAndValueHelper.CODEC_TO_DC_CODEC.getValue(
3622                             msg.getValue());
3623                     break;
3624                 case CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE:
3625                     dcMsgValue = MessageTypeAndValueHelper.RAT_TYPE_TO_DC_NETWORK_TYPE.getValue(
3626                             msg.getValue());
3627                     break;
3628                 case CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE:
3629                     dcMsgValue = MessageTypeAndValueHelper.BATTERY_STATE_TO_DC_BATTERY_STATE
3630                             .getValue(msg.getValue());
3631                     break;
3632                 case CallDiagnostics.MESSAGE_DEVICE_NETWORK_COVERAGE:
3633                     dcMsgValue = MessageTypeAndValueHelper.COVERAGE_TO_DC_COVERAGE
3634                             .getValue(msg.getValue());
3635                     break;
3636                 default:
3637                     Log.w(this, "onMessagesReceived: msg=%d - invalid msg", msg.getValue());
3638                     continue;
3639             }
3640             if (dcMsgValue == null) {
3641                 Log.w(this, "onMessagesReceived: msg=%d/%d - invalid msg value", msg.getType(),
3642                         msg.getValue());
3643                 continue;
3644             }
3645             Bundle extras = new Bundle();
3646             extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, dcMsgType);
3647             extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, dcMsgValue);
3648             sendConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, extras);
3649         }
3650     }
3651 
3652     /**
3653      * Handles report from {@link Communicator} when the availability of D2D changes.
3654      * @param isAvailable {@code true} if D2D is available, {@code false} if unavailable.
3655      */
3656     @Override
onD2DAvailabilitychanged(boolean isAvailable)3657     public void onD2DAvailabilitychanged(boolean isAvailable) {
3658         notifyD2DAvailabilityChanged(isAvailable);
3659     }
3660 
3661     /**
3662      * Called by a {@link ConnectionService} to notify Telecom that a {@link Conference#onMerge()}
3663      * operation has started.
3664      */
notifyConferenceStarted()3665     protected void notifyConferenceStarted() {
3666         for (TelephonyConnectionListener l : mTelephonyListeners) {
3667             l.onConferenceStarted();
3668         }
3669     }
3670 
3671     /**
3672      * Notifies {@link TelephonyConnectionListener}s when a change has occurred to the Connection
3673      * which impacts its ability to be a part of a conference call.
3674      * @param isConferenceSupported {@code true} if the connection supports being part of a
3675      *      conference call, {@code false} otherwise.
3676      */
notifyConferenceSupportedChanged(boolean isConferenceSupported)3677     private void notifyConferenceSupportedChanged(boolean isConferenceSupported) {
3678         for (TelephonyConnectionListener l : mTelephonyListeners) {
3679             l.onConferenceSupportedChanged(this, isConferenceSupported);
3680         }
3681     }
3682 
3683     /**
3684      * Notifies {@link TelephonyConnectionListener}s of changes to the connection capabilities.
3685      * @param newCapabilities the new capabilities.
3686      */
notifyConnectionCapabilitiesChanged(int newCapabilities)3687     private void notifyConnectionCapabilitiesChanged(int newCapabilities) {
3688         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3689             listener.onConnectionCapabilitiesChanged(this, newCapabilities);
3690         }
3691     }
3692 
3693     /**
3694      * Notifies {@link TelephonyConnectionListener}s of changes to the connection properties.
3695      * @param newProperties the new properties.
3696      */
notifyConnectionPropertiesChanged(int newProperties)3697     private void notifyConnectionPropertiesChanged(int newProperties) {
3698         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3699             listener.onConnectionPropertiesChanged(this, newProperties);
3700         }
3701     }
3702 
3703     /**
3704      * Notifies {@link TelephonyConnectionListener}s when a connection is destroyed.
3705      */
notifyDestroyed()3706     private void notifyDestroyed() {
3707         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3708             listener.onDestroyed(this);
3709         }
3710     }
3711 
3712     /**
3713      * Notifies {@link TelephonyConnectionListener}s when a connection disconnects.
3714      * @param cause The disconnect cause.
3715      */
notifyDisconnected(android.telecom.DisconnectCause cause)3716     private void notifyDisconnected(android.telecom.DisconnectCause cause) {
3717         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3718             listener.onDisconnected(this, cause);
3719         }
3720     }
3721 
3722     /**
3723      * Notifies {@link TelephonyConnectionListener}s of connection state changes.
3724      * @param newState The new state.
3725      */
notifyStateChanged(int newState)3726     private void notifyStateChanged(int newState) {
3727         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3728             listener.onStateChanged(this, newState);
3729         }
3730     }
3731 
3732     /**
3733      * Notifies {@link TelephonyConnectionListener}s of telephony connection events.
3734      * @param event The event.
3735      * @param extras Any extras.
3736      */
notifyTelephonyConnectionEvent(String event, Bundle extras)3737     private void notifyTelephonyConnectionEvent(String event, Bundle extras) {
3738         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3739             listener.onConnectionEvent(this, event, extras);
3740         }
3741     }
3742 
3743     /**
3744      * Notifies {@link TelephonyConnectionListener}s when extras are added to the connection.
3745      * @param extras The new extras.
3746      */
notifyPutExtras(Bundle extras)3747     private void notifyPutExtras(Bundle extras) {
3748         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3749             listener.onExtrasChanged(this, extras);
3750         }
3751     }
3752 
3753     /**
3754      * Notifies {@link TelephonyConnectionListener}s when extra keys are removed from a connection.
3755      * @param keys The removed keys.
3756      */
notifyRemoveExtras(List<String> keys)3757     private void notifyRemoveExtras(List<String> keys) {
3758         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3759             listener.onExtrasRemoved(this, keys);
3760         }
3761     }
3762 
3763     /**
3764      * Notifies {@link TelephonyConnectionListener}s of a change to the video state of a connection.
3765      * @param videoState The new video state. Valid values:
3766      *                   {@link VideoProfile#STATE_AUDIO_ONLY},
3767      *                   {@link VideoProfile#STATE_BIDIRECTIONAL},
3768      *                   {@link VideoProfile#STATE_TX_ENABLED},
3769      *                   {@link VideoProfile#STATE_RX_ENABLED}.
3770      */
notifyVideoStateChanged(int videoState)3771     private void notifyVideoStateChanged(int videoState) {
3772         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3773             listener.onVideoStateChanged(this, videoState);
3774         }
3775     }
3776 
3777     /**
3778      * Notifies {@link TelephonyConnectionListener}s of a whether to play Ringback Tone or not.
3779      * @param ringback Whether the ringback tone is to be played
3780      */
notifyRingbackRequested(boolean ringback)3781     private void notifyRingbackRequested(boolean ringback) {
3782         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3783             listener.onRingbackRequested(this, ringback);
3784         }
3785     }
3786 
3787     /**
3788      * Notifies {@link TelephonyConnectionListener}s of changes to the video provider for a
3789      * connection.
3790      * @param videoProvider The new video provider.
3791      */
notifyVideoProviderChanged(VideoProvider videoProvider)3792     private void notifyVideoProviderChanged(VideoProvider videoProvider) {
3793         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3794             listener.onVideoProviderChanged(this, videoProvider);
3795         }
3796     }
3797 
3798     /**
3799      * Notifies {@link TelephonyConnectionListener}s of changes to the status hints for a
3800      * connection.
3801      * @param statusHints The new status hints.
3802      */
notifyStatusHintsChanged(StatusHints statusHints)3803     private void notifyStatusHintsChanged(StatusHints statusHints) {
3804         for (TelephonyConnectionListener listener : mTelephonyListeners) {
3805             listener.onStatusHintsChanged(this, statusHints);
3806         }
3807     }
3808 
3809     /**
3810      * Whether the incoming call number should be formatted to national number for Japan.
3811      * @return {@code true} should be convert to the national format, {@code false} otherwise.
3812      */
isNeededToFormatIncomingNumberForJp()3813     private boolean isNeededToFormatIncomingNumberForJp() {
3814         if (mOriginalConnection.isIncoming()
3815                 && !TextUtils.isEmpty(mOriginalConnection.getAddress())
3816                 && mOriginalConnection.getAddress().startsWith(JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN)) {
3817             return getCarrierConfig().getBoolean(
3818                     CarrierConfigManager.KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL);
3819         }
3820         return false;
3821     }
3822 
3823     /**
3824      * Format the incoming call number to national number for Japan.
3825      * @param number
3826      * @return the formatted phone number (e.g, "+819012345678" -> "09012345678")
3827      */
formatIncomingNumberForJp(String number)3828     private String formatIncomingNumberForJp(String number) {
3829         return PhoneNumberUtils.stripSeparators(
3830                 PhoneNumberUtils.formatNumber(number, JAPAN_ISO_COUNTRY_CODE));
3831     }
3832 
getTelecomAccountRegistry(Context context)3833     public TelecomAccountRegistry getTelecomAccountRegistry(Context context) {
3834         return TelecomAccountRegistry.getInstance(context);
3835     }
3836 
3837     /**
3838      * @return {@code true} if the carrier supports D2D using RTP header extensions, {@code false}
3839      * otherwise.
3840      */
supportsD2DUsingRtp()3841     private boolean supportsD2DUsingRtp() {
3842         return getCarrierConfig().getBoolean(
3843                 CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL);
3844     }
3845 
3846     /**
3847      * @return {@code true} if the carrier supports D2D using DTMF digits, {@code false} otherwise.
3848      */
supportsD2DUsingDtmf()3849     private boolean supportsD2DUsingDtmf() {
3850         return getCarrierConfig().getBoolean(
3851                 CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL);
3852     }
3853 
3854     /**
3855      * @return {@code true} if the carrier supports using SDP negotiation for the RTP header
3856      * extensions used in D2D comms, {@code false} otherwise.
3857      */
supportsSdpNegotiationOfRtpHeaderExtensions()3858     private boolean supportsSdpNegotiationOfRtpHeaderExtensions() {
3859         return getCarrierConfig().getBoolean(
3860                 CarrierConfigManager
3861                         .KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL);
3862     }
3863 
3864     /**
3865      * Handles a device to device message which a {@link CallDiagnostics} wishes to send.
3866      * @param extras the call event extras bundle.
3867      */
handleOutgoingDeviceToDeviceMessage(Bundle extras)3868     private void handleOutgoingDeviceToDeviceMessage(Bundle extras) {
3869         int messageType = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE);
3870         int messageValue = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE);
3871 
3872         Integer internalMessageValue;
3873         switch (messageType) {
3874             case CallDiagnostics.MESSAGE_CALL_AUDIO_CODEC:
3875                 internalMessageValue = MessageTypeAndValueHelper.CODEC_TO_DC_CODEC.getKey(
3876                         messageValue);
3877                 break;
3878             case CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE:
3879                 internalMessageValue = MessageTypeAndValueHelper.RAT_TYPE_TO_DC_NETWORK_TYPE.getKey(
3880                         messageValue);
3881                 break;
3882             case CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE:
3883                 internalMessageValue = MessageTypeAndValueHelper.BATTERY_STATE_TO_DC_BATTERY_STATE
3884                         .getKey(messageValue);
3885                 break;
3886             case CallDiagnostics.MESSAGE_DEVICE_NETWORK_COVERAGE:
3887                 internalMessageValue = MessageTypeAndValueHelper.COVERAGE_TO_DC_COVERAGE
3888                         .getKey(messageValue);
3889                 break;
3890             default:
3891                 Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d - invalid msg",
3892                         messageType);
3893                 return;
3894         }
3895         Integer internalMessageType = MessageTypeAndValueHelper.MSG_TYPE_TO_DC_MSG_TYPE.getKey(
3896                 messageType);
3897         if (internalMessageValue == null) {
3898             Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d/%d - invalid value",
3899                     messageType, messageValue);
3900             return;
3901         }
3902 
3903         if (mCommunicator != null) {
3904             Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d/%d - sending",
3905                     internalMessageType, internalMessageValue);
3906             Set<Communicator.Message> set = new ArraySet<>();
3907             set.add(new Communicator.Message(internalMessageType, internalMessageValue));
3908             mCommunicator.sendMessages(set);
3909         }
3910     }
3911 
3912     /**
3913      * Returns the current telephony connection listeners for test purposes.
3914      * @return list of telephony connection listeners.
3915      */
3916     @VisibleForTesting
getTelephonyConnectionListeners()3917     public List<TelephonyConnectionListener> getTelephonyConnectionListeners() {
3918         return new ArrayList<>(mTelephonyListeners);
3919     }
3920 
3921     /**
3922      * @return An {@link Integer} instance of the emergency service category.
3923      */
getEmergencyServiceCategory()3924     public @Nullable Integer getEmergencyServiceCategory() {
3925         return mEmergencyServiceCategory;
3926     }
3927 
3928     /**
3929      * Sets the emergency service category.
3930      *
3931      * @param eccCategory The emergency service category.
3932      */
3933     @VisibleForTesting
setEmergencyServiceCategory(int eccCategory)3934     public void setEmergencyServiceCategory(int eccCategory) {
3935         mEmergencyServiceCategory = eccCategory;
3936     }
3937 }
3938