ImsPhoneConnection.java revision e70972cf6a3b48df1f7d036877eb28529e606ca9
1/*
2 * Copyright (C) 2013 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.internal.telephony.imsphone;
18
19import android.content.Context;
20import android.net.Uri;
21import android.os.AsyncResult;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.Message;
25import android.os.PowerManager;
26import android.os.Registrant;
27import android.os.SystemClock;
28import android.telecom.Log;
29import android.telephony.DisconnectCause;
30import android.telephony.PhoneNumberUtils;
31import android.telephony.Rlog;
32
33import com.android.ims.ImsException;
34import com.android.ims.ImsStreamMediaProfile;
35import com.android.internal.telephony.CallStateException;
36import com.android.internal.telephony.Connection;
37import com.android.internal.telephony.Phone;
38import com.android.internal.telephony.PhoneConstants;
39import com.android.internal.telephony.UUSInfo;
40
41import com.android.ims.ImsCall;
42import com.android.ims.ImsCallProfile;
43
44/**
45 * {@hide}
46 */
47public class ImsPhoneConnection extends Connection {
48    private static final String LOG_TAG = "ImsPhoneConnection";
49    private static final boolean DBG = true;
50
51    //***** Instance Variables
52
53    private ImsPhoneCallTracker mOwner;
54    private ImsPhoneCall mParent;
55    private ImsCall mImsCall;
56
57    private String mPostDialString;      // outgoing calls only
58    private boolean mDisconnected;
59
60    /*
61    int mIndex;          // index in ImsPhoneCallTracker.connections[], -1 if unassigned
62                        // The GSM index is 1 + this
63    */
64
65    /*
66     * These time/timespan values are based on System.currentTimeMillis(),
67     * i.e., "wall clock" time.
68     */
69    private long mDisconnectTime;
70
71    private int mNextPostDialChar;       // index into postDialString
72
73    private int mCause = DisconnectCause.NOT_DISCONNECTED;
74    private PostDialState mPostDialState = PostDialState.NOT_STARTED;
75    private UUSInfo mUusInfo;
76    private Handler mHandler;
77
78    private PowerManager.WakeLock mPartialWakeLock;
79
80    //***** Event Constants
81    private static final int EVENT_DTMF_DONE = 1;
82    private static final int EVENT_PAUSE_DONE = 2;
83    private static final int EVENT_NEXT_POST_DIAL = 3;
84    private static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
85
86    //***** Constants
87    private static final int PAUSE_DELAY_MILLIS = 3 * 1000;
88    private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
89
90    //***** Inner Classes
91
92    class MyHandler extends Handler {
93        MyHandler(Looper l) {super(l);}
94
95        @Override
96        public void
97        handleMessage(Message msg) {
98
99            switch (msg.what) {
100                case EVENT_NEXT_POST_DIAL:
101                case EVENT_DTMF_DONE:
102                case EVENT_PAUSE_DONE:
103                    processNextPostDialChar();
104                    break;
105                case EVENT_WAKE_LOCK_TIMEOUT:
106                    releaseWakeLock();
107                    break;
108            }
109        }
110    }
111
112    //***** Constructors
113
114    /** This is probably an MT call */
115    /*package*/
116    ImsPhoneConnection(Context context, ImsCall imsCall, ImsPhoneCallTracker ct, ImsPhoneCall parent) {
117        createWakeLock(context);
118        acquireWakeLock();
119
120        mOwner = ct;
121        mHandler = new MyHandler(mOwner.getLooper());
122        mImsCall = imsCall;
123
124        if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
125            mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI);
126            mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA);
127            mNumberPresentation = ImsCallProfile.OIRToPresentation(
128                    imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR));
129            mCnapNamePresentation = ImsCallProfile.OIRToPresentation(
130                    imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
131
132            updateMediaCapabilities(imsCall);
133        } else {
134            mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
135            mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN;
136        }
137
138        mIsIncoming = true;
139        mCreateTime = System.currentTimeMillis();
140        mUusInfo = null;
141
142        //mIndex = index;
143
144        mParent = parent;
145        mParent.attach(this, ImsPhoneCall.State.INCOMING);
146    }
147
148    /** This is an MO call, created when dialing */
149    /*package*/
150    ImsPhoneConnection(Context context, String dialString, ImsPhoneCallTracker ct, ImsPhoneCall parent) {
151        createWakeLock(context);
152        acquireWakeLock();
153
154        mOwner = ct;
155        mHandler = new MyHandler(mOwner.getLooper());
156
157        mDialString = dialString;
158
159        mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
160        mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
161
162        //mIndex = -1;
163
164        mIsIncoming = false;
165        mCnapName = null;
166        mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
167        mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
168        mCreateTime = System.currentTimeMillis();
169
170        mParent = parent;
171        parent.attachFake(this, ImsPhoneCall.State.DIALING);
172    }
173
174    public void dispose() {
175    }
176
177    static boolean
178    equalsHandlesNulls (Object a, Object b) {
179        return (a == null) ? (b == null) : a.equals (b);
180    }
181
182    @Override
183    public String getOrigDialString(){
184        return mDialString;
185    }
186
187    @Override
188    public ImsPhoneCall getCall() {
189        return mParent;
190    }
191
192    @Override
193    public long getDisconnectTime() {
194        return mDisconnectTime;
195    }
196
197    @Override
198    public long getHoldingStartTime() {
199        return mHoldingStartTime;
200    }
201
202    @Override
203    public long getHoldDurationMillis() {
204        if (getState() != ImsPhoneCall.State.HOLDING) {
205            // If not holding, return 0
206            return 0;
207        } else {
208            return SystemClock.elapsedRealtime() - mHoldingStartTime;
209        }
210    }
211
212    @Override
213    public int getDisconnectCause() {
214        return mCause;
215    }
216
217    public void setDisconnectCause(int cause) {
218        mCause = cause;
219    }
220
221    public ImsPhoneCallTracker getOwner () {
222        return mOwner;
223    }
224
225    @Override
226    public ImsPhoneCall.State getState() {
227        if (mDisconnected) {
228            return ImsPhoneCall.State.DISCONNECTED;
229        } else {
230            return super.getState();
231        }
232    }
233
234    @Override
235    public void hangup() throws CallStateException {
236        if (!mDisconnected) {
237            mOwner.hangup(this);
238        } else {
239            throw new CallStateException ("disconnected");
240        }
241    }
242
243    @Override
244    public void separate() throws CallStateException {
245        throw new CallStateException ("not supported");
246    }
247
248    @Override
249    public PostDialState getPostDialState() {
250        return mPostDialState;
251    }
252
253    @Override
254    public void proceedAfterWaitChar() {
255        if (mPostDialState != PostDialState.WAIT) {
256            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
257                    + "getPostDialState() to be WAIT but was " + mPostDialState);
258            return;
259        }
260
261        setPostDialState(PostDialState.STARTED);
262
263        processNextPostDialChar();
264    }
265
266    @Override
267    public void proceedAfterWildChar(String str) {
268        if (mPostDialState != PostDialState.WILD) {
269            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
270                    + "getPostDialState() to be WILD but was " + mPostDialState);
271            return;
272        }
273
274        setPostDialState(PostDialState.STARTED);
275
276        // make a new postDialString, with the wild char replacement string
277        // at the beginning, followed by the remaining postDialString.
278
279        StringBuilder buf = new StringBuilder(str);
280        buf.append(mPostDialString.substring(mNextPostDialChar));
281        mPostDialString = buf.toString();
282        mNextPostDialChar = 0;
283        if (Phone.DEBUG_PHONE) {
284            Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " +
285                    mPostDialString);
286        }
287
288        processNextPostDialChar();
289    }
290
291    @Override
292    public void cancelPostDial() {
293        setPostDialState(PostDialState.CANCELLED);
294    }
295
296    /**
297     * Called when this Connection is being hung up locally (eg, user pressed "end")
298     */
299    void
300    onHangupLocal() {
301        mCause = DisconnectCause.LOCAL;
302    }
303
304    /** Called when the connection has been disconnected */
305    public boolean
306    onDisconnect(int cause) {
307        Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
308        if (mCause != DisconnectCause.LOCAL) mCause = cause;
309        return onDisconnect();
310    }
311
312    /*package*/ boolean
313    onDisconnect() {
314        boolean changed = false;
315
316        if (!mDisconnected) {
317            //mIndex = -1;
318
319            mDisconnectTime = System.currentTimeMillis();
320            mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
321            mDisconnected = true;
322
323            mOwner.mPhone.notifyDisconnect(this);
324
325            if (mParent != null) {
326                changed = mParent.connectionDisconnected(this);
327            } else {
328                Rlog.d(LOG_TAG, "onDisconnect: no parent");
329            }
330            if (mImsCall != null) mImsCall.close();
331            mImsCall = null;
332        }
333        releaseWakeLock();
334        return changed;
335    }
336
337    /**
338     * An incoming or outgoing call has connected
339     */
340    void
341    onConnectedInOrOut() {
342        mConnectTime = System.currentTimeMillis();
343        mConnectTimeReal = SystemClock.elapsedRealtime();
344        mDuration = 0;
345
346        if (Phone.DEBUG_PHONE) {
347            Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime);
348        }
349
350        if (!mIsIncoming) {
351            // outgoing calls only
352            processNextPostDialChar();
353        }
354        releaseWakeLock();
355    }
356
357    /*package*/ void
358    onStartedHolding() {
359        mHoldingStartTime = SystemClock.elapsedRealtime();
360    }
361    /**
362     * Performs the appropriate action for a post-dial char, but does not
363     * notify application. returns false if the character is invalid and
364     * should be ignored
365     */
366    private boolean
367    processPostDialChar(char c) {
368        if (PhoneNumberUtils.is12Key(c)) {
369            mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE));
370        } else if (c == PhoneNumberUtils.PAUSE) {
371            // From TS 22.101:
372            // It continues...
373            // Upon the called party answering the UE shall send the DTMF digits
374            // automatically to the network after a delay of 3 seconds( 20 ).
375            // The digits shall be sent according to the procedures and timing
376            // specified in 3GPP TS 24.008 [13]. The first occurrence of the
377            // "DTMF Control Digits Separator" shall be used by the ME to
378            // distinguish between the addressing digits (i.e. the phone number)
379            // and the DTMF digits. Upon subsequent occurrences of the
380            // separator,
381            // the UE shall pause again for 3 seconds ( 20 ) before sending
382            // any further DTMF digits.
383            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
384                    PAUSE_DELAY_MILLIS);
385        } else if (c == PhoneNumberUtils.WAIT) {
386            setPostDialState(PostDialState.WAIT);
387        } else if (c == PhoneNumberUtils.WILD) {
388            setPostDialState(PostDialState.WILD);
389        } else {
390            return false;
391        }
392
393        return true;
394    }
395
396    @Override
397    public String
398    getRemainingPostDialString() {
399        if (mPostDialState == PostDialState.CANCELLED
400            || mPostDialState == PostDialState.COMPLETE
401            || mPostDialString == null
402            || mPostDialString.length() <= mNextPostDialChar
403        ) {
404            return "";
405        }
406
407        return mPostDialString.substring(mNextPostDialChar);
408    }
409
410    @Override
411    protected void finalize()
412    {
413        releaseWakeLock();
414    }
415
416    private void
417    processNextPostDialChar() {
418        char c = 0;
419        Registrant postDialHandler;
420
421        if (mPostDialState == PostDialState.CANCELLED) {
422            //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail");
423            return;
424        }
425
426        if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) {
427            setPostDialState(PostDialState.COMPLETE);
428
429            // notifyMessage.arg1 is 0 on complete
430            c = 0;
431        } else {
432            boolean isValid;
433
434            setPostDialState(PostDialState.STARTED);
435
436            c = mPostDialString.charAt(mNextPostDialChar++);
437
438            isValid = processPostDialChar(c);
439
440            if (!isValid) {
441                // Will call processNextPostDialChar
442                mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
443                // Don't notify application
444                Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
445                return;
446            }
447        }
448
449        notifyPostDialListenersNextChar(c);
450
451        // TODO: remove the following code since the handler no longer executes anything.
452        postDialHandler = mOwner.mPhone.mPostDialHandler;
453
454        Message notifyMessage;
455
456        if (postDialHandler != null
457                && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
458            // The AsyncResult.result is the Connection object
459            PostDialState state = mPostDialState;
460            AsyncResult ar = AsyncResult.forMessage(notifyMessage);
461            ar.result = this;
462            ar.userObj = state;
463
464            // arg1 is the character that was/is being processed
465            notifyMessage.arg1 = c;
466
467            //Rlog.v(LOG_TAG, "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
468            notifyMessage.sendToTarget();
469        }
470    }
471
472    /**
473     * Set post dial state and acquire wake lock while switching to "started"
474     * state, the wake lock will be released if state switches out of "started"
475     * state or after WAKE_LOCK_TIMEOUT_MILLIS.
476     * @param s new PostDialState
477     */
478    private void setPostDialState(PostDialState s) {
479        if (mPostDialState != PostDialState.STARTED
480                && s == PostDialState.STARTED) {
481            acquireWakeLock();
482            Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
483            mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
484        } else if (mPostDialState == PostDialState.STARTED
485                && s != PostDialState.STARTED) {
486            mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
487            releaseWakeLock();
488        }
489        mPostDialState = s;
490        notifyPostDialListeners();
491    }
492
493    private void
494    createWakeLock(Context context) {
495        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
496        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
497    }
498
499    private void
500    acquireWakeLock() {
501        Rlog.d(LOG_TAG, "acquireWakeLock");
502        mPartialWakeLock.acquire();
503    }
504
505    void
506    releaseWakeLock() {
507        synchronized(mPartialWakeLock) {
508            if (mPartialWakeLock.isHeld()) {
509                Rlog.d(LOG_TAG, "releaseWakeLock");
510                mPartialWakeLock.release();
511            }
512        }
513    }
514
515    @Override
516    public int getNumberPresentation() {
517        return mNumberPresentation;
518    }
519
520    @Override
521    public UUSInfo getUUSInfo() {
522        return mUusInfo;
523    }
524
525    @Override
526    public Connection getOrigConnection() {
527        return null;
528    }
529
530    @Override
531    public boolean isMultiparty() {
532        return mImsCall != null && mImsCall.isMultiparty();
533    }
534
535    /*package*/ ImsCall getImsCall() {
536        return mImsCall;
537    }
538
539    /*package*/ void setImsCall(ImsCall imsCall) {
540        mImsCall = imsCall;
541    }
542
543    /*package*/ void changeParent(ImsPhoneCall parent) {
544        mParent = parent;
545    }
546
547    /**
548     * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been
549     *     changed, and {@code false} otherwise.
550     */
551    /*package*/ boolean update(ImsCall imsCall, ImsPhoneCall.State state) {
552        if (state == ImsPhoneCall.State.ACTIVE) {
553            if (mParent.getState().isRinging() || mParent.getState().isDialing()) {
554                onConnectedInOrOut();
555            }
556
557            if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) {
558                //mForegroundCall should be IDLE
559                //when accepting WAITING call
560                //before accept WAITING call,
561                //the ACTIVE call should be held ahead
562                mParent.detach(this);
563                mParent = mOwner.mForegroundCall;
564                mParent.attach(this);
565            }
566        } else if (state == ImsPhoneCall.State.HOLDING) {
567            onStartedHolding();
568        }
569
570        boolean updateParent = mParent.update(this, imsCall, state);
571        boolean updateMediaCapabilities = updateMediaCapabilities(imsCall);
572        return updateParent || updateMediaCapabilities;
573    }
574
575    @Override
576    public int getPreciseDisconnectCause() {
577        return 0;
578    }
579
580    /**
581     * Notifies this Connection of a request to disconnect a participant of the conference managed
582     * by the connection.
583     *
584     * @param endpoint the {@link android.net.Uri} of the participant to disconnect.
585     */
586    @Override
587    public void onDisconnectConferenceParticipant(Uri endpoint) {
588        ImsCall imsCall = getImsCall();
589        if (imsCall == null) {
590            return;
591        }
592        try {
593            imsCall.removeParticipants(new String[]{endpoint.toString()});
594        } catch (ImsException e) {
595            // No session in place -- no change
596            Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+
597                    "Failed to disconnect endpoint = " + endpoint);
598        }
599    }
600
601    /**
602     * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and
603     * update the {@link ImsPhoneConnection} with this information.
604     *
605     * @param imsCall The call to check for changes in media capabilities.
606     * @return Whether the media capabilities have been changed.
607     */
608    private boolean updateMediaCapabilities(ImsCall imsCall) {
609        if (imsCall == null) {
610            return false;
611        }
612
613        boolean changed = false;
614
615        try {
616            // The actual call profile (negotiated between local and peer).
617            ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile();
618            // The capabilities of the local device.
619            ImsCallProfile localCallProfile = imsCall.getLocalCallProfile();
620            // The capabilities of the peer device.
621            ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile();
622
623            if (negotiatedCallProfile != null) {
624                int callType = negotiatedCallProfile.mCallType;
625
626                int newVideoState = ImsCallProfile.getVideoStateFromCallType(callType);
627                if (getVideoState() != newVideoState) {
628                    setVideoState(newVideoState);
629                    changed = true;
630                }
631            }
632
633            if (localCallProfile != null) {
634                int callType = localCallProfile.mCallType;
635
636                boolean newLocalVideoCapable = callType == ImsCallProfile.CALL_TYPE_VT;
637                if (isLocalVideoCapable() != newLocalVideoCapable) {
638                    setLocalVideoCapable(newLocalVideoCapable);
639                    changed = true;
640                }
641            }
642
643            int newAudioQuality =
644                    getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile);
645            if (getAudioQuality() != newAudioQuality) {
646                setAudioQuality(newAudioQuality);
647                changed = true;
648            }
649        } catch (ImsException e) {
650            // No session in place -- no change
651        }
652
653        return changed;
654    }
655
656    /**
657     * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote
658     * {@link ImsCallProfile}. If indicate a HQ audio call if the local stream profile
659     * indicates AMR_WB or EVRC_WB and there is no remote restrict cause.
660     *
661     * @param localCallProfile The local call profile.
662     * @param remoteCallProfile The remote call profile.
663     * @return The audio quality.
664     */
665    private int getAudioQualityFromCallProfile(
666            ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) {
667        if (localCallProfile == null || remoteCallProfile == null
668                || localCallProfile.mMediaProfile == null) {
669            return AUDIO_QUALITY_STANDARD;
670        }
671
672        boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality
673                        == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB
674                || localCallProfile.mMediaProfile.mAudioQuality
675                        == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB)
676                && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE;
677        return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD;
678    }
679
680    /**
681     * Provides a string representation of the {@link ImsPhoneConnection}.  Primarily intended for
682     * use in log statements.
683     *
684     * @return String representation of call.
685     */
686    @Override
687    public String toString() {
688        StringBuilder sb = new StringBuilder();
689        sb.append("[ImsPhoneConnection objId: ");
690        sb.append(System.identityHashCode(this));
691        sb.append(" address:");
692        sb.append(Log.pii(getAddress()));
693        sb.append(" ImsCall:");
694        if (mImsCall == null) {
695            sb.append("null");
696        } else {
697            sb.append(mImsCall);
698        }
699        sb.append("]");
700        return sb.toString();
701    }
702}
703
704