TelephonyConnection.java revision 2093a451b17c26f4341e9565b65dcaa0e20bbd7d
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 */ 32abstract class 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 abstract int buildCallCapabilities(); 139 140 final void updateCallCapabilities() { 141 int newCallCapabilities = buildCallCapabilities(); 142 if (getCallCapabilities() != newCallCapabilities) { 143 setCallCapabilities(newCallCapabilities); 144 } 145 } 146 147 final void onAddedToCallService() { 148 updateCallCapabilities(); 149 if (mOriginalConnection != null) { 150 setCallerDisplayName( 151 mOriginalConnection.getCnapName(), 152 mOriginalConnection.getCnapNamePresentation()); 153 } 154 } 155 156 protected void hangup(int disconnectCause) { 157 if (mOriginalConnection != null) { 158 try { 159 Call call = mOriginalConnection.getCall(); 160 if (call != null && !call.isMultiparty()) { 161 call.hangup(); 162 } else { 163 mOriginalConnection.hangup(); 164 } 165 // Set state deliberately since we are going to close() and will no longer be 166 // listening to state updates from mOriginalConnection 167 setDisconnected(disconnectCause, null); 168 } catch (CallStateException e) { 169 Log.e(this, e, "Call to Connection.hangup failed with exception"); 170 } 171 } 172 close(); 173 } 174 175 private void updateState() { 176 if (mOriginalConnection == null) { 177 return; 178 } 179 180 Call.State newState = mOriginalConnection.getState(); 181 Log.v(this, "Update state from %s to %s for %s", mState, newState, this); 182 if (mState != newState) { 183 Log.d(this, "mOriginalConnection new state = %s", newState); 184 185 mState = newState; 186 switch (newState) { 187 case IDLE: 188 break; 189 case ACTIVE: 190 setActive(); 191 break; 192 case HOLDING: 193 setOnHold(); 194 break; 195 case DIALING: 196 case ALERTING: 197 setDialing(); 198 break; 199 case INCOMING: 200 case WAITING: 201 setRinging(); 202 break; 203 case DISCONNECTED: 204 setDisconnected(mOriginalConnection.getDisconnectCause(), null); 205 break; 206 case DISCONNECTING: 207 break; 208 } 209 updateCallCapabilities(); 210 } 211 } 212 213 private void close() { 214 if (mOriginalConnection != null) { 215 Call call = mOriginalConnection.getCall(); 216 if (call != null) { 217 call.getPhone().unregisterForPreciseCallStateChanged(mHandler); 218 } 219 mOriginalConnection = null; 220 setDestroyed(); 221 } 222 } 223 224 private class StateHandler extends Handler { 225 @Override 226 public void handleMessage(Message msg) { 227 switch (msg.what) { 228 case EVENT_PRECISE_CALL_STATE_CHANGED: 229 updateState(); 230 break; 231 } 232 } 233 } 234} 235