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