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