Home | History | Annotate | Download | only in imsphone
      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 
     17 package com.android.internal.telephony.imsphone;
     18 
     19 import android.content.Context;
     20 import android.os.AsyncResult;
     21 import android.os.Handler;
     22 import android.os.Looper;
     23 import android.os.Message;
     24 import android.os.PowerManager;
     25 import android.os.Registrant;
     26 import android.os.SystemClock;
     27 import android.telephony.DisconnectCause;
     28 import android.telephony.PhoneNumberUtils;
     29 import android.telephony.Rlog;
     30 
     31 import com.android.ims.ImsException;
     32 import com.android.ims.ImsStreamMediaProfile;
     33 import com.android.internal.telephony.CallStateException;
     34 import com.android.internal.telephony.Connection;
     35 import com.android.internal.telephony.Phone;
     36 import com.android.internal.telephony.PhoneConstants;
     37 import com.android.internal.telephony.UUSInfo;
     38 
     39 import com.android.ims.ImsCall;
     40 import com.android.ims.ImsCallProfile;
     41 
     42 /**
     43  * {@hide}
     44  */
     45 public 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     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         if (imsCall != null) {
    622             // Check for a change in the video capabilities for the call and update the
    623             // {@link ImsPhoneConnection} with this information.
    624             try {
    625                 // Get the current local VT capabilities (i.e. even if currentCallType above is
    626                 // audio-only, the local capability could support bi-directional video).
    627                 ImsCallProfile localCallProfile = imsCall.getLocalCallProfile();
    628                 if (localCallProfile != null) {
    629                     int localCallTypeCapability = localCallProfile.mCallType;
    630                     boolean newLocalVideoCapable = localCallTypeCapability
    631                             == ImsCallProfile.CALL_TYPE_VT;
    632 
    633                     if (isLocalVideoCapable() != newLocalVideoCapable) {
    634                         setLocalVideoCapable(newLocalVideoCapable);
    635                         changed = true;
    636                     }
    637                 }
    638             } catch (ImsException e) {
    639                 // No session in place -- no change
    640             }
    641 
    642             // Check for a change in the call type / video state, or audio quality of the
    643             // {@link ImsCall} and update the {@link ImsPhoneConnection} with this information.
    644             ImsCallProfile callProfile = imsCall.getCallProfile();
    645             if (callProfile != null) {
    646                 int oldVideoState = getVideoState();
    647                 int newVideoState = ImsCallProfile.getVideoStateFromCallType(callProfile.mCallType);
    648 
    649                 if (oldVideoState != newVideoState) {
    650                     setVideoState(newVideoState);
    651                     changed = true;
    652                 }
    653 
    654                 ImsStreamMediaProfile mediaProfile = callProfile.mMediaProfile;
    655                 if (mediaProfile != null) {
    656                     int oldAudioQuality = getAudioQuality();
    657                     int newAudioQuality = getAudioQualityFromMediaProfile(mediaProfile);
    658 
    659                     if (oldAudioQuality != newAudioQuality) {
    660                         setAudioQuality(newAudioQuality);
    661                         changed = true;
    662                     }
    663                 }
    664             }
    665         }
    666         return changed;
    667     }
    668 
    669     @Override
    670     public int getPreciseDisconnectCause() {
    671         return 0;
    672     }
    673 }
    674 
    675