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