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 if (isOutOfService()) { 267 loge("Cannot place call in current ServiceState: " + mServiceState.getState()); 268 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE); 269 return; 270 } 271 272 logd("notifyCsSelected"); 273 mSelectorState = SelectorState.INACTIVE; 274 if (mWwanSelectorCallback == null) { 275 mTransportSelectorCallback.onWwanSelected((callback) -> { 276 mWwanSelectorCallback = callback; 277 notifyCsSelectedInternal(); 278 }); 279 } else { 280 notifyCsSelectedInternal(); 281 } 282 } 283 notifyCsSelectedInternal()284 private void notifyCsSelectedInternal() { 285 if (mWwanSelectorCallback != null) { 286 logd("wwanSelectorCallback -> onDomainSelected(DOMAIN_CS)"); 287 mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false); 288 } else { 289 loge("wwanSelectorCallback is null"); 290 mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 291 } 292 } 293 notifySelectionTerminated(@isconnectCauses int cause)294 private void notifySelectionTerminated(@DisconnectCauses int cause) { 295 mSelectorState = SelectorState.INACTIVE; 296 if (mTransportSelectorCallback != null) { 297 mTransportSelectorCallback.onSelectionTerminated(cause); 298 finishSelection(); 299 } 300 } 301 isOutOfService()302 private boolean isOutOfService() { 303 return (mServiceState.getState() == ServiceState.STATE_OUT_OF_SERVICE 304 || mServiceState.getState() == ServiceState.STATE_POWER_OFF 305 || mServiceState.getState() == ServiceState.STATE_EMERGENCY_ONLY); 306 } 307 isWpsCallSupportedByIms()308 private boolean isWpsCallSupportedByIms() { 309 CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class); 310 311 PersistableBundle config = null; 312 if (configManager != null) { 313 config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(), 314 new String[] {CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL}); 315 } 316 317 return (config != null) 318 ? config.getBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL) : false; 319 } 320 handleWpsCall()321 private void handleWpsCall() { 322 if (isWpsCallSupportedByIms()) { 323 logd("WPS call placed over PS"); 324 notifyPsSelected(); 325 } else { 326 logd("WPS call placed over CS"); 327 notifyCsSelected(); 328 } 329 } 330 isTtySupportedByIms()331 private boolean isTtySupportedByIms() { 332 CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class); 333 334 PersistableBundle config = null; 335 if (configManager != null) { 336 config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(), 337 new String[] {CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL}); 338 } 339 340 return (config != null) 341 && config.getBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL); 342 } 343 isTtyModeEnabled()344 private boolean isTtyModeEnabled() { 345 TelecomManager tm = mContext.getSystemService(TelecomManager.class); 346 if (tm == null) { 347 loge("isTtyModeEnabled: telecom not available"); 348 return false; 349 } 350 return tm.getCurrentTtyMode() != TelecomManager.TTY_MODE_OFF; 351 } 352 selectDomain()353 private synchronized void selectDomain() { 354 if (mSelectorState != SelectorState.ACTIVE || mSelectionAttributes == null 355 || mTransportSelectorCallback == null) { 356 mSelectorState = SelectorState.INACTIVE; 357 logd("Domain Selection is stopped."); 358 return; 359 } 360 361 if (mServiceState == null) { 362 logd("Waiting for ServiceState callback."); 363 return; 364 } 365 366 // Check if this is a re-dial scenario 367 ImsReasonInfo imsReasonInfo = mSelectionAttributes.getPsDisconnectCause(); 368 if (mReselectDomain) { 369 mReselectDomain = false; 370 371 // IMS -> CS 372 if (imsReasonInfo != null) { 373 logd("PsDisconnectCause:" + imsReasonInfo.getCode()); 374 if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) { 375 logd("Redialing over CS"); 376 notifyCsSelected(); 377 } else { 378 // Not a valid redial 379 logd("Redialing cancelled."); 380 notifySelectionTerminated(DisconnectCause.NOT_VALID); 381 } 382 return; 383 } 384 385 // CS -> IMS 386 int csDisconnectCause = mSelectionAttributes.getCsDisconnectCause(); 387 switch (csDisconnectCause) { 388 case CallFailCause.EMC_REDIAL_ON_IMS: 389 case CallFailCause.EMC_REDIAL_ON_VOWIFI: 390 // Check IMS registration state. 391 if (mImsStateTracker.isImsRegistered()) { 392 logd("IMS is registered"); 393 notifyPsSelected(); 394 return; 395 } else { 396 logd("IMS is NOT registered"); 397 } 398 } 399 400 // Not a valid redial 401 logd("Redialing cancelled."); 402 notifySelectionTerminated(DisconnectCause.NOT_VALID); 403 return; 404 } 405 406 if (!mImsStateTracker.isMmTelFeatureAvailable()) { 407 logd("MmTelFeatureAvailable unavailable"); 408 notifyCsSelected(); 409 return; 410 } 411 412 if (!mImsRegStateReceived || !mMmTelCapabilitiesReceived) { 413 loge("Waiting for ImsState and MmTelCapabilities callbacks"); 414 return; 415 } 416 417 if (hasMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT)) { 418 removeMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT); 419 } 420 421 // Check IMS registration state. 422 if (!mImsStateTracker.isImsRegistered()) { 423 logd("IMS is NOT registered"); 424 notifyCsSelected(); 425 return; 426 } 427 428 // Check TTY 429 if (isTtyModeEnabled() && !isTtySupportedByIms()) { 430 notifyCsSelected(); 431 return; 432 } 433 434 // Handle video call. 435 if (mSelectionAttributes.isVideoCall()) { 436 logd("It's a video call"); 437 if (mImsStateTracker.isImsVideoCapable()) { 438 logd("IMS is video capable"); 439 notifyPsSelected(); 440 } else { 441 logd("IMS is not video capable. Ending the call"); 442 notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE); 443 } 444 return; 445 } 446 447 // Handle voice call. 448 if (mImsStateTracker.isImsVoiceCapable()) { 449 logd("IMS is voice capable"); 450 String number = mSelectionAttributes.getAddress().getSchemeSpecificPart(); 451 if (PhoneNumberUtils.isWpsCallNumber(number)) { 452 handleWpsCall(); 453 } else { 454 notifyPsSelected(); 455 } 456 } else { 457 logd("IMS is not voice capable"); 458 // Voice call CS fallback 459 notifyCsSelected(); 460 } 461 } 462 463 @VisibleForTesting getSelectorState()464 protected SelectorState getSelectorState() { 465 return mSelectorState; 466 } 467 } 468