ImsPhoneConnection.java revision 64e62340aae85179a6468ccac4a401900eb4dc2f
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.telecomm.VideoCallProfile;
28import android.telephony.DisconnectCause;
29import android.telephony.PhoneNumberUtils;
30import android.telephony.Rlog;
31
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            //TODO(vt): Set the local and remote video capabilities appropriately.
154            setLocalVideoCapable(true);
155            setRemoteVideoCapable(true);
156        } else {
157            mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
158            mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN;
159        }
160
161        mIsIncoming = true;
162        mCreateTime = System.currentTimeMillis();
163        mUusInfo = null;
164
165        //mIndex = index;
166
167        mParent = parent;
168        mParent.attach(this, ImsPhoneCall.State.INCOMING);
169    }
170
171    /** This is an MO call, created when dialing */
172    /*package*/
173    ImsPhoneConnection(Context context, String dialString, ImsPhoneCallTracker ct, ImsPhoneCall parent) {
174        createWakeLock(context);
175        acquireWakeLock();
176
177        mOwner = ct;
178        mHandler = new MyHandler(mOwner.getLooper());
179
180        mDialString = dialString;
181
182        mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
183        mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
184
185        //mIndex = -1;
186
187        mIsIncoming = false;
188        mCnapName = null;
189        mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
190        mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
191        mCreateTime = System.currentTimeMillis();
192
193        mParent = parent;
194        parent.attachFake(this, ImsPhoneCall.State.DIALING);
195    }
196
197    public void dispose() {
198    }
199
200    static boolean
201    equalsHandlesNulls (Object a, Object b) {
202        return (a == null) ? (b == null) : a.equals (b);
203    }
204
205    @Override
206    public String getOrigDialString(){
207        return mDialString;
208    }
209
210    @Override
211    public String getAddress() {
212        return mAddress;
213    }
214
215    @Override
216    public ImsPhoneCall getCall() {
217        return mParent;
218    }
219
220    @Override
221    public long getCreateTime() {
222        return mCreateTime;
223    }
224
225    @Override
226    public long getConnectTime() {
227        return mConnectTime;
228    }
229
230    @Override
231    public long getConnectTimeReal() {
232        return mConnectTimeReal;
233    }
234
235    @Override
236    public long getDisconnectTime() {
237        return mDisconnectTime;
238    }
239
240    @Override
241    public long getDurationMillis() {
242        if (mConnectTimeReal == 0) {
243            return 0;
244        } else if (mDuration == 0) {
245            return SystemClock.elapsedRealtime() - mConnectTimeReal;
246        } else {
247            return mDuration;
248        }
249    }
250
251    @Override
252    public long getHoldingStartTime() {
253        return mHoldingStartTime;
254    }
255
256    @Override
257    public long getHoldDurationMillis() {
258        if (getState() != ImsPhoneCall.State.HOLDING) {
259            // If not holding, return 0
260            return 0;
261        } else {
262            return SystemClock.elapsedRealtime() - mHoldingStartTime;
263        }
264    }
265
266    @Override
267    public int getDisconnectCause() {
268        return mCause;
269    }
270
271    public void setDisconnectCause(int cause) {
272        mCause = cause;
273    }
274
275    public ImsPhoneCallTracker getOwner () {
276        return mOwner;
277    }
278
279    @Override
280    public boolean isIncoming() {
281        return mIsIncoming;
282    }
283
284    @Override
285    public ImsPhoneCall.State getState() {
286        if (mDisconnected) {
287            return ImsPhoneCall.State.DISCONNECTED;
288        } else {
289            return super.getState();
290        }
291    }
292
293    @Override
294    public void hangup() throws CallStateException {
295        if (!mDisconnected) {
296            mOwner.hangup(this);
297        } else {
298            throw new CallStateException ("disconnected");
299        }
300    }
301
302    @Override
303    public void separate() throws CallStateException {
304        throw new CallStateException ("not supported");
305    }
306
307    @Override
308    public PostDialState getPostDialState() {
309        return mPostDialState;
310    }
311
312    @Override
313    public void proceedAfterWaitChar() {
314        if (mPostDialState != PostDialState.WAIT) {
315            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
316                    + "getPostDialState() to be WAIT but was " + mPostDialState);
317            return;
318        }
319
320        setPostDialState(PostDialState.STARTED);
321
322        processNextPostDialChar();
323    }
324
325    @Override
326    public void proceedAfterWildChar(String str) {
327        if (mPostDialState != PostDialState.WILD) {
328            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
329                    + "getPostDialState() to be WILD but was " + mPostDialState);
330            return;
331        }
332
333        setPostDialState(PostDialState.STARTED);
334
335        // make a new postDialString, with the wild char replacement string
336        // at the beginning, followed by the remaining postDialString.
337
338        StringBuilder buf = new StringBuilder(str);
339        buf.append(mPostDialString.substring(mNextPostDialChar));
340        mPostDialString = buf.toString();
341        mNextPostDialChar = 0;
342        if (Phone.DEBUG_PHONE) {
343            Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " +
344                    mPostDialString);
345        }
346
347        processNextPostDialChar();
348    }
349
350    @Override
351    public void cancelPostDial() {
352        setPostDialState(PostDialState.CANCELLED);
353    }
354
355    /**
356     * Called when this Connection is being hung up locally (eg, user pressed "end")
357     */
358    void
359    onHangupLocal() {
360        mCause = DisconnectCause.LOCAL;
361    }
362
363    /** Called when the connection has been disconnected */
364    /*package*/ boolean
365    onDisconnect(int cause) {
366        Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
367        if (mCause != DisconnectCause.LOCAL) mCause = cause;
368        return onDisconnect();
369    }
370
371    /*package*/ boolean
372    onDisconnect() {
373        boolean changed = false;
374
375        if (!mDisconnected) {
376            //mIndex = -1;
377
378            mDisconnectTime = System.currentTimeMillis();
379            mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
380            mDisconnected = true;
381
382            mOwner.mPhone.notifyDisconnect(this);
383
384            if (mParent != null) {
385                changed = mParent.connectionDisconnected(this);
386            } else {
387                Rlog.d(LOG_TAG, "onDisconnect: no parent");
388            }
389            if (mImsCall != null) mImsCall.close();
390            mImsCall = null;
391        }
392        releaseWakeLock();
393        return changed;
394    }
395
396    /**
397     * An incoming or outgoing call has connected
398     */
399    void
400    onConnectedInOrOut() {
401        mConnectTime = System.currentTimeMillis();
402        mConnectTimeReal = SystemClock.elapsedRealtime();
403        mDuration = 0;
404
405        if (Phone.DEBUG_PHONE) {
406            Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime);
407        }
408
409        if (!mIsIncoming) {
410            // outgoing calls only
411            processNextPostDialChar();
412        }
413        releaseWakeLock();
414    }
415
416    /*package*/ void
417    onStartedHolding() {
418        mHoldingStartTime = SystemClock.elapsedRealtime();
419    }
420    /**
421     * Performs the appropriate action for a post-dial char, but does not
422     * notify application. returns false if the character is invalid and
423     * should be ignored
424     */
425    private boolean
426    processPostDialChar(char c) {
427        if (PhoneNumberUtils.is12Key(c)) {
428            mOwner.mCi.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE));
429        } else if (c == PhoneNumberUtils.PAUSE) {
430            // From TS 22.101:
431            // It continues...
432            // Upon the called party answering the UE shall send the DTMF digits
433            // automatically to the network after a delay of 3 seconds( 20 ).
434            // The digits shall be sent according to the procedures and timing
435            // specified in 3GPP TS 24.008 [13]. The first occurrence of the
436            // "DTMF Control Digits Separator" shall be used by the ME to
437            // distinguish between the addressing digits (i.e. the phone number)
438            // and the DTMF digits. Upon subsequent occurrences of the
439            // separator,
440            // the UE shall pause again for 3 seconds ( 20 ) before sending
441            // any further DTMF digits.
442            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
443                    PAUSE_DELAY_MILLIS);
444        } else if (c == PhoneNumberUtils.WAIT) {
445            setPostDialState(PostDialState.WAIT);
446        } else if (c == PhoneNumberUtils.WILD) {
447            setPostDialState(PostDialState.WILD);
448        } else {
449            return false;
450        }
451
452        return true;
453    }
454
455    @Override
456    public String
457    getRemainingPostDialString() {
458        if (mPostDialState == PostDialState.CANCELLED
459            || mPostDialState == PostDialState.COMPLETE
460            || mPostDialString == null
461            || mPostDialString.length() <= mNextPostDialChar
462        ) {
463            return "";
464        }
465
466        return mPostDialString.substring(mNextPostDialChar);
467    }
468
469    @Override
470    protected void finalize()
471    {
472        releaseWakeLock();
473    }
474
475    private void
476    processNextPostDialChar() {
477        char c = 0;
478        Registrant postDialHandler;
479
480        if (mPostDialState == PostDialState.CANCELLED) {
481            //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail");
482            return;
483        }
484
485        if (mPostDialString == null ||
486                mPostDialString.length() <= mNextPostDialChar) {
487            setPostDialState(PostDialState.COMPLETE);
488
489            // notifyMessage.arg1 is 0 on complete
490            c = 0;
491        } else {
492            boolean isValid;
493
494            setPostDialState(PostDialState.STARTED);
495
496            c = mPostDialString.charAt(mNextPostDialChar++);
497
498            isValid = processPostDialChar(c);
499
500            if (!isValid) {
501                // Will call processNextPostDialChar
502                mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
503                // Don't notify application
504                Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
505                return;
506            }
507        }
508
509        postDialHandler = mOwner.mPhone.mPostDialHandler;
510
511        Message notifyMessage;
512
513        if (postDialHandler != null
514                && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
515            // The AsyncResult.result is the Connection object
516            PostDialState state = mPostDialState;
517            AsyncResult ar = AsyncResult.forMessage(notifyMessage);
518            ar.result = this;
519            ar.userObj = state;
520
521            // arg1 is the character that was/is being processed
522            notifyMessage.arg1 = c;
523
524            //Rlog.v(LOG_TAG, "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
525            notifyMessage.sendToTarget();
526        }
527    }
528
529    /**
530     * Set post dial state and acquire wake lock while switching to "started"
531     * state, the wake lock will be released if state switches out of "started"
532     * state or after WAKE_LOCK_TIMEOUT_MILLIS.
533     * @param s new PostDialState
534     */
535    private void setPostDialState(PostDialState s) {
536        if (mPostDialState != PostDialState.STARTED
537                && s == PostDialState.STARTED) {
538            acquireWakeLock();
539            Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
540            mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
541        } else if (mPostDialState == PostDialState.STARTED
542                && s != PostDialState.STARTED) {
543            mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
544            releaseWakeLock();
545        }
546        mPostDialState = s;
547    }
548
549    private void
550    createWakeLock(Context context) {
551        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
552        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
553    }
554
555    private void
556    acquireWakeLock() {
557        Rlog.d(LOG_TAG, "acquireWakeLock");
558        mPartialWakeLock.acquire();
559    }
560
561    private void
562    releaseWakeLock() {
563        synchronized(mPartialWakeLock) {
564            if (mPartialWakeLock.isHeld()) {
565                Rlog.d(LOG_TAG, "releaseWakeLock");
566                mPartialWakeLock.release();
567            }
568        }
569    }
570
571    @Override
572    public int getNumberPresentation() {
573        return mNumberPresentation;
574    }
575
576    @Override
577    public UUSInfo getUUSInfo() {
578        return mUusInfo;
579    }
580
581    @Override
582    public Connection getOrigConnection() {
583        return null;
584    }
585
586    /* package */ void
587    setMultiparty(boolean isMultiparty) {
588        Rlog.d(LOG_TAG, "setMultiparty " + isMultiparty);
589        mIsMultiparty = isMultiparty;
590    }
591
592    @Override
593    public boolean isMultiparty() {
594        return mIsMultiparty;
595    }
596
597    /*package*/ ImsCall getImsCall() {
598        return mImsCall;
599    }
600
601    /*package*/ void setImsCall(ImsCall imsCall) {
602        mImsCall = imsCall;
603    }
604
605    /*package*/ void changeParent(ImsPhoneCall parent) {
606        mParent = parent;
607    }
608
609    /*package*/ boolean
610    update(ImsCall imsCall, ImsPhoneCall.State state) {
611        boolean changed = false;
612
613        if (state == ImsPhoneCall.State.ACTIVE) {
614            if (mParent.getState().isRinging()
615                    || mParent.getState().isDialing()) {
616                onConnectedInOrOut();
617            }
618
619            if (mParent.getState().isRinging()
620                    || mParent == mOwner.mBackgroundCall) {
621                //mForegroundCall should be IDLE
622                //when accepting WAITING call
623                //before accept WAITING call,
624                //the ACTIVE call should be held ahead
625                mParent.detach(this);
626                mParent = mOwner.mForegroundCall;
627                mParent.attach(this);
628            }
629        } else if (state == ImsPhoneCall.State.HOLDING) {
630            onStartedHolding();
631        }
632
633        changed = mParent.update(this, imsCall, state);
634
635        return changed;
636    }
637
638    @Override
639    public int getPreciseDisconnectCause() {
640        return 0;
641    }
642
643    private static int presentationFromOir(int oir) {
644        switch(oir) {
645            case ImsCallProfile.OIR_PRESENTATION_RESTRICTED:
646                return PhoneConstants.PRESENTATION_RESTRICTED;
647            case ImsCallProfile.OIR_PRESENTATION_NOT_RESTRICTED:
648                return PhoneConstants.PRESENTATION_ALLOWED;
649            default:
650                return PhoneConstants.PRESENTATION_UNKNOWN;
651        }
652    }
653}
654
655