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 17package com.android.services.telephony; 18 19import android.content.Context; 20import android.media.ToneGenerator; 21import android.telecom.DisconnectCause; 22 23import com.android.phone.PhoneGlobals; 24import com.android.phone.common.R; 25import com.android.phone.ImsUtil; 26 27public class DisconnectCauseUtil { 28 29 /** 30 * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more 31 * generic {@link android.telecom.DisconnectCause} object, possibly populated with a localized 32 * message and tone. 33 * 34 * @param telephonyDisconnectCause The code for the reason for the disconnect. 35 */ 36 public static DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause) { 37 return toTelecomDisconnectCause(telephonyDisconnectCause, null /* reason */); 38 } 39 40 /** 41 * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more 42 * generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized 43 * message and tone. 44 * 45 * @param telephonyDisconnectCause The code for the reason for the disconnect. 46 * @param reason Description of the reason for the disconnect, not intended for the user to see.. 47 */ 48 public static DisconnectCause toTelecomDisconnectCause( 49 int telephonyDisconnectCause, String reason) { 50 Context context = PhoneGlobals.getInstance(); 51 return new DisconnectCause( 52 toTelecomDisconnectCauseCode(telephonyDisconnectCause), 53 toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause), 54 toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause), 55 toTelecomDisconnectReason(context,telephonyDisconnectCause, reason), 56 toTelecomDisconnectCauseTone(telephonyDisconnectCause)); 57 } 58 59 /** 60 * Convert the {@link android.telephony.DisconnectCause} disconnect code into a 61 * {@link android.telecom.DisconnectCause} disconnect code. 62 * @return The disconnect code as defined in {@link android.telecom.DisconnectCause}. 63 */ 64 private static int toTelecomDisconnectCauseCode(int telephonyDisconnectCause) { 65 switch (telephonyDisconnectCause) { 66 case android.telephony.DisconnectCause.LOCAL: 67 return DisconnectCause.LOCAL; 68 69 case android.telephony.DisconnectCause.NORMAL: 70 return DisconnectCause.REMOTE; 71 72 case android.telephony.DisconnectCause.OUTGOING_CANCELED: 73 return DisconnectCause.CANCELED; 74 75 case android.telephony.DisconnectCause.INCOMING_MISSED: 76 return DisconnectCause.MISSED; 77 78 case android.telephony.DisconnectCause.INCOMING_REJECTED: 79 return DisconnectCause.REJECTED; 80 81 case android.telephony.DisconnectCause.BUSY: 82 return DisconnectCause.BUSY; 83 84 case android.telephony.DisconnectCause.CALL_BARRED: 85 case android.telephony.DisconnectCause.CDMA_ACCESS_BLOCKED: 86 case android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY: 87 case android.telephony.DisconnectCause.CS_RESTRICTED: 88 case android.telephony.DisconnectCause.CS_RESTRICTED_EMERGENCY: 89 case android.telephony.DisconnectCause.CS_RESTRICTED_NORMAL: 90 case android.telephony.DisconnectCause.EMERGENCY_ONLY: 91 case android.telephony.DisconnectCause.FDN_BLOCKED: 92 case android.telephony.DisconnectCause.LIMIT_EXCEEDED: 93 case android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED: 94 return DisconnectCause.RESTRICTED; 95 96 case android.telephony.DisconnectCause.CDMA_ACCESS_FAILURE: 97 case android.telephony.DisconnectCause.CDMA_ALREADY_ACTIVATED: 98 case android.telephony.DisconnectCause.CDMA_CALL_LOST: 99 case android.telephony.DisconnectCause.CDMA_DROP: 100 case android.telephony.DisconnectCause.CDMA_INTERCEPT: 101 case android.telephony.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE: 102 case android.telephony.DisconnectCause.CDMA_PREEMPTED: 103 case android.telephony.DisconnectCause.CDMA_REORDER: 104 case android.telephony.DisconnectCause.CDMA_RETRY_ORDER: 105 case android.telephony.DisconnectCause.CDMA_SO_REJECT: 106 case android.telephony.DisconnectCause.CONGESTION: 107 case android.telephony.DisconnectCause.ICC_ERROR: 108 case android.telephony.DisconnectCause.INVALID_CREDENTIALS: 109 case android.telephony.DisconnectCause.INVALID_NUMBER: 110 case android.telephony.DisconnectCause.LOST_SIGNAL: 111 case android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED: 112 case android.telephony.DisconnectCause.NUMBER_UNREACHABLE: 113 case android.telephony.DisconnectCause.OUTGOING_FAILURE: 114 case android.telephony.DisconnectCause.OUT_OF_NETWORK: 115 case android.telephony.DisconnectCause.OUT_OF_SERVICE: 116 case android.telephony.DisconnectCause.POWER_OFF: 117 case android.telephony.DisconnectCause.SERVER_ERROR: 118 case android.telephony.DisconnectCause.SERVER_UNREACHABLE: 119 case android.telephony.DisconnectCause.TIMED_OUT: 120 case android.telephony.DisconnectCause.UNOBTAINABLE_NUMBER: 121 case android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING: 122 case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_USSD: 123 case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_SS: 124 case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_DIAL: 125 case android.telephony.DisconnectCause.ERROR_UNSPECIFIED: 126 case android.telephony.DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED: 127 case android.telephony.DisconnectCause.DATA_DISABLED: 128 case android.telephony.DisconnectCause.DATA_LIMIT_REACHED: 129 case android.telephony.DisconnectCause.DIALED_ON_WRONG_SLOT: 130 case android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING: 131 return DisconnectCause.ERROR; 132 133 case android.telephony.DisconnectCause.DIALED_MMI: 134 case android.telephony.DisconnectCause.EXITED_ECM: 135 case android.telephony.DisconnectCause.MMI: 136 case android.telephony.DisconnectCause.IMS_MERGED_SUCCESSFULLY: 137 return DisconnectCause.OTHER; 138 139 case android.telephony.DisconnectCause.NOT_VALID: 140 case android.telephony.DisconnectCause.NOT_DISCONNECTED: 141 return DisconnectCause.UNKNOWN; 142 143 case android.telephony.DisconnectCause.CALL_PULLED: 144 return DisconnectCause.CALL_PULLED; 145 146 case android.telephony.DisconnectCause.ANSWERED_ELSEWHERE: 147 return DisconnectCause.ANSWERED_ELSEWHERE; 148 149 default: 150 Log.w("DisconnectCauseUtil.toTelecomDisconnectCauseCode", 151 "Unrecognized Telephony DisconnectCause " 152 + telephonyDisconnectCause); 153 return DisconnectCause.UNKNOWN; 154 } 155 } 156 157 /** 158 * Returns a label for to the disconnect cause to be shown to the user. 159 */ 160 private static CharSequence toTelecomDisconnectCauseLabel( 161 Context context, int telephonyDisconnectCause) { 162 if (context == null ) { 163 return ""; 164 } 165 166 Integer resourceId = null; 167 switch (telephonyDisconnectCause) { 168 case android.telephony.DisconnectCause.BUSY: 169 resourceId = R.string.callFailed_userBusy; 170 break; 171 172 case android.telephony.DisconnectCause.CONGESTION: 173 resourceId = R.string.callFailed_congestion; 174 break; 175 176 case android.telephony.DisconnectCause.TIMED_OUT: 177 resourceId = R.string.callFailed_timedOut; 178 break; 179 180 case android.telephony.DisconnectCause.SERVER_UNREACHABLE: 181 resourceId = R.string.callFailed_server_unreachable; 182 break; 183 184 case android.telephony.DisconnectCause.NUMBER_UNREACHABLE: 185 resourceId = R.string.callFailed_number_unreachable; 186 break; 187 188 case android.telephony.DisconnectCause.INVALID_CREDENTIALS: 189 resourceId = R.string.callFailed_invalid_credentials; 190 break; 191 192 case android.telephony.DisconnectCause.SERVER_ERROR: 193 resourceId = R.string.callFailed_server_error; 194 break; 195 196 case android.telephony.DisconnectCause.OUT_OF_NETWORK: 197 resourceId = R.string.callFailed_out_of_network; 198 break; 199 200 case android.telephony.DisconnectCause.LOST_SIGNAL: 201 case android.telephony.DisconnectCause.CDMA_DROP: 202 resourceId = R.string.callFailed_noSignal; 203 break; 204 205 case android.telephony.DisconnectCause.LIMIT_EXCEEDED: 206 resourceId = R.string.callFailed_limitExceeded; 207 break; 208 209 case android.telephony.DisconnectCause.POWER_OFF: 210 resourceId = R.string.callFailed_powerOff; 211 break; 212 213 case android.telephony.DisconnectCause.ICC_ERROR: 214 resourceId = R.string.callFailed_simError; 215 break; 216 217 case android.telephony.DisconnectCause.OUT_OF_SERVICE: 218 resourceId = R.string.callFailed_outOfService; 219 break; 220 221 case android.telephony.DisconnectCause.INVALID_NUMBER: 222 case android.telephony.DisconnectCause.UNOBTAINABLE_NUMBER: 223 resourceId = R.string.callFailed_unobtainable_number; 224 break; 225 226 case android.telephony.DisconnectCause.CALL_PULLED: 227 resourceId = R.string.callEnded_pulled; 228 break; 229 230 case android.telephony.DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED: 231 resourceId = R.string.callFailed_maximum_reached; 232 break; 233 234 case android.telephony.DisconnectCause.DATA_DISABLED: 235 resourceId = R.string.callFailed_data_disabled; 236 break; 237 238 case android.telephony.DisconnectCause.DATA_LIMIT_REACHED: 239 resourceId = R.string.callFailed_data_limit_reached; 240 break; 241 242 default: 243 break; 244 } 245 return resourceId == null ? "" : context.getResources().getString(resourceId); 246 } 247 248 /** 249 * Returns a description of the disconnect cause to be shown to the user. 250 */ 251 private static CharSequence toTelecomDisconnectCauseDescription( 252 Context context, int telephonyDisconnectCause) { 253 if (context == null ) { 254 return ""; 255 } 256 257 Integer resourceId = null; 258 switch (telephonyDisconnectCause) { 259 case android.telephony.DisconnectCause.CALL_BARRED: 260 resourceId = R.string.callFailed_cb_enabled; 261 break; 262 263 case android.telephony.DisconnectCause.CDMA_ALREADY_ACTIVATED: 264 resourceId = R.string.callFailed_cdma_activation; 265 break; 266 267 case android.telephony.DisconnectCause.FDN_BLOCKED: 268 resourceId = R.string.callFailed_fdn_only; 269 break; 270 271 case android.telephony.DisconnectCause.CS_RESTRICTED: 272 resourceId = R.string.callFailed_dsac_restricted; 273 break; 274 275 case android.telephony.DisconnectCause.CS_RESTRICTED_EMERGENCY: 276 resourceId = R.string.callFailed_dsac_restricted_emergency; 277 break; 278 279 case android.telephony.DisconnectCause.CS_RESTRICTED_NORMAL: 280 resourceId = R.string.callFailed_dsac_restricted_normal; 281 break; 282 283 case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_USSD: 284 resourceId = R.string.callFailed_dialToUssd; 285 break; 286 287 case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_SS: 288 resourceId = R.string.callFailed_dialToSs; 289 break; 290 291 case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_DIAL: 292 resourceId = R.string.callFailed_dialToDial; 293 break; 294 295 case android.telephony.DisconnectCause.OUTGOING_FAILURE: 296 // We couldn't successfully place the call; there was some 297 // failure in the telephony layer. 298 // TODO: Need UI spec for this failure case; for now just 299 // show a generic error. 300 resourceId = R.string.incall_error_call_failed; 301 break; 302 303 case android.telephony.DisconnectCause.POWER_OFF: 304 // Radio is explictly powered off because the device is in airplane mode. 305 306 // TODO: Offer the option to turn the radio on, and automatically retry the call 307 // once network registration is complete. 308 309 if (ImsUtil.shouldPromoteWfc(context)) { 310 resourceId = R.string.incall_error_promote_wfc; 311 } else if (ImsUtil.isWfcModeWifiOnly(context)) { 312 resourceId = R.string.incall_error_wfc_only_no_wireless_network; 313 } else if (ImsUtil.isWfcEnabled(context)) { 314 resourceId = R.string.incall_error_power_off_wfc; 315 } else { 316 resourceId = R.string.incall_error_power_off; 317 } 318 break; 319 320 case android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY: 321 // Only emergency calls are allowed when in emergency callback mode. 322 resourceId = R.string.incall_error_ecm_emergency_only; 323 break; 324 325 case android.telephony.DisconnectCause.EMERGENCY_ONLY: 326 // Only emergency numbers are allowed, but we tried to dial 327 // a non-emergency number. 328 resourceId = R.string.incall_error_emergency_only; 329 break; 330 331 case android.telephony.DisconnectCause.OUT_OF_SERVICE: 332 // No network connection. 333 if (ImsUtil.shouldPromoteWfc(context)) { 334 resourceId = R.string.incall_error_promote_wfc; 335 } else if (ImsUtil.isWfcModeWifiOnly(context)) { 336 resourceId = R.string.incall_error_wfc_only_no_wireless_network; 337 } else if (ImsUtil.isWfcEnabled(context)) { 338 resourceId = R.string.incall_error_out_of_service_wfc; 339 } else { 340 resourceId = R.string.incall_error_out_of_service; 341 } 342 break; 343 344 case android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED: 345 // The supplied Intent didn't contain a valid phone number. 346 // (This is rare and should only ever happen with broken 347 // 3rd-party apps.) For now just show a generic error. 348 resourceId = R.string.incall_error_no_phone_number_supplied; 349 break; 350 351 case android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING: 352 // TODO: Need to bring up the "Missing Voicemail Number" dialog, which 353 // will ultimately take us to the Call Settings. 354 resourceId = R.string.incall_error_missing_voicemail_number; 355 break; 356 357 case android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED: 358 resourceId = R.string.callFailed_video_call_tty_enabled; 359 break; 360 361 case android.telephony.DisconnectCause.CALL_PULLED: 362 resourceId = R.string.callEnded_pulled; 363 break; 364 365 case android.telephony.DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED: 366 resourceId = R.string.callFailed_maximum_reached; 367 368 case android.telephony.DisconnectCause.OUTGOING_CANCELED: 369 // We don't want to show any dialog for the canceled case since the call was 370 // either canceled by the user explicitly (end-call button pushed immediately) 371 // or some other app canceled the call and immediately issued a new CALL to 372 // replace it. 373 break; 374 375 case android.telephony.DisconnectCause.DATA_DISABLED: 376 resourceId = R.string.callFailed_data_disabled; 377 break; 378 379 case android.telephony.DisconnectCause.DATA_LIMIT_REACHED: 380 resourceId = R.string.callFailed_data_limit_reached_description; 381 break; 382 case android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING: 383 resourceId = com.android.internal.R.string.mmiErrorWhileRoaming; 384 break; 385 386 default: 387 break; 388 } 389 return resourceId == null ? "" : context.getResources().getString(resourceId); 390 } 391 392 /** 393 * Maps the telephony {@link android.telephony.DisconnectCause} into a reason string which is 394 * returned in the Telecom {@link DisconnectCause#getReason()}. 395 * 396 * @param context The current context. 397 * @param telephonyDisconnectCause The {@link android.telephony.DisconnectCause} code. 398 * @param reason A reason provided by the caller; only used if a more specific reason cannot 399 * be determined here. 400 * @return The disconnect reason. 401 */ 402 private static String toTelecomDisconnectReason(Context context, int telephonyDisconnectCause, 403 String reason) { 404 405 if (context == null) { 406 return ""; 407 } 408 409 switch (telephonyDisconnectCause) { 410 case android.telephony.DisconnectCause.POWER_OFF: 411 // Airplane mode (radio off) 412 // intentional fall-through 413 case android.telephony.DisconnectCause.OUT_OF_SERVICE: 414 // No network connection. 415 if (ImsUtil.shouldPromoteWfc(context)) { 416 return android.telecom.DisconnectCause.REASON_WIFI_ON_BUT_WFC_OFF; 417 } 418 break; 419 } 420 421 // If no specific code-mapping found, then fall back to using the reason. 422 String causeAsString = android.telephony.DisconnectCause.toString(telephonyDisconnectCause); 423 if (reason == null) { 424 return causeAsString; 425 } else { 426 return reason + ", " + causeAsString; 427 } 428 } 429 430 /** 431 * Returns the tone to play for the disconnect cause, or UNKNOWN if none should be played. 432 */ 433 private static int toTelecomDisconnectCauseTone(int telephonyDisconnectCause) { 434 switch (telephonyDisconnectCause) { 435 case android.telephony.DisconnectCause.BUSY: 436 return ToneGenerator.TONE_SUP_BUSY; 437 438 case android.telephony.DisconnectCause.CONGESTION: 439 return ToneGenerator.TONE_SUP_CONGESTION; 440 441 case android.telephony.DisconnectCause.CDMA_REORDER: 442 return ToneGenerator.TONE_CDMA_REORDER; 443 444 case android.telephony.DisconnectCause.CDMA_INTERCEPT: 445 return ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; 446 447 case android.telephony.DisconnectCause.CDMA_DROP: 448 case android.telephony.DisconnectCause.OUT_OF_SERVICE: 449 return ToneGenerator.TONE_CDMA_CALLDROP_LITE; 450 451 case android.telephony.DisconnectCause.UNOBTAINABLE_NUMBER: 452 return ToneGenerator.TONE_SUP_ERROR; 453 454 case android.telephony.DisconnectCause.ERROR_UNSPECIFIED: 455 case android.telephony.DisconnectCause.LOCAL: 456 case android.telephony.DisconnectCause.NORMAL: 457 case android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED: 458 return ToneGenerator.TONE_PROP_PROMPT; 459 460 case android.telephony.DisconnectCause.IMS_MERGED_SUCCESSFULLY: 461 // Do not play any tones if disconnected because of a successful merge. 462 default: 463 return ToneGenerator.TONE_UNKNOWN; 464 } 465 } 466} 467