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 if (!mReselectDomain) { 144 sendEmptyMessageDelayed(MSG_WAIT_FOR_IMS_STATE_TIMEOUT, 145 WAIT_FOR_IMS_STATE_TIMEOUT_MS); 146 } 147 post(() -> selectDomain()); 148 } else { 149 mSelectorState = SelectorState.INACTIVE; 150 loge("Subscription-ids doesn't match. This instance is associated with sub-id:" 151 + getSubId() + ", requested sub-id:" + subId); 152 // TODO: Throw anomaly here. This condition should never occur. 153 } 154 } 155 156 @Override reselectDomain(SelectionAttributes attributes)157 public void reselectDomain(SelectionAttributes attributes) { 158 logd("reselectDomain called"); 159 mReselectDomain = true; 160 selectDomain(attributes, mTransportSelectorCallback); 161 } 162 163 @Override finishSelection()164 public synchronized void finishSelection() { 165 logd("finishSelection"); 166 if (mSelectorState == SelectorState.ACTIVE) { 167 // This is cancel selection case. 168 cancelSelection(); 169 return; 170 } 171 172 if (mSelectorState != SelectorState.DESTROYED) { 173 mImsStateTracker.removeServiceStateListener(this); 174 mImsStateTracker.removeImsStateListener(this); 175 mSelectionAttributes = null; 176 mTransportSelectorCallback = null; 177 destroy(); 178 } 179 } 180 181 @Override destroy()182 public void destroy() { 183 logd("destroy"); 184 switch (mSelectorState) { 185 case INACTIVE: 186 mSelectorState = SelectorState.DESTROYED; 187 super.destroy(); 188 break; 189 190 case ACTIVE: 191 loge("destroy is called when selector state is in ACTIVE state"); 192 cancelSelection(); 193 break; 194 195 case DESTROYED: 196 super.destroy(); 197 break; 198 } 199 } 200 cancelSelection()201 public void cancelSelection() { 202 logd("cancelSelection"); 203 mSelectorState = SelectorState.INACTIVE; 204 mReselectDomain = false; 205 if (mTransportSelectorCallback != null) { 206 mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_CANCELED); 207 } 208 finishSelection(); 209 } 210 211 @Override onImsRegistrationStateChanged()212 public void onImsRegistrationStateChanged() { 213 logd("onImsRegistrationStateChanged. IsImsRegistered: " 214 + mImsStateTracker.isImsRegistered()); 215 mImsRegStateReceived = true; 216 selectDomain(); 217 } 218 219 @Override onImsMmTelCapabilitiesChanged()220 public void onImsMmTelCapabilitiesChanged() { 221 logd("onImsMmTelCapabilitiesChanged. ImsVoiceCap: " + mImsStateTracker.isImsVoiceCapable() 222 + " ImsVideoCap: " + mImsStateTracker.isImsVideoCapable()); 223 mMmTelCapabilitiesReceived = true; 224 selectDomain(); 225 } 226 227 @Override onImsMmTelFeatureAvailableChanged()228 public void onImsMmTelFeatureAvailableChanged() { 229 logd("onImsMmTelFeatureAvailableChanged"); 230 selectDomain(); 231 } 232 233 @Override onServiceStateUpdated(ServiceState serviceState)234 public void onServiceStateUpdated(ServiceState serviceState) { 235 logd("onServiceStateUpdated:" + serviceState.getState()); 236 mServiceState = serviceState; 237 selectDomain(); 238 } 239 notifyPsSelected()240 private void notifyPsSelected() { 241 logd("notifyPsSelected"); 242 mSelectorState = SelectorState.INACTIVE; 243 if (mImsStateTracker.isImsRegisteredOverWlan()) { 244 logd("WLAN selected"); 245 mTransportSelectorCallback.onWlanSelected(false); 246 } else { 247 if (mWwanSelectorCallback == null) { 248 mTransportSelectorCallback.onWwanSelected((callback) -> { 249 mWwanSelectorCallback = callback; 250 notifyPsSelectedInternal(); 251 }); 252 } else { 253 notifyPsSelectedInternal(); 254 } 255 } 256 } 257 notifyPsSelectedInternal()258 private void notifyPsSelectedInternal() { 259 if (mWwanSelectorCallback != null) { 260 logd("notifyPsSelected - onWwanSelected"); 261 mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, false); 262 } else { 263 loge("wwanSelectorCallback is null"); 264 mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 265 } 266 } 267 notifyCsSelected(boolean checkServiceState)268 private void notifyCsSelected(boolean checkServiceState) { 269 if (checkServiceState && isOutOfService()) { 270 loge("Cannot place call in current ServiceState: " + mServiceState.getState()); 271 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE); 272 return; 273 } 274 275 logd("notifyCsSelected"); 276 mSelectorState = SelectorState.INACTIVE; 277 if (mWwanSelectorCallback == null) { 278 mTransportSelectorCallback.onWwanSelected((callback) -> { 279 mWwanSelectorCallback = callback; 280 notifyCsSelectedInternal(); 281 }); 282 } else { 283 notifyCsSelectedInternal(); 284 } 285 } 286 notifyCsSelectedInternal()287 private void notifyCsSelectedInternal() { 288 if (mWwanSelectorCallback != null) { 289 logd("wwanSelectorCallback -> onDomainSelected(DOMAIN_CS)"); 290 mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false); 291 } else { 292 loge("wwanSelectorCallback is null"); 293 mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 294 } 295 } 296 notifySelectionTerminated(@isconnectCauses int cause)297 private void notifySelectionTerminated(@DisconnectCauses int cause) { 298 mSelectorState = SelectorState.INACTIVE; 299 if (mTransportSelectorCallback != null) { 300 mTransportSelectorCallback.onSelectionTerminated(cause); 301 finishSelection(); 302 } 303 } 304 isOutOfService()305 private boolean isOutOfService() { 306 return (mServiceState.getState() == ServiceState.STATE_OUT_OF_SERVICE 307 || mServiceState.getState() == ServiceState.STATE_POWER_OFF 308 || mServiceState.getState() == ServiceState.STATE_EMERGENCY_ONLY); 309 } 310 isWpsCallSupportedByIms()311 private boolean isWpsCallSupportedByIms() { 312 CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class); 313 314 PersistableBundle config = null; 315 if (configManager != null) { 316 config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(), 317 new String[] {CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL}); 318 } 319 320 return (config != null) 321 ? config.getBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL) : false; 322 } 323 handleWpsCall()324 private void handleWpsCall() { 325 if (isWpsCallSupportedByIms()) { 326 logd("WPS call placed over PS"); 327 notifyPsSelected(); 328 } else { 329 logd("WPS call placed over CS"); 330 notifyCsSelected(true); 331 } 332 } 333 handleReselectDomain(ImsReasonInfo imsReasonInfo)334 private void handleReselectDomain(ImsReasonInfo imsReasonInfo) { 335 mReselectDomain = false; 336 337 // IMS -> CS 338 if (imsReasonInfo != null) { 339 logd("PsDisconnectCause:" + imsReasonInfo.getCode()); 340 if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) { 341 logd("Redialing over CS"); 342 // Don't check for ServiceState when CODE_LOCAL_CALL_CS_RETRY_REQUIRED is received 343 // as requested here b/380412925. 344 notifyCsSelected(false); 345 } else { 346 // Not a valid redial 347 logd("Redialing cancelled."); 348 notifySelectionTerminated(DisconnectCause.NOT_VALID); 349 } 350 return; 351 } 352 353 // CS -> IMS 354 int csDisconnectCause = mSelectionAttributes.getCsDisconnectCause(); 355 if (csDisconnectCause == CallFailCause.EMC_REDIAL_ON_IMS 356 || csDisconnectCause == CallFailCause.EMC_REDIAL_ON_VOWIFI) { 357 // Check IMS registration state. 358 if (mImsStateTracker.isImsRegistered()) { 359 logd("IMS is registered"); 360 notifyPsSelected(); 361 return; 362 } 363 364 logd("IMS is NOT registered"); 365 } 366 367 // Not a valid redial 368 logd("Redialing cancelled."); 369 notifySelectionTerminated(DisconnectCause.NOT_VALID); 370 } 371 isTtySupportedByIms()372 private boolean isTtySupportedByIms() { 373 CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class); 374 375 PersistableBundle config = null; 376 if (configManager != null) { 377 config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(), 378 new String[] {CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL}); 379 } 380 381 return (config != null) 382 && config.getBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL); 383 } 384 isTtyModeEnabled()385 private boolean isTtyModeEnabled() { 386 TelecomManager tm = mContext.getSystemService(TelecomManager.class); 387 if (tm == null) { 388 loge("isTtyModeEnabled: telecom not available"); 389 return false; 390 } 391 return tm.getCurrentTtyMode() != TelecomManager.TTY_MODE_OFF; 392 } 393 selectDomain()394 private synchronized void selectDomain() { 395 if (mSelectorState != SelectorState.ACTIVE || mSelectionAttributes == null 396 || mTransportSelectorCallback == null) { 397 mSelectorState = SelectorState.INACTIVE; 398 logd("Domain Selection is stopped."); 399 return; 400 } 401 402 if (mServiceState == null) { 403 logd("Waiting for ServiceState callback."); 404 return; 405 } 406 407 // Check if this is a re-dial scenario 408 if (mReselectDomain) { 409 handleReselectDomain(mSelectionAttributes.getPsDisconnectCause()); 410 return; 411 } 412 413 if (!mImsStateTracker.isMmTelFeatureAvailable()) { 414 logd("MmTelFeatureAvailable unavailable"); 415 notifyCsSelected(true); 416 return; 417 } 418 419 if (!mImsRegStateReceived || !mMmTelCapabilitiesReceived) { 420 loge("Waiting for ImsState and MmTelCapabilities callbacks"); 421 return; 422 } 423 424 if (hasMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT)) { 425 removeMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT); 426 } 427 428 // Check IMS registration state. 429 if (!mImsStateTracker.isImsRegistered()) { 430 logd("IMS is NOT registered"); 431 notifyCsSelected(true); 432 return; 433 } 434 435 // Check TTY 436 if (isTtyModeEnabled() && !isTtySupportedByIms()) { 437 notifyCsSelected(true); 438 return; 439 } 440 441 // Handle video call. 442 if (mSelectionAttributes.isVideoCall()) { 443 logd("It's a video call"); 444 if (mImsStateTracker.isImsVideoCapable()) { 445 logd("IMS is video capable"); 446 notifyPsSelected(); 447 } else { 448 logd("IMS is not video capable. Ending the call"); 449 notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 450 } 451 return; 452 } 453 454 // Handle voice call. 455 if (mImsStateTracker.isImsVoiceCapable()) { 456 logd("IMS is voice capable"); 457 String number = mSelectionAttributes.getAddress().getSchemeSpecificPart(); 458 if (PhoneNumberUtils.isWpsCallNumber(number)) { 459 handleWpsCall(); 460 } else { 461 notifyPsSelected(); 462 } 463 } else { 464 logd("IMS is not voice capable"); 465 // Voice call CS fallback 466 notifyCsSelected(true); 467 } 468 } 469 470 @VisibleForTesting getSelectorState()471 protected SelectorState getSelectorState() { 472 return mSelectorState; 473 } 474 } 475