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 17 package com.android.services.telephony; 18 19 import android.net.Uri; 20 import android.os.AsyncResult; 21 import android.os.Bundle; 22 import android.os.Handler; 23 import android.os.Message; 24 import android.os.SystemClock; 25 import android.telecom.PhoneAccount; 26 import android.telecom.PhoneAccountHandle; 27 import android.telecom.TelecomManager; 28 import android.text.TextUtils; 29 30 import com.android.internal.telephony.Call; 31 import com.android.internal.telephony.CallStateException; 32 import com.android.internal.telephony.Connection; 33 import com.android.internal.telephony.GsmCdmaPhone; 34 import com.android.internal.telephony.Phone; 35 import com.android.internal.telephony.PhoneConstants; 36 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification; 37 import com.android.internal.telephony.imsphone.ImsExternalCallTracker; 38 import com.android.internal.telephony.imsphone.ImsExternalConnection; 39 import com.android.internal.telephony.imsphone.ImsPhoneConnection; 40 import com.android.phone.NumberVerificationManager; 41 import com.android.phone.PhoneUtils; 42 import com.android.telephony.Rlog; 43 44 import java.util.Objects; 45 46 /** 47 * Listens to incoming-call events from the associated phone object and notifies Telecom upon each 48 * occurence. One instance of these exists for each of the telephony-based call services. 49 */ 50 final class PstnIncomingCallNotifier { 51 private static final String LOG_TAG = "PstnIncomingCallNotifier"; 52 53 /** New ringing connection event code. */ 54 private static final int EVENT_NEW_RINGING_CONNECTION = 100; 55 private static final int EVENT_CDMA_CALL_WAITING = 101; 56 private static final int EVENT_UNKNOWN_CONNECTION = 102; 57 58 /** 59 * The max amount of time to wait before hanging up a call that was for number verification. 60 * 61 * The delay is so that the remote end has time to hang up the call after receiving the 62 * verification signal so that the call doesn't go to voicemail. 63 */ 64 private static final int MAX_NUMBER_VERIFICATION_HANGUP_DELAY_MILLIS = 10000; 65 66 /** The phone object to listen to. */ 67 private final Phone mPhone; 68 69 /** 70 * Used to listen to events from {@link #mPhone}. 71 */ 72 private final Handler mHandler = new Handler() { 73 @Override 74 public void handleMessage(Message msg) { 75 switch(msg.what) { 76 case EVENT_NEW_RINGING_CONNECTION: 77 handleNewRingingConnection((AsyncResult) msg.obj); 78 break; 79 case EVENT_CDMA_CALL_WAITING: 80 handleCdmaCallWaiting((AsyncResult) msg.obj); 81 break; 82 case EVENT_UNKNOWN_CONNECTION: 83 handleNewUnknownConnection((AsyncResult) msg.obj); 84 break; 85 default: 86 break; 87 } 88 } 89 }; 90 91 /** 92 * Persists the specified parameters and starts listening to phone events. 93 * 94 * @param phone The phone object for listening to incoming calls. 95 */ PstnIncomingCallNotifier(Phone phone)96 PstnIncomingCallNotifier(Phone phone) { 97 if (phone == null) { 98 throw new NullPointerException(); 99 } 100 101 mPhone = phone; 102 103 registerForNotifications(); 104 } 105 teardown()106 void teardown() { 107 unregisterForNotifications(); 108 } 109 110 /** 111 * Register for notifications from the base phone. 112 */ registerForNotifications()113 private void registerForNotifications() { 114 if (mPhone != null) { 115 Log.i(this, "Registering: %s", mPhone); 116 mPhone.registerForNewRingingConnection(mHandler, EVENT_NEW_RINGING_CONNECTION, null); 117 mPhone.registerForCallWaiting(mHandler, EVENT_CDMA_CALL_WAITING, null); 118 mPhone.registerForUnknownConnection(mHandler, EVENT_UNKNOWN_CONNECTION, null); 119 } 120 } 121 unregisterForNotifications()122 private void unregisterForNotifications() { 123 if (mPhone != null) { 124 Log.i(this, "Unregistering: %s", mPhone); 125 mPhone.unregisterForNewRingingConnection(mHandler); 126 mPhone.unregisterForCallWaiting(mHandler); 127 mPhone.unregisterForUnknownConnection(mHandler); 128 } 129 } 130 131 /** 132 * Verifies the incoming call and triggers sending the incoming-call intent to Telecom. 133 * 134 * @param asyncResult The result object from the new ringing event. 135 */ handleNewRingingConnection(AsyncResult asyncResult)136 private void handleNewRingingConnection(AsyncResult asyncResult) { 137 Log.d(this, "handleNewRingingConnection"); 138 Connection connection = (Connection) asyncResult.result; 139 if (connection != null) { 140 Call call = connection.getCall(); 141 // Check if we have a pending number verification request. 142 if (connection.getAddress() != null) { 143 if (NumberVerificationManager.getInstance() 144 .checkIncomingCall(connection.getAddress())) { 145 // Disconnect the call if it matches, after a delay 146 mHandler.postDelayed(() -> { 147 try { 148 connection.hangup(); 149 } catch (CallStateException e) { 150 Log.i(this, "Remote end hung up call verification call"); 151 } 152 // TODO: use an app-supplied delay (needs new API), not to exceed the 153 // existing max. 154 }, MAX_NUMBER_VERIFICATION_HANGUP_DELAY_MILLIS); 155 return; 156 } 157 } 158 159 // Final verification of the ringing state before sending the intent to Telecom. 160 if (call != null && call.getState().isRinging()) { 161 sendIncomingCallIntent(connection); 162 } 163 } 164 } 165 handleCdmaCallWaiting(AsyncResult asyncResult)166 private void handleCdmaCallWaiting(AsyncResult asyncResult) { 167 Log.d(this, "handleCdmaCallWaiting"); 168 CdmaCallWaitingNotification ccwi = (CdmaCallWaitingNotification) asyncResult.result; 169 Call call = mPhone.getRingingCall(); 170 if (call.getState() == Call.State.WAITING) { 171 Connection connection = call.getLatestConnection(); 172 if (connection != null) { 173 String number = connection.getAddress(); 174 int presentation = connection.getNumberPresentation(); 175 176 if (presentation != PhoneConstants.PRESENTATION_ALLOWED 177 && presentation == ccwi.numberPresentation) { 178 // Presentation of number not allowed, but the presentation of the Connection 179 // and the call waiting presentation match. 180 Log.i(this, "handleCdmaCallWaiting: inform telecom of waiting call; " 181 + "presentation = %d", presentation); 182 sendIncomingCallIntent(connection); 183 } else if (!TextUtils.isEmpty(number) && Objects.equals(number, ccwi.number)) { 184 // Presentation of the number is allowed, so we ensure the number matches the 185 // one in the call waiting information. 186 Log.i(this, "handleCdmaCallWaiting: inform telecom of waiting call; " 187 + "number = %s", Rlog.pii(LOG_TAG, number)); 188 sendIncomingCallIntent(connection); 189 } else { 190 Log.w(this, "handleCdmaCallWaiting: presentation or number do not match, not" 191 + " informing telecom of call: %s", ccwi); 192 } 193 } 194 } 195 } 196 handleNewUnknownConnection(AsyncResult asyncResult)197 private void handleNewUnknownConnection(AsyncResult asyncResult) { 198 Log.i(this, "handleNewUnknownConnection"); 199 if (!(asyncResult.result instanceof Connection)) { 200 Log.w(this, "handleNewUnknownConnection called with non-Connection object"); 201 return; 202 } 203 Connection connection = (Connection) asyncResult.result; 204 if (connection != null) { 205 // Because there is a handler between telephony and here, it causes this action to be 206 // asynchronous which means that the call can switch to DISCONNECTED by the time it gets 207 // to this code. Check here to ensure we are not adding a disconnected or IDLE call. 208 Call.State state = connection.getState(); 209 if (state == Call.State.DISCONNECTED || state == Call.State.IDLE) { 210 Log.i(this, "Skipping new unknown connection because it is idle. " + connection); 211 return; 212 } 213 214 Call call = connection.getCall(); 215 if (call != null && call.getState().isAlive()) { 216 addNewUnknownCall(connection); 217 } else { 218 Log.i(this, "Skipping new unknown connection because its call is null or dead." 219 + " connection=" + connection); 220 } 221 } 222 } 223 addNewUnknownCall(Connection connection)224 private void addNewUnknownCall(Connection connection) { 225 Log.i(this, "addNewUnknownCall, connection is: %s", connection); 226 227 if (!maybeSwapAnyWithUnknownConnection(connection)) { 228 Log.i(this, "determined new connection is: %s", connection); 229 Bundle extras = new Bundle(); 230 if (connection.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED && 231 !TextUtils.isEmpty(connection.getAddress())) { 232 Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, connection.getAddress(), null); 233 extras.putParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE, uri); 234 } 235 // ImsExternalConnections are keyed by a unique mCallId; include this as an extra on 236 // the call to addNewUknownCall in Telecom. This way when the request comes back to the 237 // TelephonyConnectionService, we will be able to determine which unknown connection is 238 // being added. 239 if (connection instanceof ImsExternalConnection) { 240 ImsExternalConnection externalConnection = (ImsExternalConnection) connection; 241 extras.putInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, 242 externalConnection.getCallId()); 243 } 244 245 // Specifies the time the call was added. This is used by the dialer for analytics. 246 extras.putLong(TelecomManager.EXTRA_CALL_CREATED_TIME_MILLIS, 247 SystemClock.elapsedRealtime()); 248 249 PhoneAccountHandle handle = findCorrectPhoneAccountHandle(); 250 if (handle == null) { 251 try { 252 connection.hangup(); 253 } catch (CallStateException e) { 254 // connection already disconnected. Do nothing 255 } 256 } else { 257 TelecomManager tm = mPhone.getContext().getSystemService(TelecomManager.class); 258 tm.addNewUnknownCall(handle, extras); 259 } 260 } else { 261 Log.i(this, "swapped an old connection, new one is: %s", connection); 262 } 263 } 264 265 /** 266 * Sends the incoming call intent to telecom. 267 */ sendIncomingCallIntent(Connection connection)268 private void sendIncomingCallIntent(Connection connection) { 269 Bundle extras = new Bundle(); 270 if (connection.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED && 271 !TextUtils.isEmpty(connection.getAddress())) { 272 Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, connection.getAddress(), null); 273 extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri); 274 } 275 276 // Specifies the time the call was added. This is used by the dialer for analytics. 277 extras.putLong(TelecomManager.EXTRA_CALL_CREATED_TIME_MILLIS, 278 SystemClock.elapsedRealtime()); 279 280 if (connection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 281 if (((ImsPhoneConnection) connection).isRttEnabledForCall()) { 282 extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true); 283 } 284 } 285 PhoneAccountHandle handle = findCorrectPhoneAccountHandle(); 286 if (handle == null) { 287 try { 288 connection.hangup(); 289 } catch (CallStateException e) { 290 // connection already disconnected. Do nothing 291 } 292 } else { 293 TelecomManager tm = mPhone.getContext().getSystemService(TelecomManager.class); 294 if (connection.isMultiparty()) { 295 tm.addNewIncomingConference(handle, extras); 296 } else { 297 tm.addNewIncomingCall(handle, extras); 298 } 299 } 300 } 301 302 /** 303 * Returns the PhoneAccount associated with this {@code PstnIncomingCallNotifier}'s phone. On a 304 * device with No SIM or in airplane mode, it can return an Emergency-only PhoneAccount. If no 305 * PhoneAccount is registered with telecom, return null. 306 * @return A valid PhoneAccountHandle that is registered to Telecom or null if there is none 307 * registered. 308 */ findCorrectPhoneAccountHandle()309 private PhoneAccountHandle findCorrectPhoneAccountHandle() { 310 TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry.getInstance(null); 311 // Check to see if a the SIM PhoneAccountHandle Exists for the Call. 312 PhoneAccountHandle handle = PhoneUtils.makePstnPhoneAccountHandle(mPhone); 313 if (telecomAccountRegistry.hasAccountEntryForPhoneAccount(handle)) { 314 return handle; 315 } 316 // The PhoneAccountHandle does not match any PhoneAccount registered in Telecom. 317 // This is only known to happen if there is no SIM card in the device and the device 318 // receives an MT call while in ECM. Use the Emergency PhoneAccount to receive the account 319 // if it exists. 320 PhoneAccountHandle emergencyHandle = 321 PhoneUtils.makePstnPhoneAccountHandleWithPrefix(mPhone, "", true); 322 if(telecomAccountRegistry.hasAccountEntryForPhoneAccount(emergencyHandle)) { 323 Log.i(this, "Receiving MT call in ECM. Using Emergency PhoneAccount Instead."); 324 return emergencyHandle; 325 } 326 Log.w(this, "PhoneAccount not found."); 327 return null; 328 } 329 330 /** 331 * Define cait.Connection := com.android.internal.telephony.Connection 332 * 333 * Given a previously unknown cait.Connection, check to see if it's likely a replacement for 334 * another cait.Connnection we already know about. If it is, then we silently swap it out 335 * underneath within the relevant {@link TelephonyConnection}, using 336 * {@link TelephonyConnection#setOriginalConnection(Connection)}, and return {@code true}. 337 * Otherwise, we return {@code false}. 338 */ maybeSwapAnyWithUnknownConnection(Connection unknown)339 private boolean maybeSwapAnyWithUnknownConnection(Connection unknown) { 340 if (!unknown.isIncoming()) { 341 TelecomAccountRegistry registry = TelecomAccountRegistry.getInstance(null); 342 if (registry != null) { 343 TelephonyConnectionService service = registry.getTelephonyConnectionService(); 344 if (service != null) { 345 for (android.telecom.Connection telephonyConnection : service 346 .getAllConnections()) { 347 if (telephonyConnection instanceof TelephonyConnection) { 348 if (maybeSwapWithUnknownConnection( 349 (TelephonyConnection) telephonyConnection, 350 unknown)) { 351 return true; 352 } 353 } 354 } 355 } 356 } 357 } 358 return false; 359 } 360 maybeSwapWithUnknownConnection( TelephonyConnection telephonyConnection, Connection unknown)361 private boolean maybeSwapWithUnknownConnection( 362 TelephonyConnection telephonyConnection, 363 Connection unknown) { 364 Connection original = telephonyConnection.getOriginalConnection(); 365 if (original != null && !original.isIncoming() 366 && Objects.equals(original.getAddress(), unknown.getAddress())) { 367 // If the new unknown connection is an external connection, don't swap one with an 368 // actual connection. This means a call got pulled away. We want the actual connection 369 // to disconnect. 370 if (unknown instanceof ImsExternalConnection && 371 !(telephonyConnection 372 .getOriginalConnection() instanceof ImsExternalConnection)) { 373 Log.v(this, "maybeSwapWithUnknownConnection - not swapping regular connection " + 374 "with external connection."); 375 return false; 376 } 377 378 telephonyConnection.setOriginalConnection(unknown); 379 380 // Do not call hang up if the original connection is an ImsExternalConnection, it is 381 // not supported. 382 if (original instanceof ImsExternalConnection) { 383 return true; 384 } 385 // If the connection we're replacing was a GSM or CDMA connection, call upon the call 386 // tracker to perform cleanup of calls. This ensures that we don't end up with a 387 // call stuck in the call tracker preventing other calls from being placed. 388 if (original.getCall() != null && original.getCall().getPhone() != null && 389 original.getCall().getPhone() instanceof GsmCdmaPhone) { 390 391 GsmCdmaPhone phone = (GsmCdmaPhone) original.getCall().getPhone(); 392 phone.getCallTracker().cleanupCalls(); 393 Log.i(this, "maybeSwapWithUnknownConnection - Invoking call tracker cleanup " 394 + "for connection: " + original); 395 } 396 return true; 397 } 398 return false; 399 } 400 } 401