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