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