TelephonyConnection.java revision 536cff018ecf3a329ccf44f723bae071c840ab71
1/* 2 * Copyright (C) 2014 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.services.telephony; 18 19import android.os.Handler; 20import android.os.Message; 21import android.telecomm.CallAudioState; 22import android.telephony.DisconnectCause; 23 24import com.android.internal.telephony.Call; 25import com.android.internal.telephony.CallStateException; 26import com.android.internal.telephony.Phone; 27import android.telecomm.Connection; 28 29/** 30 * Manages a single phone call in Telephony. 31 */ 32class TelephonyConnection extends Connection { 33 private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 1; 34 35 private final StateHandler mHandler = new StateHandler(); 36 37 private com.android.internal.telephony.Connection mOriginalConnection; 38 private Call.State mState = Call.State.IDLE; 39 40 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) { 41 mOriginalConnection = originalConnection; 42 mOriginalConnection.getCall().getPhone().registerForPreciseCallStateChanged(mHandler, 43 EVENT_PRECISE_CALL_STATE_CHANGED, null); 44 updateState(); 45 } 46 47 com.android.internal.telephony.Connection getOriginalConnection() { 48 return mOriginalConnection; 49 } 50 51 @Override 52 protected void onAbort() { 53 hangup(DisconnectCause.LOCAL); 54 super.onAbort(); 55 } 56 57 @Override 58 protected void onDisconnect() { 59 hangup(DisconnectCause.LOCAL); 60 super.onDisconnect(); 61 } 62 63 @Override 64 protected void onSeparate() { 65 if (mOriginalConnection != null) { 66 try { 67 mOriginalConnection.separate(); 68 } catch (CallStateException e) { 69 Log.e(this, e, "Call to Connection.separate failed with exception"); 70 } 71 } 72 super.onSeparate(); 73 } 74 75 @Override 76 public void onHold() { 77 Log.d(this, "Attempting to put call on hold"); 78 // TODO(santoscordon): Can dialing calls be put on hold as well since they take up the 79 // foreground call slot? 80 if (Call.State.ACTIVE == mState) { 81 Log.v(this, "Holding active call"); 82 try { 83 Phone phone = mOriginalConnection.getCall().getPhone(); 84 Call ringingCall = phone.getRingingCall(); 85 86 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 87 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 88 // a call on hold while a call-waiting call exists, it'll end up accepting the 89 // call-waiting call, which is bad if that was not the user's intention. We are 90 // cheating here and simply skipping it because we know any attempt to hold a call 91 // while a call-waiting call is happening is likely a request from Telecomm prior to 92 // accepting the call-waiting call. 93 // TODO(santoscordon): Investigate a better solution. It would be great here if we 94 // could "fake" hold by silencing the audio and microphone streams for this call 95 // instead of actually putting it on hold. 96 if (ringingCall.getState() != Call.State.WAITING) { 97 phone.switchHoldingAndActive(); 98 } 99 100 // TODO(santoscordon): Cdma calls are slightly different. 101 } catch (CallStateException e) { 102 Log.e(this, e, "Exception occurred while trying to put call on hold."); 103 } 104 } else { 105 Log.w(this, "Cannot put a call that is not currently active on hold."); 106 } 107 super.onHold(); 108 } 109 110 @Override 111 protected void onUnhold() { 112 Log.d(this, "Attempting to release call from hold"); 113 if (Call.State.HOLDING == mState) { 114 try { 115 // TODO: This doesn't handle multiple calls across connection services yet 116 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 117 } catch (CallStateException e) { 118 Log.e(this, e, "Exception occurred while trying to release call from hold."); 119 } 120 } else { 121 Log.w(this, "Cannot release a call that is not already on hold from hold."); 122 } 123 super.onUnhold(); 124 } 125 126 @Override 127 public void onSetAudioState(CallAudioState audioState) { 128 // TODO: update TTY mode. 129 if (mOriginalConnection != null) { 130 Call call = mOriginalConnection.getCall(); 131 if (call != null) { 132 call.getPhone().setEchoSuppressionEnabled(); 133 } 134 } 135 super.onSetAudioState(audioState); 136 } 137 138 protected void hangup(int disconnectCause) { 139 if (mOriginalConnection != null) { 140 try { 141 Call call = mOriginalConnection.getCall(); 142 if (call != null && !call.isMultiparty()) { 143 call.hangup(); 144 } else { 145 mOriginalConnection.hangup(); 146 } 147 // Set state deliberately since we are going to close() and will no longer be 148 // listening to state updates from mOriginalConnection 149 setDisconnected(disconnectCause, null); 150 } catch (CallStateException e) { 151 Log.e(this, e, "Call to Connection.hangup failed with exception"); 152 } 153 } 154 close(); 155 } 156 157 private void updateState() { 158 if (mOriginalConnection == null) { 159 return; 160 } 161 162 Call.State newState = mOriginalConnection.getState(); 163 Log.v(this, "Update state from %s to %s for %s", mState, newState, this); 164 if (mState != newState) { 165 Log.d(this, "mOriginalConnection new state = %s", newState); 166 167 mState = newState; 168 switch (newState) { 169 case IDLE: 170 break; 171 case ACTIVE: 172 setActive(); 173 break; 174 case HOLDING: 175 setOnHold(); 176 break; 177 case DIALING: 178 case ALERTING: 179 setDialing(); 180 break; 181 case INCOMING: 182 case WAITING: 183 setRinging(); 184 break; 185 case DISCONNECTED: 186 setDisconnected(mOriginalConnection.getDisconnectCause(), null); 187 break; 188 case DISCONNECTING: 189 break; 190 } 191 } 192 } 193 194 private void close() { 195 if (mOriginalConnection != null) { 196 Call call = mOriginalConnection.getCall(); 197 if (call != null) { 198 call.getPhone().unregisterForPreciseCallStateChanged(mHandler); 199 } 200 mOriginalConnection = null; 201 setDestroyed(); 202 } 203 } 204 205 private class StateHandler extends Handler { 206 @Override 207 public void handleMessage(Message msg) { 208 switch (msg.what) { 209 case EVENT_PRECISE_CALL_STATE_CHANGED: 210 updateState(); 211 break; 212 } 213 } 214 } 215} 216