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