TelephonyConnection.java revision 13ae12c2b4519b28ad25ceb699545991199c0e11
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
17package com.android.services.telephony;
18
19import android.content.Context;
20import android.graphics.drawable.Icon;
21import android.net.Uri;
22import android.os.AsyncResult;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Message;
26import android.telecom.CallAudioState;
27import android.telecom.ConferenceParticipant;
28import android.telecom.Connection;
29import android.telecom.PhoneAccount;
30import android.telecom.PhoneAccountHandle;
31import android.telecom.StatusHints;
32import android.telecom.TelecomManager;
33import android.telecom.VideoProfile;
34import android.telephony.PhoneNumberUtils;
35import android.util.Pair;
36
37import com.android.ims.ImsCallProfile;
38import com.android.internal.telephony.Call;
39import com.android.internal.telephony.CallStateException;
40import com.android.internal.telephony.Connection.Capability;
41import com.android.internal.telephony.Connection.PostDialListener;
42import com.android.internal.telephony.PhoneConstants;
43import com.android.internal.telephony.gsm.SuppServiceNotification;
44
45import com.android.internal.telephony.Phone;
46import com.android.internal.telephony.imsphone.ImsPhone;
47import com.android.phone.PhoneUtils;
48import com.android.phone.R;
49
50import java.lang.Override;
51import java.util.Arrays;
52import java.util.ArrayList;
53import java.util.Collections;
54import java.util.HashMap;
55import java.util.List;
56import java.util.Map;
57import java.util.Objects;
58import java.util.Set;
59import java.util.concurrent.ConcurrentHashMap;
60
61/**
62 * Base class for CDMA and GSM connections.
63 */
64abstract class TelephonyConnection extends Connection {
65    private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
66    private static final int MSG_RINGBACK_TONE = 2;
67    private static final int MSG_HANDOVER_STATE_CHANGED = 3;
68    private static final int MSG_DISCONNECT = 4;
69    private static final int MSG_MULTIPARTY_STATE_CHANGED = 5;
70    private static final int MSG_CONFERENCE_MERGE_FAILED = 6;
71    private static final int MSG_SUPP_SERVICE_NOTIFY = 7;
72
73    /**
74     * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their
75     * equivalents defined in {@link android.telecom.Connection}.
76     */
77    private static final Map<String, String> sExtrasMap = createExtrasMap();
78
79    private static final int MSG_SET_VIDEO_STATE = 8;
80    private static final int MSG_SET_VIDEO_PROVIDER = 9;
81    private static final int MSG_SET_AUDIO_QUALITY = 10;
82    private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11;
83    private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12;
84    private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13;
85    private static final int MSG_ON_HOLD_TONE = 14;
86    private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15;
87    private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16;
88
89    private final Handler mHandler = new Handler() {
90        @Override
91        public void handleMessage(Message msg) {
92            switch (msg.what) {
93                case MSG_PRECISE_CALL_STATE_CHANGED:
94                    Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
95                    updateState();
96                    break;
97                case MSG_HANDOVER_STATE_CHANGED:
98                    Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED");
99                    AsyncResult ar = (AsyncResult) msg.obj;
100                    com.android.internal.telephony.Connection connection =
101                         (com.android.internal.telephony.Connection) ar.result;
102                    if (mOriginalConnection != null) {
103                        if (connection != null &&
104                            ((connection.getAddress() != null &&
105                            mOriginalConnection.getAddress() != null &&
106                            mOriginalConnection.getAddress().contains(connection.getAddress())) ||
107                            connection.getState() == mOriginalConnection.getStateBeforeHandover())) {
108                            Log.d(TelephonyConnection.this,
109                                    "SettingOriginalConnection " + mOriginalConnection.toString()
110                                            + " with " + connection.toString());
111                            setOriginalConnection(connection);
112                            mWasImsConnection = false;
113                        }
114                    } else {
115                        Log.w(TelephonyConnection.this,
116                                "MSG_HANDOVER_STATE_CHANGED: mOriginalConnection==null - invalid state (not cleaned up)");
117                    }
118                    break;
119                case MSG_RINGBACK_TONE:
120                    Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
121                    // TODO: This code assumes that there is only one connection in the foreground
122                    // call, in other words, it punts on network-mediated conference calling.
123                    if (getOriginalConnection() != getForegroundConnection()) {
124                        Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
125                                "not foreground connection, skipping");
126                        return;
127                    }
128                    setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result);
129                    break;
130                case MSG_DISCONNECT:
131                    updateState();
132                    break;
133                case MSG_MULTIPARTY_STATE_CHANGED:
134                    boolean isMultiParty = (Boolean) msg.obj;
135                    Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
136                    mIsMultiParty = isMultiParty;
137                    if (isMultiParty) {
138                        notifyConferenceStarted();
139                    }
140                    break;
141                case MSG_CONFERENCE_MERGE_FAILED:
142                    notifyConferenceMergeFailed();
143                    break;
144                case MSG_SUPP_SERVICE_NOTIFY:
145                    Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : "
146                            +getPhone().getPhoneId());
147                    SuppServiceNotification mSsNotification = null;
148                    if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
149                        mSsNotification =
150                                (SuppServiceNotification)((AsyncResult) msg.obj).result;
151                        if (mOriginalConnection != null && mSsNotification.history != null) {
152                            Bundle lastForwardedNumber = new Bundle();
153                            Log.v(TelephonyConnection.this,
154                                    "Updating call history info in extras.");
155                            lastForwardedNumber.putStringArrayList(
156                                Connection.EXTRA_LAST_FORWARDED_NUMBER,
157                                new ArrayList(Arrays.asList(mSsNotification.history)));
158                            putExtras(lastForwardedNumber);
159                        }
160                    }
161                    break;
162
163                case MSG_SET_VIDEO_STATE:
164                    int videoState = (int) msg.obj;
165                    setVideoState(videoState);
166
167                    // A change to the video state of the call can influence whether or not it
168                    // can be part of a conference.
169                    refreshConferenceSupported();
170                    break;
171
172                case MSG_SET_VIDEO_PROVIDER:
173                    VideoProvider videoProvider = (VideoProvider) msg.obj;
174                    setVideoProvider(videoProvider);
175                    break;
176
177                case MSG_SET_AUDIO_QUALITY:
178                    int audioQuality = (int) msg.obj;
179                    setAudioQuality(audioQuality);
180                    break;
181
182                case MSG_SET_CONFERENCE_PARTICIPANTS:
183                    List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj;
184                    updateConferenceParticipants(participants);
185                    break;
186
187                case MSG_CONNECTION_EXTRAS_CHANGED:
188                    final Bundle extras = (Bundle) msg.obj;
189                    updateExtras(extras);
190                    break;
191
192                case MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES:
193                    setOriginalConnectionCapabilities(msg.arg1);
194                    break;
195
196                case MSG_ON_HOLD_TONE:
197                    AsyncResult asyncResult = (AsyncResult) msg.obj;
198                    Pair<com.android.internal.telephony.Connection, Boolean> heldInfo =
199                            (Pair<com.android.internal.telephony.Connection, Boolean>)
200                                    asyncResult.result;
201
202                    // Determines if the hold tone is starting or stopping.
203                    boolean playTone = ((Boolean) (heldInfo.second)).booleanValue();
204
205                    // Determine which connection the hold tone is stopping or starting for
206                    com.android.internal.telephony.Connection heldConnection = heldInfo.first;
207
208                    // Only start or stop the hold tone if this is the connection which is starting
209                    // or stopping the hold tone.
210                    if (heldConnection == mOriginalConnection) {
211                        // If starting the hold tone, send a connection event to Telecom which will
212                        // cause it to play the on hold tone.
213                        if (playTone) {
214                            sendConnectionEvent(EVENT_ON_HOLD_TONE_START, null);
215                        } else {
216                            sendConnectionEvent(EVENT_ON_HOLD_TONE_END, null);
217                        }
218                    }
219                    break;
220
221                case MSG_CDMA_VOICE_PRIVACY_ON:
222                    Log.d(this, "MSG_CDMA_VOICE_PRIVACY_ON received");
223                    setCdmaVoicePrivacy(true);
224                    break;
225                case MSG_CDMA_VOICE_PRIVACY_OFF:
226                    Log.d(this, "MSG_CDMA_VOICE_PRIVACY_OFF received");
227                    setCdmaVoicePrivacy(false);
228                    break;
229            }
230        }
231    };
232
233    /**
234     * A listener/callback mechanism that is specific communication from TelephonyConnections
235     * to TelephonyConnectionService (for now). It is more specific that Connection.Listener
236     * because it is only exposed in Telephony.
237     */
238    public abstract static class TelephonyConnectionListener {
239        public void onOriginalConnectionConfigured(TelephonyConnection c) {}
240    }
241
242    private final PostDialListener mPostDialListener = new PostDialListener() {
243        @Override
244        public void onPostDialWait() {
245            Log.v(TelephonyConnection.this, "onPostDialWait");
246            if (mOriginalConnection != null) {
247                setPostDialWait(mOriginalConnection.getRemainingPostDialString());
248            }
249        }
250
251        @Override
252        public void onPostDialChar(char c) {
253            Log.v(TelephonyConnection.this, "onPostDialChar: %s", c);
254            if (mOriginalConnection != null) {
255                setNextPostDialChar(c);
256            }
257        }
258    };
259
260    /**
261     * Listener for listening to events in the {@link com.android.internal.telephony.Connection}.
262     */
263    private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener =
264            new com.android.internal.telephony.Connection.ListenerBase() {
265        @Override
266        public void onVideoStateChanged(int videoState) {
267            mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget();
268        }
269
270        /*
271         * The {@link com.android.internal.telephony.Connection} has reported a change in
272         * connection capability.
273         * @param capabilities bit mask containing voice or video or both capabilities.
274         */
275        @Override
276        public void onConnectionCapabilitiesChanged(int capabilities) {
277            mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES,
278                    capabilities, 0).sendToTarget();
279        }
280
281        /**
282         * The {@link com.android.internal.telephony.Connection} has reported a change in the
283         * video call provider.
284         *
285         * @param videoProvider The video call provider.
286         */
287        @Override
288        public void onVideoProviderChanged(VideoProvider videoProvider) {
289            mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, videoProvider).sendToTarget();
290        }
291
292        /**
293         * Used by {@link com.android.internal.telephony.Connection} to report a change in whether
294         * the call is being made over a wifi network.
295         *
296         * @param isWifi True if call is made over wifi.
297         */
298        @Override
299        public void onWifiChanged(boolean isWifi) {
300            setWifi(isWifi);
301        }
302
303        /**
304         * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
305         * audio quality for the current call.
306         *
307         * @param audioQuality The audio quality.
308         */
309        @Override
310        public void onAudioQualityChanged(int audioQuality) {
311            mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget();
312        }
313        /**
314         * Handles a change in the state of conference participant(s), as reported by the
315         * {@link com.android.internal.telephony.Connection}.
316         *
317         * @param participants The participant(s) which changed.
318         */
319        @Override
320        public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
321            mHandler.obtainMessage(MSG_SET_CONFERENCE_PARTICIPANTS, participants).sendToTarget();
322        }
323
324        /*
325         * Handles a change to the multiparty state for this connection.
326         *
327         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
328         *      otherwise.
329         */
330        @Override
331        public void onMultipartyStateChanged(boolean isMultiParty) {
332            handleMultipartyStateChange(isMultiParty);
333        }
334
335        /**
336         * Handles the event that the request to merge calls failed.
337         */
338        @Override
339        public void onConferenceMergedFailed() {
340            handleConferenceMergeFailed();
341        }
342
343        @Override
344        public void onExtrasChanged(Bundle extras) {
345            mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, extras).sendToTarget();
346        }
347
348        /**
349         * Handles the phone exiting ECM mode by updating the connection capabilities.  During an
350         * ongoing call, if ECM mode is exited, we will re-enable mute for CDMA calls.
351         */
352        @Override
353        public void onExitedEcmMode() {
354            handleExitedEcmMode();
355        }
356    };
357
358    protected com.android.internal.telephony.Connection mOriginalConnection;
359    private Call.State mConnectionState = Call.State.IDLE;
360    private Bundle mOriginalConnectionExtras = new Bundle();
361    private boolean mIsStateOverridden = false;
362    private Call.State mOriginalConnectionState = Call.State.IDLE;
363    private Call.State mConnectionOverriddenState = Call.State.IDLE;
364
365    private boolean mWasImsConnection;
366
367    /**
368     * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected.
369     */
370    private boolean mIsMultiParty = false;
371
372    /**
373     * The {@link com.android.internal.telephony.Connection} capabilities associated with the
374     * current {@link #mOriginalConnection}.
375     */
376    private int mOriginalConnectionCapabilities;
377
378    /**
379     * Determines if the {@link TelephonyConnection} is using wifi.
380     * This is used when {@link TelephonyConnection#updateConnectionProperties()} is called to
381     * indicate whether a call has the {@link Connection#PROPERTY_WIFI} property.
382     */
383    private boolean mIsWifi;
384
385    /**
386     * Determines the audio quality is high for the {@link TelephonyConnection}.
387     * This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to
388     * indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
389     */
390    private boolean mHasHighDefAudio;
391
392    /**
393     * Indicates that the connection should be treated as an emergency call because the
394     * number dialed matches an internal list of emergency numbers. Does not guarantee whether
395     * the network will treat the call as an emergency call.
396     */
397    private boolean mTreatAsEmergencyCall;
398
399    /**
400     * For video calls, indicates whether the outgoing video for the call can be paused using
401     * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
402     */
403    private boolean mIsVideoPauseSupported;
404
405    /**
406     * Indicates whether this connection supports being a part of a conference..
407     */
408    private boolean mIsConferenceSupported;
409
410    /**
411     * Indicates whether or not this connection has CDMA Enhanced Voice Privacy enabled.
412     */
413    private boolean mIsCdmaVoicePrivacyEnabled;
414
415    /**
416     * Listeners to our TelephonyConnection specific callbacks
417     */
418    private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
419            new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
420
421    protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
422            String callId) {
423        setTelecomCallId(callId);
424        if (originalConnection != null) {
425            setOriginalConnection(originalConnection);
426        }
427    }
428
429    /**
430     * Creates a clone of the current {@link TelephonyConnection}.
431     *
432     * @return The clone.
433     */
434    public abstract TelephonyConnection cloneConnection();
435
436    @Override
437    public void onCallAudioStateChanged(CallAudioState audioState) {
438        // TODO: update TTY mode.
439        if (getPhone() != null) {
440            getPhone().setEchoSuppressionEnabled();
441        }
442    }
443
444    @Override
445    public void onStateChanged(int state) {
446        Log.v(this, "onStateChanged, state: " + Connection.stateToString(state));
447        updateStatusHints();
448    }
449
450    @Override
451    public void onDisconnect() {
452        Log.v(this, "onDisconnect");
453        hangup(android.telephony.DisconnectCause.LOCAL);
454    }
455
456    /**
457     * Notifies this Connection of a request to disconnect a participant of the conference managed
458     * by the connection.
459     *
460     * @param endpoint the {@link Uri} of the participant to disconnect.
461     */
462    @Override
463    public void onDisconnectConferenceParticipant(Uri endpoint) {
464        Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
465
466        if (mOriginalConnection == null) {
467            return;
468        }
469
470        mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
471    }
472
473    @Override
474    public void onSeparate() {
475        Log.v(this, "onSeparate");
476        if (mOriginalConnection != null) {
477            try {
478                mOriginalConnection.separate();
479            } catch (CallStateException e) {
480                Log.e(this, e, "Call to Connection.separate failed with exception");
481            }
482        }
483    }
484
485    @Override
486    public void onAbort() {
487        Log.v(this, "onAbort");
488        hangup(android.telephony.DisconnectCause.LOCAL);
489    }
490
491    @Override
492    public void onHold() {
493        performHold();
494    }
495
496    @Override
497    public void onUnhold() {
498        performUnhold();
499    }
500
501    @Override
502    public void onAnswer(int videoState) {
503        Log.v(this, "onAnswer");
504        if (isValidRingingCall() && getPhone() != null) {
505            try {
506                getPhone().acceptCall(videoState);
507            } catch (CallStateException e) {
508                Log.e(this, e, "Failed to accept call.");
509            }
510        }
511    }
512
513    @Override
514    public void onReject() {
515        Log.v(this, "onReject");
516        if (isValidRingingCall()) {
517            hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
518        }
519        super.onReject();
520    }
521
522    @Override
523    public void onPostDialContinue(boolean proceed) {
524        Log.v(this, "onPostDialContinue, proceed: " + proceed);
525        if (mOriginalConnection != null) {
526            if (proceed) {
527                mOriginalConnection.proceedAfterWaitChar();
528            } else {
529                mOriginalConnection.cancelPostDial();
530            }
531        }
532    }
533
534    /**
535     * Handles requests to pull an external call.
536     */
537    @Override
538    public void onPullExternalCall() {
539        if ((getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) !=
540                Connection.PROPERTY_IS_EXTERNAL_CALL) {
541            Log.w(this, "onPullExternalCall - cannot pull non-external call");
542            return;
543        }
544
545        if (mOriginalConnection != null) {
546            mOriginalConnection.pullExternalCall();
547        }
548    }
549
550    public void performHold() {
551        Log.v(this, "performHold");
552        // TODO: Can dialing calls be put on hold as well since they take up the
553        // foreground call slot?
554        if (Call.State.ACTIVE == mConnectionState) {
555            Log.v(this, "Holding active call");
556            try {
557                Phone phone = mOriginalConnection.getCall().getPhone();
558                Call ringingCall = phone.getRingingCall();
559
560                // Although the method says switchHoldingAndActive, it eventually calls a RIL method
561                // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
562                // a call on hold while a call-waiting call exists, it'll end up accepting the
563                // call-waiting call, which is bad if that was not the user's intention. We are
564                // cheating here and simply skipping it because we know any attempt to hold a call
565                // while a call-waiting call is happening is likely a request from Telecom prior to
566                // accepting the call-waiting call.
567                // TODO: Investigate a better solution. It would be great here if we
568                // could "fake" hold by silencing the audio and microphone streams for this call
569                // instead of actually putting it on hold.
570                if (ringingCall.getState() != Call.State.WAITING) {
571                    phone.switchHoldingAndActive();
572                }
573
574                // TODO: Cdma calls are slightly different.
575            } catch (CallStateException e) {
576                Log.e(this, e, "Exception occurred while trying to put call on hold.");
577            }
578        } else {
579            Log.w(this, "Cannot put a call that is not currently active on hold.");
580        }
581    }
582
583    public void performUnhold() {
584        Log.v(this, "performUnhold");
585        if (Call.State.HOLDING == mConnectionState) {
586            try {
587                // Here's the deal--Telephony hold/unhold is weird because whenever there exists
588                // more than one call, one of them must always be active. In other words, if you
589                // have an active call and holding call, and you put the active call on hold, it
590                // will automatically activate the holding call. This is weird with how Telecom
591                // sends its commands. When a user opts to "unhold" a background call, telecom
592                // issues hold commands to all active calls, and then the unhold command to the
593                // background call. This means that we get two commands...each of which reduces to
594                // switchHoldingAndActive(). The result is that they simply cancel each other out.
595                // To fix this so that it works well with telecom we add a minor hack. If we
596                // have one telephony call, everything works as normally expected. But if we have
597                // two or more calls, we will ignore all requests to "unhold" knowing that the hold
598                // requests already do what we want. If you've read up to this point, I'm very sorry
599                // that we are doing this. I didn't think of a better solution that wouldn't also
600                // make the Telecom APIs very ugly.
601
602                if (!hasMultipleTopLevelCalls()) {
603                    mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
604                } else {
605                    Log.i(this, "Skipping unhold command for %s", this);
606                }
607            } catch (CallStateException e) {
608                Log.e(this, e, "Exception occurred while trying to release call from hold.");
609            }
610        } else {
611            Log.w(this, "Cannot release a call that is not already on hold from hold.");
612        }
613    }
614
615    public void performConference(TelephonyConnection otherConnection) {
616        Log.d(this, "performConference - %s", this);
617        if (getPhone() != null) {
618            try {
619                // We dont use the "other" connection because there is no concept of that in the
620                // implementation of calls inside telephony. Basically, you can "conference" and it
621                // will conference with the background call.  We know that otherConnection is the
622                // background call because it would never have called setConferenceableConnections()
623                // otherwise.
624                getPhone().conference();
625            } catch (CallStateException e) {
626                Log.e(this, e, "Failed to conference call.");
627            }
628        }
629    }
630
631    /**
632     * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based
633     * capabilities.
634     */
635    protected int buildConnectionCapabilities() {
636        int callCapabilities = 0;
637        if (mOriginalConnection != null && mOriginalConnection.isIncoming()) {
638            callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO;
639        }
640        if (isImsConnection()) {
641            if (!shouldTreatAsEmergencyCall()) {
642                callCapabilities |= CAPABILITY_SUPPORT_HOLD;
643                if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
644                    callCapabilities |= CAPABILITY_HOLD;
645                }
646            }
647        }
648
649        return callCapabilities;
650    }
651
652    protected final void updateConnectionCapabilities() {
653        int newCapabilities = buildConnectionCapabilities();
654
655        newCapabilities = applyOriginalConnectionCapabilities(newCapabilities);
656        newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO,
657                mIsVideoPauseSupported && isVideoCapable());
658        newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL,
659                isExternalConnection() && isPullable());
660        newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
661
662        if (getConnectionCapabilities() != newCapabilities) {
663            setConnectionCapabilities(newCapabilities);
664        }
665    }
666
667    protected int buildConnectionProperties() {
668        int connectionProperties = 0;
669
670        // If the phone is in ECM mode, mark the call to indicate that the callback number should be
671        // shown.
672        Phone phone = getPhone();
673        if (phone != null && phone.isInEcm()) {
674            connectionProperties |= PROPERTY_SHOW_CALLBACK_NUMBER;
675        }
676
677        return connectionProperties;
678    }
679
680    /**
681     * Updates the properties of the connection.
682     */
683    protected final void updateConnectionProperties() {
684        int newProperties = buildConnectionProperties();
685
686        newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO, mHasHighDefAudio);
687        newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi);
688        newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL,
689                isExternalConnection());
690        newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY,
691                mIsCdmaVoicePrivacyEnabled);
692
693        if (getConnectionProperties() != newProperties) {
694            setConnectionProperties(newProperties);
695        }
696    }
697
698    protected final void updateAddress() {
699        updateConnectionCapabilities();
700        updateConnectionProperties();
701        if (mOriginalConnection != null) {
702            Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
703            int presentation = mOriginalConnection.getNumberPresentation();
704            if (!Objects.equals(address, getAddress()) ||
705                    presentation != getAddressPresentation()) {
706                Log.v(this, "updateAddress, address changed");
707                setAddress(address, presentation);
708            }
709
710            String name = mOriginalConnection.getCnapName();
711            int namePresentation = mOriginalConnection.getCnapNamePresentation();
712            if (!Objects.equals(name, getCallerDisplayName()) ||
713                    namePresentation != getCallerDisplayNamePresentation()) {
714                Log.v(this, "updateAddress, caller display name changed");
715                setCallerDisplayName(name, namePresentation);
716            }
717
718            if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
719                mTreatAsEmergencyCall = true;
720            }
721
722            // Changing the address of the connection can change whether it is an emergency call or
723            // not, which can impact whether it can be part of a conference.
724            refreshConferenceSupported();
725        }
726    }
727
728    void onRemovedFromCallService() {
729        // Subclass can override this to do cleanup.
730    }
731
732    void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
733        Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
734        clearOriginalConnection();
735        mOriginalConnectionExtras.clear();
736        mOriginalConnection = originalConnection;
737        mOriginalConnection.setTelecomCallId(getTelecomCallId());
738        getPhone().registerForPreciseCallStateChanged(
739                mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
740        getPhone().registerForHandoverStateChanged(
741                mHandler, MSG_HANDOVER_STATE_CHANGED, null);
742        getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
743        getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
744        getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
745        getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
746        getPhone().registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null);
747        getPhone().registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null);
748        mOriginalConnection.addPostDialListener(mPostDialListener);
749        mOriginalConnection.addListener(mOriginalConnectionListener);
750
751        // Set video state and capabilities
752        setVideoState(mOriginalConnection.getVideoState());
753        setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities());
754        setWifi(mOriginalConnection.isWifi());
755        setVideoProvider(mOriginalConnection.getVideoProvider());
756        setAudioQuality(mOriginalConnection.getAudioQuality());
757        setTechnologyTypeExtra();
758
759        // Post update of extras to the handler; extras are updated via the handler to ensure thread
760        // safety. The Extras Bundle is cloned in case the original extras are modified while they
761        // are being added to mOriginalConnectionExtras in updateExtras.
762        Bundle connExtras = mOriginalConnection.getConnectionExtras();
763            mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null :
764                    new Bundle(connExtras)).sendToTarget();
765
766        if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
767            mTreatAsEmergencyCall = true;
768        }
769
770        if (isImsConnection()) {
771            mWasImsConnection = true;
772        }
773        mIsMultiParty = mOriginalConnection.isMultiparty();
774
775        Bundle extrasToPut = new Bundle();
776        List<String> extrasToRemove = new ArrayList<>();
777        if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) {
778            extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
779        } else {
780            extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL);
781        }
782
783        if (!mOriginalConnection.shouldAllowAddCallDuringVideoCall()) {
784            extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL, true);
785        } else {
786            extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL);
787        }
788        putExtras(extrasToPut);
789        removeExtras(extrasToRemove);
790
791        // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
792        // should be executed *after* the above setters have run.
793        updateState();
794        if (mOriginalConnection == null) {
795            Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " +
796                    originalConnection);
797        }
798
799        fireOnOriginalConnectionConfigured();
800    }
801
802    /**
803     * Sets the EXTRA_CALL_TECHNOLOGY_TYPE extra on the connection to report back to Telecom.
804     */
805    private void setTechnologyTypeExtra() {
806        if (getPhone() != null) {
807            putExtra(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType());
808        }
809    }
810
811    /**
812     * Whether the connection should be treated as an emergency.
813     * @return {@code true} if the connection should be treated as an emergency call based
814     * on the number dialed, {@code false} otherwise.
815     */
816    protected boolean shouldTreatAsEmergencyCall() {
817        return mTreatAsEmergencyCall;
818    }
819
820    /**
821     * Un-sets the underlying radio connection.
822     */
823    void clearOriginalConnection() {
824        if (mOriginalConnection != null) {
825            if (getPhone() != null) {
826                getPhone().unregisterForPreciseCallStateChanged(mHandler);
827                getPhone().unregisterForRingbackTone(mHandler);
828                getPhone().unregisterForHandoverStateChanged(mHandler);
829                getPhone().unregisterForDisconnect(mHandler);
830                getPhone().unregisterForSuppServiceNotification(mHandler);
831                getPhone().unregisterForOnHoldTone(mHandler);
832                getPhone().unregisterForInCallVoicePrivacyOn(mHandler);
833                getPhone().unregisterForInCallVoicePrivacyOff(mHandler);
834            }
835            mOriginalConnection.removePostDialListener(mPostDialListener);
836            mOriginalConnection.removeListener(mOriginalConnectionListener);
837            mOriginalConnection = null;
838        }
839    }
840
841    protected void hangup(int telephonyDisconnectCode) {
842        if (mOriginalConnection != null) {
843            try {
844                // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
845                // connection.hangup(). Without this change, the party originating the call will not
846                // get sent to voicemail if the user opts to reject the call.
847                if (isValidRingingCall()) {
848                    Call call = getCall();
849                    if (call != null) {
850                        call.hangup();
851                    } else {
852                        Log.w(this, "Attempting to hangup a connection without backing call.");
853                    }
854                } else {
855                    // We still prefer to call connection.hangup() for non-ringing calls in order
856                    // to support hanging-up specific calls within a conference call. If we invoked
857                    // call.hangup() while in a conference, we would end up hanging up the entire
858                    // conference call instead of the specific connection.
859                    mOriginalConnection.hangup();
860                }
861            } catch (CallStateException e) {
862                Log.e(this, e, "Call to Connection.hangup failed with exception");
863            }
864        }
865    }
866
867    com.android.internal.telephony.Connection getOriginalConnection() {
868        return mOriginalConnection;
869    }
870
871    protected Call getCall() {
872        if (mOriginalConnection != null) {
873            return mOriginalConnection.getCall();
874        }
875        return null;
876    }
877
878    Phone getPhone() {
879        Call call = getCall();
880        if (call != null) {
881            return call.getPhone();
882        }
883        return null;
884    }
885
886    private boolean hasMultipleTopLevelCalls() {
887        int numCalls = 0;
888        Phone phone = getPhone();
889        if (phone != null) {
890            if (!phone.getRingingCall().isIdle()) {
891                numCalls++;
892            }
893            if (!phone.getForegroundCall().isIdle()) {
894                numCalls++;
895            }
896            if (!phone.getBackgroundCall().isIdle()) {
897                numCalls++;
898            }
899        }
900        return numCalls > 1;
901    }
902
903    private com.android.internal.telephony.Connection getForegroundConnection() {
904        if (getPhone() != null) {
905            return getPhone().getForegroundCall().getEarliestConnection();
906        }
907        return null;
908    }
909
910     /**
911     * Checks for and returns the list of conference participants
912     * associated with this connection.
913     */
914    public List<ConferenceParticipant> getConferenceParticipants() {
915        if (mOriginalConnection == null) {
916            Log.v(this, "Null mOriginalConnection, cannot get conf participants.");
917            return null;
918        }
919        return mOriginalConnection.getConferenceParticipants();
920    }
921
922    /**
923     * Checks to see the original connection corresponds to an active incoming call. Returns false
924     * if there is no such actual call, or if the associated call is not incoming (See
925     * {@link Call.State#isRinging}).
926     */
927    private boolean isValidRingingCall() {
928        if (getPhone() == null) {
929            Log.v(this, "isValidRingingCall, phone is null");
930            return false;
931        }
932
933        Call ringingCall = getPhone().getRingingCall();
934        if (!ringingCall.getState().isRinging()) {
935            Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
936            return false;
937        }
938
939        if (ringingCall.getEarliestConnection() != mOriginalConnection) {
940            Log.v(this, "isValidRingingCall, ringing call connection does not match");
941            return false;
942        }
943
944        Log.v(this, "isValidRingingCall, returning true");
945        return true;
946    }
947
948    // Make sure the extras being passed into this method is a COPY of the original extras Bundle.
949    // We do not want the extras to be cleared or modified during mOriginalConnectionExtras.putAll
950    // below.
951    protected void updateExtras(Bundle extras) {
952        if (mOriginalConnection != null) {
953            if (extras != null) {
954                // Check if extras have changed and need updating.
955                if (!areBundlesEqual(mOriginalConnectionExtras, extras)) {
956                    if (Log.DEBUG) {
957                        Log.d(TelephonyConnection.this, "Updating extras:");
958                        for (String key : extras.keySet()) {
959                            Object value = extras.get(key);
960                            if (value instanceof String) {
961                                Log.d(this, "updateExtras Key=" + Log.pii(key) +
962                                             " value=" + Log.pii((String)value));
963                            }
964                        }
965                    }
966                    mOriginalConnectionExtras.clear();
967
968                    mOriginalConnectionExtras.putAll(extras);
969
970                    // Remap any string extras that have a remapping defined.
971                    for (String key : mOriginalConnectionExtras.keySet()) {
972                        if (sExtrasMap.containsKey(key)) {
973                            String newKey = sExtrasMap.get(key);
974                            mOriginalConnectionExtras.putString(newKey, extras.getString(key));
975                            mOriginalConnectionExtras.remove(key);
976                        }
977                    }
978
979                    // Ensure extras are propagated to Telecom.
980                    putExtras(mOriginalConnectionExtras);
981                } else {
982                    Log.d(this, "Extras update not required");
983                }
984            } else {
985                Log.d(this, "updateExtras extras: " + Log.pii(extras));
986            }
987        }
988    }
989
990    private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
991        if (extras == null || newExtras == null) {
992            return extras == newExtras;
993        }
994
995        if (extras.size() != newExtras.size()) {
996            return false;
997        }
998
999        for(String key : extras.keySet()) {
1000            if (key != null) {
1001                final Object value = extras.get(key);
1002                final Object newValue = newExtras.get(key);
1003                if (!Objects.equals(value, newValue)) {
1004                    return false;
1005                }
1006            }
1007        }
1008        return true;
1009    }
1010
1011    void setStateOverride(Call.State state) {
1012        mIsStateOverridden = true;
1013        mConnectionOverriddenState = state;
1014        // Need to keep track of the original connection's state before override.
1015        mOriginalConnectionState = mOriginalConnection.getState();
1016        updateStateInternal();
1017    }
1018
1019    void resetStateOverride() {
1020        mIsStateOverridden = false;
1021        updateStateInternal();
1022    }
1023
1024    void updateStateInternal() {
1025        if (mOriginalConnection == null) {
1026            return;
1027        }
1028        Call.State newState;
1029        // If the state is overridden and the state of the original connection hasn't changed since,
1030        // then we continue in the overridden state, else we go to the original connection's state.
1031        if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) {
1032            newState = mConnectionOverriddenState;
1033        } else {
1034            newState = mOriginalConnection.getState();
1035        }
1036        Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this);
1037
1038        if (mConnectionState != newState) {
1039            mConnectionState = newState;
1040            switch (newState) {
1041                case IDLE:
1042                    break;
1043                case ACTIVE:
1044                    setActiveInternal();
1045                    break;
1046                case HOLDING:
1047                    setOnHold();
1048                    break;
1049                case DIALING:
1050                case ALERTING:
1051                    setDialing();
1052                    break;
1053                case INCOMING:
1054                case WAITING:
1055                    setRinging();
1056                    break;
1057                case DISCONNECTED:
1058                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
1059                            mOriginalConnection.getDisconnectCause(),
1060                            mOriginalConnection.getVendorDisconnectCause()));
1061                    close();
1062                    break;
1063                case DISCONNECTING:
1064                    break;
1065            }
1066        }
1067    }
1068
1069    void updateState() {
1070        if (mOriginalConnection == null) {
1071            return;
1072        }
1073
1074        updateStateInternal();
1075        updateStatusHints();
1076        updateConnectionCapabilities();
1077        updateConnectionProperties();
1078        updateAddress();
1079        updateMultiparty();
1080    }
1081
1082    /**
1083     * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
1084     */
1085    private void updateMultiparty() {
1086        if (mOriginalConnection == null) {
1087            return;
1088        }
1089
1090        if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
1091            mIsMultiParty = mOriginalConnection.isMultiparty();
1092
1093            if (mIsMultiParty) {
1094                notifyConferenceStarted();
1095            }
1096        }
1097    }
1098
1099    /**
1100     * Handles a failure when merging calls into a conference.
1101     * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()}
1102     * listener.
1103     */
1104    private void handleConferenceMergeFailed(){
1105        mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget();
1106    }
1107
1108    /**
1109     * Handles requests to update the multiparty state received via the
1110     * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)}
1111     * listener.
1112     * <p>
1113     * Note: We post this to the mHandler to ensure that if a conference must be created as a
1114     * result of the multiparty state change, the conference creation happens on the correct
1115     * thread.  This ensures that the thread check in
1116     * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)}
1117     * does not fire.
1118     *
1119     * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise.
1120     */
1121    private void handleMultipartyStateChange(boolean isMultiParty) {
1122        Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
1123        mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget();
1124    }
1125
1126    private void setActiveInternal() {
1127        if (getState() == STATE_ACTIVE) {
1128            Log.w(this, "Should not be called if this is already ACTIVE");
1129            return;
1130        }
1131
1132        // When we set a call to active, we need to make sure that there are no other active
1133        // calls. However, the ordering of state updates to connections can be non-deterministic
1134        // since all connections register for state changes on the phone independently.
1135        // To "optimize", we check here to see if there already exists any active calls.  If so,
1136        // we issue an update for those calls first to make sure we only have one top-level
1137        // active call.
1138        if (getConnectionService() != null) {
1139            for (Connection current : getConnectionService().getAllConnections()) {
1140                if (current != this && current instanceof TelephonyConnection) {
1141                    TelephonyConnection other = (TelephonyConnection) current;
1142                    if (other.getState() == STATE_ACTIVE) {
1143                        other.updateState();
1144                    }
1145                }
1146            }
1147        }
1148        setActive();
1149    }
1150
1151    private void close() {
1152        Log.v(this, "close");
1153        clearOriginalConnection();
1154        destroy();
1155    }
1156
1157    /**
1158     * Determines if the current connection is video capable.
1159     *
1160     * A connection is deemed to be video capable if the original connection capabilities state that
1161     * both local and remote video is supported.
1162     *
1163     * @return {@code true} if the connection is video capable, {@code false} otherwise.
1164     */
1165    private boolean isVideoCapable() {
1166        return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
1167                && can(mOriginalConnectionCapabilities,
1168                Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
1169    }
1170
1171    /**
1172     * Determines if the current connection is an external connection.
1173     *
1174     * A connection is deemed to be external if the original connection capabilities state that it
1175     * is.
1176     *
1177     * @return {@code true} if the connection is external, {@code false} otherwise.
1178     */
1179    private boolean isExternalConnection() {
1180        return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
1181                && can(mOriginalConnectionCapabilities,
1182                Capability.IS_EXTERNAL_CONNECTION);
1183    }
1184
1185    /**
1186     * Determines if the current connection is pullable.
1187     *
1188     * A connection is deemed to be pullable if the original connection capabilities state that it
1189     * is.
1190     *
1191     * @return {@code true} if the connection is pullable, {@code false} otherwise.
1192     */
1193    private boolean isPullable() {
1194        return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
1195                && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE);
1196    }
1197
1198    /**
1199     * Sets whether or not CDMA enhanced call privacy is enabled for this connection.
1200     */
1201    private void setCdmaVoicePrivacy(boolean isEnabled) {
1202        if(mIsCdmaVoicePrivacyEnabled != isEnabled) {
1203            mIsCdmaVoicePrivacyEnabled = isEnabled;
1204            updateConnectionProperties();
1205        }
1206    }
1207
1208    /**
1209     * Applies capabilities specific to conferences termination to the
1210     * {@code ConnectionCapabilities} bit-mask.
1211     *
1212     * @param capabilities The {@code ConnectionCapabilities} bit-mask.
1213     * @return The capabilities with the IMS conference capabilities applied.
1214     */
1215    private int applyConferenceTerminationCapabilities(int capabilities) {
1216        int currentCapabilities = capabilities;
1217
1218        // An IMS call cannot be individually disconnected or separated from its parent conference.
1219        // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
1220        if (!mWasImsConnection) {
1221            currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
1222            currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
1223        }
1224
1225        return currentCapabilities;
1226    }
1227
1228    /**
1229     * Stores the new original connection capabilities, and applies them to the current connection,
1230     * notifying any listeners as necessary.
1231     *
1232     * @param connectionCapabilities The original connection capabilties.
1233     */
1234    public void setOriginalConnectionCapabilities(int connectionCapabilities) {
1235        mOriginalConnectionCapabilities = connectionCapabilities;
1236        updateConnectionCapabilities();
1237        updateConnectionProperties();
1238    }
1239
1240    /**
1241     * Called to apply the capabilities present in the {@link #mOriginalConnection} to this
1242     * {@link Connection}.  Provides a mapping between the capabilities present in the original
1243     * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in
1244     * this {@link Connection}.
1245     *
1246     * @param capabilities The capabilities bitmask from the {@link Connection}.
1247     * @return the capabilities bitmask with the original connection capabilities remapped and
1248     *      applied.
1249     */
1250    public int applyOriginalConnectionCapabilities(int capabilities) {
1251        // We only support downgrading to audio if both the remote and local side support
1252        // downgrading to audio.
1253        boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities,
1254                Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
1255                        Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE);
1256        capabilities = changeBitmask(capabilities,
1257                CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio);
1258
1259        capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
1260                can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL));
1261
1262        capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
1263                can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
1264
1265        return capabilities;
1266    }
1267
1268    /**
1269     * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset
1270     * the {@link Connection#PROPERTY_WIFI} property.
1271     */
1272    public void setWifi(boolean isWifi) {
1273        mIsWifi = isWifi;
1274        updateConnectionProperties();
1275        updateStatusHints();
1276    }
1277
1278    /**
1279     * Whether the call is using wifi.
1280     */
1281    boolean isWifi() {
1282        return mIsWifi;
1283    }
1284
1285    /**
1286     * Sets the current call audio quality. Used during rebuild of the properties
1287     * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
1288     *
1289     * @param audioQuality The audio quality.
1290     */
1291    public void setAudioQuality(int audioQuality) {
1292        mHasHighDefAudio = audioQuality ==
1293                com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION;
1294        updateConnectionProperties();
1295    }
1296
1297    void resetStateForConference() {
1298        if (getState() == Connection.STATE_HOLDING) {
1299            resetStateOverride();
1300        }
1301    }
1302
1303    boolean setHoldingForConference() {
1304        if (getState() == Connection.STATE_ACTIVE) {
1305            setStateOverride(Call.State.HOLDING);
1306            return true;
1307        }
1308        return false;
1309    }
1310
1311    /**
1312     * For video calls, sets whether this connection supports pausing the outgoing video for the
1313     * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
1314     *
1315     * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise.
1316     */
1317    public void setVideoPauseSupported(boolean isVideoPauseSupported) {
1318        mIsVideoPauseSupported = isVideoPauseSupported;
1319    }
1320
1321    /**
1322     * Sets whether this connection supports conference calling.
1323     * @param isConferenceSupported {@code true} if conference calling is supported by this
1324     *                                         connection, {@code false} otherwise.
1325     */
1326    public void setConferenceSupported(boolean isConferenceSupported) {
1327        mIsConferenceSupported = isConferenceSupported;
1328    }
1329
1330    /**
1331     * @return {@code true} if this connection supports merging calls into a conference.
1332     */
1333    public boolean isConferenceSupported() {
1334        return mIsConferenceSupported;
1335    }
1336
1337    /**
1338     * Whether the original connection is an IMS connection.
1339     * @return {@code True} if the original connection is an IMS connection, {@code false}
1340     *     otherwise.
1341     */
1342    protected boolean isImsConnection() {
1343        com.android.internal.telephony.Connection originalConnection = getOriginalConnection();
1344        return originalConnection != null &&
1345                originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
1346    }
1347
1348    /**
1349     * Whether the original connection was ever an IMS connection, either before or now.
1350     * @return {@code True} if the original connection was ever an IMS connection, {@code false}
1351     *     otherwise.
1352     */
1353    public boolean wasImsConnection() {
1354        return mWasImsConnection;
1355    }
1356
1357    private static Uri getAddressFromNumber(String number) {
1358        // Address can be null for blocked calls.
1359        if (number == null) {
1360            number = "";
1361        }
1362        return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
1363    }
1364
1365    /**
1366     * Changes a capabilities bit-mask to add or remove a capability.
1367     *
1368     * @param bitmask The bit-mask.
1369     * @param bitfield The bit-field to change.
1370     * @param enabled Whether the bit-field should be set or removed.
1371     * @return The bit-mask with the bit-field changed.
1372     */
1373    private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
1374        if (enabled) {
1375            return bitmask | bitfield;
1376        } else {
1377            return bitmask & ~bitfield;
1378        }
1379    }
1380
1381    private void updateStatusHints() {
1382        boolean isIncoming = isValidRingingCall();
1383        if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) {
1384            int labelId = isIncoming
1385                    ? R.string.status_hint_label_incoming_wifi_call
1386                    : R.string.status_hint_label_wifi_call;
1387
1388            Context context = getPhone().getContext();
1389            setStatusHints(new StatusHints(
1390                    context.getString(labelId),
1391                    Icon.createWithResource(
1392                            context.getResources(),
1393                            R.drawable.ic_signal_wifi_4_bar_24dp),
1394                    null /* extras */));
1395        } else {
1396            setStatusHints(null);
1397        }
1398    }
1399
1400    /**
1401     * Register a listener for {@link TelephonyConnection} specific triggers.
1402     * @param l The instance of the listener to add
1403     * @return The connection being listened to
1404     */
1405    public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
1406        mTelephonyListeners.add(l);
1407        // If we already have an original connection, let's call back immediately.
1408        // This would be the case for incoming calls.
1409        if (mOriginalConnection != null) {
1410            fireOnOriginalConnectionConfigured();
1411        }
1412        return this;
1413    }
1414
1415    /**
1416     * Remove a listener for {@link TelephonyConnection} specific triggers.
1417     * @param l The instance of the listener to remove
1418     * @return The connection being listened to
1419     */
1420    public final TelephonyConnection removeTelephonyConnectionListener(
1421            TelephonyConnectionListener l) {
1422        if (l != null) {
1423            mTelephonyListeners.remove(l);
1424        }
1425        return this;
1426    }
1427
1428    /**
1429     * Fire a callback to the various listeners for when the original connection is
1430     * set in this {@link TelephonyConnection}
1431     */
1432    private final void fireOnOriginalConnectionConfigured() {
1433        for (TelephonyConnectionListener l : mTelephonyListeners) {
1434            l.onOriginalConnectionConfigured(this);
1435        }
1436    }
1437
1438    /**
1439     * Handles exiting ECM mode.
1440     */
1441    protected void handleExitedEcmMode() {
1442        updateConnectionProperties();
1443    }
1444
1445    /**
1446     * Determines whether the connection supports conference calling.  A connection supports
1447     * conference calling if it:
1448     * 1. Is not an emergency call.
1449     * 2. Carrier supports conference calls.
1450     * 3. If call is a video call, carrier supports video conference calls.
1451     * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls.
1452     */
1453    private void refreshConferenceSupported() {
1454        boolean isVideoCall = VideoProfile.isVideo(getVideoState());
1455        Phone phone = getPhone();
1456        boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
1457        boolean isVoWifiEnabled = false;
1458        if (isIms) {
1459            ImsPhone imsPhone = (ImsPhone) phone;
1460            isVoWifiEnabled = imsPhone.isWifiCallingEnabled();
1461        }
1462        PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
1463                .makePstnPhoneAccountHandle(phone.getDefaultPhone())
1464                : PhoneUtils.makePstnPhoneAccountHandle(phone);
1465        TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry
1466                .getInstance(getPhone().getContext());
1467        boolean isConferencingSupported = telecomAccountRegistry
1468                .isMergeCallSupported(phoneAccountHandle);
1469        boolean isVideoConferencingSupported = telecomAccountRegistry
1470                .isVideoConferencingSupported(phoneAccountHandle);
1471        boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry
1472                .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle);
1473
1474        Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isVidConfSupp=%b, " +
1475                "isMergeOfWifiAllowed=%b, isWifi=%b, isVoWifiEnabled=%b", isConferencingSupported,
1476                isVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff, isWifi(),
1477                isVoWifiEnabled);
1478        boolean isConferenceSupported = true;
1479        if (mTreatAsEmergencyCall) {
1480            isConferenceSupported = false;
1481            Log.d(this, "refreshConferenceSupported = false; emergency call");
1482        } else if (!isConferencingSupported) {
1483            isConferenceSupported = false;
1484            Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf.");
1485        } else if (isVideoCall && !isVideoConferencingSupported) {
1486            isConferenceSupported = false;
1487            Log.d(this, "refreshConferenceSupported = false; video conf not supported.");
1488        } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) {
1489            isConferenceSupported = false;
1490            Log.d(this,
1491                    "refreshConferenceSupported = false; can't merge wifi calls when voWifi off.");
1492        } else {
1493            Log.d(this, "refreshConferenceSupported = true.");
1494        }
1495
1496        if (isConferenceSupported != isConferenceSupported()) {
1497            setConferenceSupported(isConferenceSupported);
1498            notifyConferenceSupportedChanged(isConferenceSupported);
1499        }
1500    }
1501    /**
1502     * Provides a mapping from extras keys which may be found in the
1503     * {@link com.android.internal.telephony.Connection} to their equivalents defined in
1504     * {@link android.telecom.Connection}.
1505     *
1506     * @return Map containing key mappings.
1507     */
1508    private static Map<String, String> createExtrasMap() {
1509        Map<String, String> result = new HashMap<String, String>();
1510        result.put(ImsCallProfile.EXTRA_CHILD_NUMBER,
1511                android.telecom.Connection.EXTRA_CHILD_ADDRESS);
1512        result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT,
1513                android.telecom.Connection.EXTRA_CALL_SUBJECT);
1514        return Collections.unmodifiableMap(result);
1515    }
1516
1517    /**
1518     * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
1519     * use in log statements.
1520     *
1521     * @return String representation of the connection.
1522     */
1523    @Override
1524    public String toString() {
1525        StringBuilder sb = new StringBuilder();
1526        sb.append("[TelephonyConnection objId:");
1527        sb.append(System.identityHashCode(this));
1528        sb.append(" telecomCallID:");
1529        sb.append(getTelecomCallId());
1530        sb.append(" type:");
1531        if (isImsConnection()) {
1532            sb.append("ims");
1533        } else if (this instanceof com.android.services.telephony.GsmConnection) {
1534            sb.append("gsm");
1535        } else if (this instanceof CdmaConnection) {
1536            sb.append("cdma");
1537        }
1538        sb.append(" state:");
1539        sb.append(Connection.stateToString(getState()));
1540        sb.append(" capabilities:");
1541        sb.append(capabilitiesToString(getConnectionCapabilities()));
1542        sb.append(" properties:");
1543        sb.append(propertiesToString(getConnectionProperties()));
1544        sb.append(" address:");
1545        sb.append(Log.pii(getAddress()));
1546        sb.append(" originalConnection:");
1547        sb.append(mOriginalConnection);
1548        sb.append(" partOfConf:");
1549        if (getConference() == null) {
1550            sb.append("N");
1551        } else {
1552            sb.append("Y");
1553        }
1554        sb.append(" confSupported:");
1555        sb.append(mIsConferenceSupported ? "Y" : "N");
1556        sb.append("]");
1557        return sb.toString();
1558    }
1559}
1560