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