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.telecom.ConferenceParticipant;
     20 import android.telephony.Rlog;
     21 import android.telephony.DisconnectCause;
     22 import android.util.Log;
     23 
     24 import com.android.internal.annotations.VisibleForTesting;
     25 import com.android.internal.telephony.Call;
     26 import com.android.internal.telephony.CallStateException;
     27 import com.android.internal.telephony.Connection;
     28 import com.android.internal.telephony.Phone;
     29 import com.android.ims.ImsCall;
     30 import com.android.ims.ImsException;
     31 import com.android.ims.ImsStreamMediaProfile;
     32 
     33 import java.util.List;
     34 
     35 /**
     36  * {@hide}
     37  */
     38 public class ImsPhoneCall extends Call {
     39     private static final String LOG_TAG = "ImsPhoneCall";
     40 
     41     // This flag is meant to be used as a debugging tool to quickly see all logs
     42     // regardless of the actual log level set on this component.
     43     private static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
     44     private static final boolean DBG = FORCE_DEBUG || Rlog.isLoggable(LOG_TAG, Log.DEBUG);
     45     private static final boolean VDBG = FORCE_DEBUG || Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
     46 
     47     /*************************** Instance Variables **************************/
     48     public static final String CONTEXT_UNKNOWN = "UK";
     49     public static final String CONTEXT_RINGING = "RG";
     50     public static final String CONTEXT_FOREGROUND = "FG";
     51     public static final String CONTEXT_BACKGROUND = "BG";
     52     public static final String CONTEXT_HANDOVER = "HO";
     53 
     54     /*package*/ ImsPhoneCallTracker mOwner;
     55 
     56     private boolean mRingbackTonePlayed = false;
     57 
     58     // Determines what type of ImsPhoneCall this is.  ImsPhoneCallTracker uses instances of
     59     // ImsPhoneCall to for fg, bg, etc calls.  This is used as a convenience for logging so that it
     60     // can be made clear whether a call being logged is the foreground, background, etc.
     61     private final String mCallContext;
     62 
     63     /****************************** Constructors *****************************/
     64     /*package*/
     65     ImsPhoneCall() {
     66         mCallContext = CONTEXT_UNKNOWN;
     67     }
     68 
     69     /*package*/
     70     ImsPhoneCall(ImsPhoneCallTracker owner, String context) {
     71         mOwner = owner;
     72         mCallContext = context;
     73     }
     74 
     75     public void dispose() {
     76         try {
     77             mOwner.hangup(this);
     78         } catch (CallStateException ex) {
     79             //Rlog.e(LOG_TAG, "dispose: unexpected error on hangup", ex);
     80             //while disposing, ignore the exception and clean the connections
     81         } finally {
     82             for(int i = 0, s = mConnections.size(); i < s; i++) {
     83                 ImsPhoneConnection c = (ImsPhoneConnection) mConnections.get(i);
     84                 c.onDisconnect(DisconnectCause.LOST_SIGNAL);
     85             }
     86         }
     87     }
     88 
     89     /************************** Overridden from Call *************************/
     90 
     91     @Override
     92     public List<Connection>
     93     getConnections() {
     94         return mConnections;
     95     }
     96 
     97     @Override
     98     public Phone
     99     getPhone() {
    100         return mOwner.mPhone;
    101     }
    102 
    103     @Override
    104     public boolean
    105     isMultiparty() {
    106         ImsCall imsCall = getImsCall();
    107         if (imsCall == null) {
    108             return false;
    109         }
    110 
    111         return imsCall.isMultiparty();
    112     }
    113 
    114     /** Please note: if this is the foreground call and a
    115      *  background call exists, the background call will be resumed.
    116      */
    117     @Override
    118     public void
    119     hangup() throws CallStateException {
    120         mOwner.hangup(this);
    121     }
    122 
    123     @Override
    124     public String toString() {
    125         StringBuilder sb = new StringBuilder();
    126         sb.append("[ImsPhoneCall ");
    127         sb.append(mCallContext);
    128         sb.append(" state: ");
    129         sb.append(mState.toString());
    130         sb.append(" ");
    131         if (mConnections.size() > 1) {
    132             sb.append(" ERROR_MULTIPLE ");
    133         }
    134         for (Connection conn : mConnections) {
    135             sb.append(conn);
    136             sb.append(" ");
    137         }
    138 
    139         sb.append("]");
    140         return sb.toString();
    141     }
    142 
    143     @Override
    144     public List<ConferenceParticipant> getConferenceParticipants() {
    145          ImsCall call = getImsCall();
    146          if (call == null) {
    147              return null;
    148          }
    149          return call.getConferenceParticipants();
    150     }
    151 
    152     //***** Called from ImsPhoneConnection
    153 
    154     /*package*/ void
    155     attach(Connection conn) {
    156         if (VDBG) {
    157             Rlog.v(LOG_TAG, "attach : " + mCallContext + " conn = " + conn);
    158         }
    159         clearDisconnected();
    160         mConnections.add(conn);
    161 
    162         mOwner.logState();
    163     }
    164 
    165     /*package*/ void
    166     attach(Connection conn, State state) {
    167         if (VDBG) {
    168             Rlog.v(LOG_TAG, "attach : " + mCallContext + " state = " +
    169                     state.toString());
    170         }
    171         this.attach(conn);
    172         mState = state;
    173     }
    174 
    175     /*package*/ void
    176     attachFake(Connection conn, State state) {
    177         attach(conn, state);
    178     }
    179 
    180     /**
    181      * Called by ImsPhoneConnection when it has disconnected
    182      */
    183     boolean
    184     connectionDisconnected(ImsPhoneConnection conn) {
    185         if (mState != State.DISCONNECTED) {
    186             /* If only disconnected connections remain, we are disconnected*/
    187 
    188             boolean hasOnlyDisconnectedConnections = true;
    189 
    190             for (int i = 0, s = mConnections.size()  ; i < s; i ++) {
    191                 if (mConnections.get(i).getState() != State.DISCONNECTED) {
    192                     hasOnlyDisconnectedConnections = false;
    193                     break;
    194                 }
    195             }
    196 
    197             if (hasOnlyDisconnectedConnections) {
    198                 mState = State.DISCONNECTED;
    199                 return true;
    200             }
    201         }
    202 
    203         return false;
    204     }
    205 
    206     /*package*/ void
    207     detach(ImsPhoneConnection conn) {
    208         if (VDBG) {
    209             Rlog.v(LOG_TAG, "detach : " + mCallContext + " conn = " + conn);
    210         }
    211         mConnections.remove(conn);
    212         clearDisconnected();
    213 
    214         mOwner.logState();
    215     }
    216 
    217     /**
    218      * @return true if there's no space in this call for additional
    219      * connections to be added via "conference"
    220      */
    221     /*package*/ boolean
    222     isFull() {
    223         return mConnections.size() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL;
    224     }
    225 
    226     //***** Called from ImsPhoneCallTracker
    227     /**
    228      * Called when this Call is being hung up locally (eg, user pressed "end")
    229      */
    230     void
    231     onHangupLocal() {
    232         for (int i = 0, s = mConnections.size(); i < s; i++) {
    233             ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i);
    234             cn.onHangupLocal();
    235         }
    236         mState = State.DISCONNECTING;
    237     }
    238 
    239     /**
    240      * Called when it's time to clean up disconnected Connection objects
    241      */
    242     void
    243     clearDisconnected() {
    244         for (int i = mConnections.size() - 1 ; i >= 0 ; i--) {
    245             ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i);
    246 
    247             if (cn.getState() == State.DISCONNECTED) {
    248                 mConnections.remove(i);
    249             }
    250         }
    251 
    252         if (mConnections.size() == 0) {
    253             mState = State.IDLE;
    254         }
    255     }
    256 
    257     /*package*/ ImsPhoneConnection
    258     getFirstConnection() {
    259         if (mConnections.size() == 0) return null;
    260 
    261         return (ImsPhoneConnection) mConnections.get(0);
    262     }
    263 
    264     /*package*/ void
    265     setMute(boolean mute) {
    266         ImsCall imsCall = getFirstConnection() == null ?
    267                 null : getFirstConnection().getImsCall();
    268         if (imsCall != null) {
    269             try {
    270                 imsCall.setMute(mute);
    271             } catch (ImsException e) {
    272                 Rlog.e(LOG_TAG, "setMute failed : " + e.getMessage());
    273             }
    274         }
    275     }
    276 
    277     /* package */ void
    278     merge(ImsPhoneCall that, State state) {
    279         // This call is the conference host and the "that" call is the one being merged in.
    280         // Set the connect time for the conference; this will have been determined when the
    281         // conference was initially created.
    282         ImsPhoneConnection imsPhoneConnection = getFirstConnection();
    283         if (imsPhoneConnection != null) {
    284             long conferenceConnectTime = imsPhoneConnection.getConferenceConnectTime();
    285             if (conferenceConnectTime > 0) {
    286                 imsPhoneConnection.setConnectTime(conferenceConnectTime);
    287             } else {
    288                 if (DBG) {
    289                     Rlog.d(LOG_TAG, "merge: conference connect time is 0");
    290                 }
    291             }
    292         }
    293         if (DBG) {
    294             Rlog.d(LOG_TAG, "merge(" + mCallContext + "): " + that + "state = "
    295                     + state);
    296         }
    297     }
    298 
    299     /**
    300      * Retrieves the {@link ImsCall} for the current {@link ImsPhoneCall}.
    301      * <p>
    302      * Marked as {@code VisibleForTesting} so that the
    303      * {@link com.android.internal.telephony.TelephonyTester} class can inject a test conference
    304      * event package into a regular ongoing IMS call.
    305      *
    306      * @return The {@link ImsCall}.
    307      */
    308     @VisibleForTesting
    309     public ImsCall
    310     getImsCall() {
    311         return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall();
    312     }
    313 
    314     /*package*/ static boolean isLocalTone(ImsCall imsCall) {
    315         if ((imsCall == null) || (imsCall.getCallProfile() == null)
    316                 || (imsCall.getCallProfile().mMediaProfile == null)) {
    317             return false;
    318         }
    319 
    320         ImsStreamMediaProfile mediaProfile = imsCall.getCallProfile().mMediaProfile;
    321 
    322         return (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE)
    323                 ? true : false;
    324     }
    325 
    326     /*package*/ boolean
    327     update (ImsPhoneConnection conn, ImsCall imsCall, State state) {
    328         State newState = state;
    329         boolean changed = false;
    330 
    331         //ImsCall.Listener.onCallProgressing can be invoked several times
    332         //and ringback tone mode can be changed during the call setup procedure
    333         if (state == State.ALERTING) {
    334             if (mRingbackTonePlayed && !isLocalTone(imsCall)) {
    335                 mOwner.mPhone.stopRingbackTone();
    336                 mRingbackTonePlayed = false;
    337             } else if (!mRingbackTonePlayed && isLocalTone(imsCall)) {
    338                 mOwner.mPhone.startRingbackTone();
    339                 mRingbackTonePlayed = true;
    340             }
    341         } else {
    342             if (mRingbackTonePlayed) {
    343                 mOwner.mPhone.stopRingbackTone();
    344                 mRingbackTonePlayed = false;
    345             }
    346         }
    347 
    348         if ((newState != mState) && (state != State.DISCONNECTED)) {
    349             mState = newState;
    350             changed = true;
    351         } else if (state == State.DISCONNECTED) {
    352             changed = true;
    353         }
    354 
    355         return changed;
    356     }
    357 
    358     /* package */ ImsPhoneConnection
    359     getHandoverConnection() {
    360         return (ImsPhoneConnection) getEarliestConnection();
    361     }
    362 
    363     void switchWith(ImsPhoneCall that) {
    364         if (VDBG) {
    365             Rlog.v(LOG_TAG, "switchWith : switchCall = " + this + " withCall = " + that);
    366         }
    367         synchronized (ImsPhoneCall.class) {
    368             ImsPhoneCall tmp = new ImsPhoneCall();
    369             tmp.takeOver(this);
    370             this.takeOver(that);
    371             that.takeOver(tmp);
    372         }
    373         mOwner.logState();
    374     }
    375 
    376     private void takeOver(ImsPhoneCall that) {
    377         mConnections = that.mConnections;
    378         mState = that.mState;
    379         for (Connection c : mConnections) {
    380             ((ImsPhoneConnection) c).changeParent(this);
    381         }
    382     }
    383 }
    384