1 /* 2 * Copyright (C) 2022 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.domainselection; 18 19 import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING; 20 21 import android.annotation.NonNull; 22 import android.content.Context; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.os.PersistableBundle; 26 import android.telecom.TelecomManager; 27 import android.telephony.Annotation.DisconnectCauses; 28 import android.telephony.CarrierConfigManager; 29 import android.telephony.DisconnectCause; 30 import android.telephony.DomainSelectionService.SelectionAttributes; 31 import android.telephony.NetworkRegistrationInfo; 32 import android.telephony.PhoneNumberUtils; 33 import android.telephony.ServiceState; 34 import android.telephony.SubscriptionManager; 35 import android.telephony.TransportSelectorCallback; 36 import android.telephony.ims.ImsReasonInfo; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.telephony.CallFailCause; 40 41 /** 42 * Implements domain selector for outgoing non-emergency calls. 43 */ 44 public class NormalCallDomainSelector extends DomainSelectorBase implements 45 ImsStateTracker.ImsStateListener, ImsStateTracker.ServiceStateListener { 46 47 private static final String LOG_TAG = "NCDS"; 48 49 // Wait-time for IMS state change callback. 50 @VisibleForTesting 51 protected static final int WAIT_FOR_IMS_STATE_TIMEOUT_MS = 3000; // 3 seconds 52 53 @VisibleForTesting 54 protected static final int MSG_WAIT_FOR_IMS_STATE_TIMEOUT = 11; 55 56 @VisibleForTesting 57 protected enum SelectorState { 58 ACTIVE, 59 INACTIVE, 60 DESTROYED 61 }; 62 63 protected SelectorState mSelectorState = SelectorState.INACTIVE; 64 protected ServiceState mServiceState; 65 private boolean mImsRegStateReceived; 66 private boolean mMmTelCapabilitiesReceived; 67 private boolean mReselectDomain; 68 NormalCallDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker, @NonNull DestroyListener destroyListener)69 public NormalCallDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper, 70 @NonNull ImsStateTracker imsStateTracker, 71 @NonNull DestroyListener destroyListener) { 72 super(context, slotId, subId, looper, imsStateTracker, destroyListener, LOG_TAG); 73 74 if (SubscriptionManager.isValidSubscriptionId(subId)) { 75 logd("Subscribing to state callbacks. Subid:" + subId); 76 mImsStateTracker.addServiceStateListener(this); 77 mImsStateTracker.addImsStateListener(this); 78 79 } else { 80 loge("Invalid Subscription. Subid:" + subId); 81 } 82 } 83 84 @Override handleMessage(Message message)85 public void handleMessage(Message message) { 86 switch (message.what) { 87 88 case MSG_WAIT_FOR_IMS_STATE_TIMEOUT: { 89 loge("ImsStateTimeout. ImsState callback not received"); 90 if (mSelectorState != SelectorState.ACTIVE) { 91 return; 92 } 93 94 if (!mImsRegStateReceived) { 95 onImsRegistrationStateChanged(); 96 } 97 98 if (!mMmTelCapabilitiesReceived) { 99 onImsMmTelCapabilitiesChanged(); 100 } 101 } 102 break; 103 104 default: { 105 super.handleMessage(message); 106 } 107 break; 108 } 109 } 110 111 @Override selectDomain(SelectionAttributes attributes, TransportSelectorCallback callback)112 public void selectDomain(SelectionAttributes attributes, TransportSelectorCallback callback) { 113 mSelectionAttributes = attributes; 114 mTransportSelectorCallback = callback; 115 mSelectorState = SelectorState.ACTIVE; 116 117 if (callback == null) { 118 mSelectorState = SelectorState.INACTIVE; 119 loge("Invalid params: TransportSelectorCallback is null"); 120 return; 121 } 122 123 if (attributes == null) { 124 loge("Invalid params: SelectionAttributes are null"); 125 notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 126 return; 127 } 128 129 int subId = attributes.getSubscriptionId(); 130 boolean validSubscriptionId = SubscriptionManager.isValidSubscriptionId(subId); 131 if (attributes.getSelectorType() != SELECTOR_TYPE_CALLING || attributes.isEmergency() 132 || !validSubscriptionId) { 133 loge("Domain Selection stopped. SelectorType:" + attributes.getSelectorType() 134 + ", isEmergency:" + attributes.isEmergency() 135 + ", ValidSubscriptionId:" + validSubscriptionId); 136 137 notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 138 return; 139 } 140 141 if (subId == getSubId()) { 142 logd("NormalCallDomainSelection triggered. Sub-id:" + subId); 143 sendEmptyMessageDelayed(MSG_WAIT_FOR_IMS_STATE_TIMEOUT, WAIT_FOR_IMS_STATE_TIMEOUT_MS); 144 post(() -> selectDomain()); 145 } else { 146 mSelectorState = SelectorState.INACTIVE; 147 loge("Subscription-ids doesn't match. This instance is associated with sub-id:" 148 + getSubId() + ", requested sub-id:" + subId); 149 // TODO: Throw anomaly here. This condition should never occur. 150 } 151 } 152 153 @Override reselectDomain(SelectionAttributes attributes)154 public void reselectDomain(SelectionAttributes attributes) { 155 logd("reselectDomain called"); 156 mReselectDomain = true; 157 selectDomain(attributes, mTransportSelectorCallback); 158 } 159 160 @Override finishSelection()161 public synchronized void finishSelection() { 162 logd("finishSelection"); 163 if (mSelectorState == SelectorState.ACTIVE) { 164 // This is cancel selection case. 165 cancelSelection(); 166 return; 167 } 168 169 if (mSelectorState != SelectorState.DESTROYED) { 170 mImsStateTracker.removeServiceStateListener(this); 171 mImsStateTracker.removeImsStateListener(this); 172 mSelectionAttributes = null; 173 mTransportSelectorCallback = null; 174 destroy(); 175 } 176 } 177 178 @Override destroy()179 public void destroy() { 180 logd("destroy"); 181 switch (mSelectorState) { 182 case INACTIVE: 183 mSelectorState = SelectorState.DESTROYED; 184 super.destroy(); 185 break; 186 187 case ACTIVE: 188 loge("destroy is called when selector state is in ACTIVE state"); 189 cancelSelection(); 190 break; 191 192 case DESTROYED: 193 super.destroy(); 194 break; 195 } 196 } 197 cancelSelection()198 public void cancelSelection() { 199 logd("cancelSelection"); 200 mSelectorState = SelectorState.INACTIVE; 201 mReselectDomain = false; 202 if (mTransportSelectorCallback != null) { 203 mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_CANCELED); 204 } 205 finishSelection(); 206 } 207 208 @Override onImsRegistrationStateChanged()209 public void onImsRegistrationStateChanged() { 210 logd("onImsRegistrationStateChanged. IsImsRegistered: " 211 + mImsStateTracker.isImsRegistered()); 212 mImsRegStateReceived = true; 213 selectDomain(); 214 } 215 216 @Override onImsMmTelCapabilitiesChanged()217 public void onImsMmTelCapabilitiesChanged() { 218 logd("onImsMmTelCapabilitiesChanged. ImsVoiceCap: " + mImsStateTracker.isImsVoiceCapable() 219 + " ImsVideoCap: " + mImsStateTracker.isImsVideoCapable()); 220 mMmTelCapabilitiesReceived = true; 221 selectDomain(); 222 } 223 224 @Override onImsMmTelFeatureAvailableChanged()225 public void onImsMmTelFeatureAvailableChanged() { 226 logd("onImsMmTelFeatureAvailableChanged"); 227 selectDomain(); 228 } 229 230 @Override onServiceStateUpdated(ServiceState serviceState)231 public void onServiceStateUpdated(ServiceState serviceState) { 232 logd("onServiceStateUpdated"); 233 mServiceState = serviceState; 234 selectDomain(); 235 } 236 notifyPsSelected()237 private void notifyPsSelected() { 238 logd("notifyPsSelected"); 239 mSelectorState = SelectorState.INACTIVE; 240 if (mImsStateTracker.isImsRegisteredOverWlan()) { 241 logd("WLAN selected"); 242 mTransportSelectorCallback.onWlanSelected(false); 243 } else { 244 if (mWwanSelectorCallback == null) { 245 mTransportSelectorCallback.onWwanSelected((callback) -> { 246 mWwanSelectorCallback = callback; 247 notifyPsSelectedInternal(); 248 }); 249 } else { 250 notifyPsSelectedInternal(); 251 } 252 } 253 } 254 notifyPsSelectedInternal()255 private void notifyPsSelectedInternal() { 256 if (mWwanSelectorCallback != null) { 257 logd("notifyPsSelected - onWwanSelected"); 258 mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, false); 259 } else { 260 loge("wwanSelectorCallback is null"); 261 mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 262 } 263 } 264 notifyCsSelected()265 private void notifyCsSelected() { 266 logd("notifyCsSelected"); 267 mSelectorState = SelectorState.INACTIVE; 268 if (mWwanSelectorCallback == null) { 269 mTransportSelectorCallback.onWwanSelected((callback) -> { 270 mWwanSelectorCallback = callback; 271 notifyCsSelectedInternal(); 272 }); 273 } else { 274 notifyCsSelectedInternal(); 275 } 276 } 277 notifyCsSelectedInternal()278 private void notifyCsSelectedInternal() { 279 if (mWwanSelectorCallback != null) { 280 logd("wwanSelectorCallback -> onDomainSelected(DOMAIN_CS)"); 281 mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false); 282 } else { 283 loge("wwanSelectorCallback is null"); 284 mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 285 } 286 } 287 notifySelectionTerminated(@isconnectCauses int cause)288 private void notifySelectionTerminated(@DisconnectCauses int cause) { 289 mSelectorState = SelectorState.INACTIVE; 290 if (mTransportSelectorCallback != null) { 291 mTransportSelectorCallback.onSelectionTerminated(cause); 292 finishSelection(); 293 } 294 } 295 isOutOfService()296 private boolean isOutOfService() { 297 return (mServiceState.getState() == ServiceState.STATE_OUT_OF_SERVICE 298 || mServiceState.getState() == ServiceState.STATE_POWER_OFF 299 || mServiceState.getState() == ServiceState.STATE_EMERGENCY_ONLY); 300 } 301 isWpsCallSupportedByIms()302 private boolean isWpsCallSupportedByIms() { 303 CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class); 304 305 PersistableBundle config = null; 306 if (configManager != null) { 307 config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(), 308 new String[] {CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL}); 309 } 310 311 return (config != null) 312 ? config.getBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL) : false; 313 } 314 handleWpsCall()315 private void handleWpsCall() { 316 if (isWpsCallSupportedByIms()) { 317 logd("WPS call placed over PS"); 318 notifyPsSelected(); 319 } else { 320 if (isOutOfService()) { 321 loge("Cannot place call in current ServiceState: " + mServiceState.getState()); 322 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE); 323 } else { 324 logd("WPS call placed over CS"); 325 notifyCsSelected(); 326 } 327 } 328 } 329 isTtySupportedByIms()330 private boolean isTtySupportedByIms() { 331 CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class); 332 333 PersistableBundle config = null; 334 if (configManager != null) { 335 config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(), 336 new String[] {CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL}); 337 } 338 339 return (config != null) 340 && config.getBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL); 341 } 342 isTtyModeEnabled()343 private boolean isTtyModeEnabled() { 344 TelecomManager tm = mContext.getSystemService(TelecomManager.class); 345 if (tm == null) { 346 loge("isTtyModeEnabled: telecom not available"); 347 return false; 348 } 349 return tm.getCurrentTtyMode() != TelecomManager.TTY_MODE_OFF; 350 } 351 selectDomain()352 private synchronized void selectDomain() { 353 if (mSelectorState != SelectorState.ACTIVE || mSelectionAttributes == null 354 || mTransportSelectorCallback == null) { 355 mSelectorState = SelectorState.INACTIVE; 356 logd("Domain Selection is stopped."); 357 return; 358 } 359 360 if (mServiceState == null) { 361 logd("Waiting for ServiceState callback."); 362 return; 363 } 364 365 // Check if this is a re-dial scenario 366 ImsReasonInfo imsReasonInfo = mSelectionAttributes.getPsDisconnectCause(); 367 if (mReselectDomain) { 368 mReselectDomain = false; 369 370 // Out of service 371 if (isOutOfService()) { 372 loge("Cannot place call in current ServiceState: " + mServiceState.getState()); 373 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE); 374 375 return; 376 } 377 378 // IMS -> CS 379 if (imsReasonInfo != null) { 380 logd("PsDisconnectCause:" + imsReasonInfo.getCode()); 381 if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) { 382 logd("Redialing over CS"); 383 notifyCsSelected(); 384 } else { 385 // Not a valid redial 386 logd("Redialing cancelled."); 387 notifySelectionTerminated(DisconnectCause.NOT_VALID); 388 } 389 return; 390 } 391 392 // CS -> IMS 393 int csDisconnectCause = mSelectionAttributes.getCsDisconnectCause(); 394 switch (csDisconnectCause) { 395 case CallFailCause.EMC_REDIAL_ON_IMS: 396 case CallFailCause.EMC_REDIAL_ON_VOWIFI: 397 // Check IMS registration state. 398 if (mImsStateTracker.isImsRegistered()) { 399 logd("IMS is registered"); 400 notifyPsSelected(); 401 return; 402 } else { 403 logd("IMS is NOT registered"); 404 } 405 } 406 407 // Not a valid redial 408 logd("Redialing cancelled."); 409 notifySelectionTerminated(DisconnectCause.NOT_VALID); 410 return; 411 } 412 413 if (!mImsStateTracker.isMmTelFeatureAvailable()) { 414 logd("MmTelFeatureAvailable unavailable"); 415 if (isOutOfService()) { 416 loge("Cannot place call in current ServiceState: " + mServiceState.getState()); 417 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE); 418 } else { 419 notifyCsSelected(); 420 } 421 return; 422 } 423 424 if (!mImsRegStateReceived || !mMmTelCapabilitiesReceived) { 425 loge("Waiting for ImsState and MmTelCapabilities callbacks"); 426 return; 427 } 428 429 if (hasMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT)) { 430 removeMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT); 431 } 432 433 // Check IMS registration state. 434 if (!mImsStateTracker.isImsRegistered()) { 435 logd("IMS is NOT registered"); 436 if (isOutOfService()) { 437 loge("Cannot place call in current ServiceState: " + mServiceState.getState()); 438 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE); 439 } else { 440 notifyCsSelected(); 441 } 442 return; 443 } 444 445 // Check TTY 446 if (isTtyModeEnabled() && !isTtySupportedByIms()) { 447 if (isOutOfService()) { 448 loge("Cannot place call in current ServiceState: " + mServiceState.getState()); 449 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE); 450 } else { 451 notifyCsSelected(); 452 } 453 return; 454 } 455 456 // Handle video call. 457 if (mSelectionAttributes.isVideoCall()) { 458 logd("It's a video call"); 459 if (mImsStateTracker.isImsVideoCapable()) { 460 logd("IMS is video capable"); 461 notifyPsSelected(); 462 } else { 463 logd("IMS is not video capable. Ending the call"); 464 notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 465 } 466 return; 467 } 468 469 // Handle voice call. 470 if (mImsStateTracker.isImsVoiceCapable()) { 471 logd("IMS is voice capable"); 472 String number = mSelectionAttributes.getAddress().getSchemeSpecificPart(); 473 if (PhoneNumberUtils.isWpsCallNumber(number)) { 474 handleWpsCall(); 475 } else { 476 notifyPsSelected(); 477 } 478 } else { 479 logd("IMS is not voice capable"); 480 // Voice call CS fallback 481 if (isOutOfService()) { 482 loge("Cannot place call in current ServiceState: " + mServiceState.getState()); 483 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE); 484 } else { 485 notifyCsSelected(); 486 } 487 } 488 } 489 490 @VisibleForTesting getSelectorState()491 protected SelectorState getSelectorState() { 492 return mSelectorState; 493 } 494 } 495