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