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 android.telecom; 18 19 import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SystemApi; 25 import android.media.ToneGenerator; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.telephony.Annotation; 29 import android.telephony.PreciseDisconnectCause; 30 import android.telephony.ims.ImsReasonInfo; 31 import android.text.TextUtils; 32 33 import androidx.annotation.IntDef; 34 35 import com.android.server.telecom.flags.Flags; 36 37 import java.lang.annotation.Retention; 38 import java.util.Objects; 39 40 /** 41 * Describes the cause of a disconnected call. This always includes a code describing the generic 42 * cause of the disconnect. Optionally, it may include a label and/or description to display to the 43 * user. It is the responsibility of the {@link ConnectionService} to provide localized versions of 44 * the label and description. It also may contain a reason for the disconnect, which is intended for 45 * logging and not for display to the user. 46 */ 47 public final class DisconnectCause implements Parcelable { 48 49 /** Disconnected because of an unknown or unspecified reason. */ 50 public static final int UNKNOWN = 0; 51 /** Disconnected because there was an error, such as a problem with the network. */ 52 public static final int ERROR = 1; 53 /** Disconnected because of a local user-initiated action, such as hanging up. */ 54 public static final int LOCAL = 2; 55 /** 56 * Disconnected because the remote party hung up an ongoing call, or because an outgoing call 57 * was not answered by the remote party. 58 */ 59 public static final int REMOTE = 3; 60 /** Disconnected because it has been canceled. */ 61 public static final int CANCELED = 4; 62 /** Disconnected because there was no response to an incoming call. */ 63 public static final int MISSED = 5; 64 /** Disconnected because the user rejected an incoming call. */ 65 public static final int REJECTED = 6; 66 /** Disconnected because the other party was busy. */ 67 public static final int BUSY = 7; 68 /** 69 * Disconnected because of a restriction on placing the call, such as dialing in airplane 70 * mode. 71 */ 72 public static final int RESTRICTED = 8; 73 /** Disconnected for reason not described by other disconnect codes. */ 74 public static final int OTHER = 9; 75 /** 76 * Disconnected because the connection manager did not support the call. The call will be tried 77 * again without a connection manager. See {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. 78 */ 79 public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; 80 81 /** 82 * Disconnected because the user did not locally answer the incoming call, but it was answered 83 * on another device where the call was ringing. 84 */ 85 public static final int ANSWERED_ELSEWHERE = 11; 86 87 /** 88 * Disconnected because the call was pulled from the current device to another device. 89 */ 90 public static final int CALL_PULLED = 12; 91 92 /** 93 * @hide 94 */ 95 @Retention(SOURCE) 96 @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) 97 @IntDef({ 98 UNKNOWN, 99 ERROR, 100 LOCAL, 101 REMOTE, 102 CANCELED, 103 MISSED, 104 REJECTED, 105 BUSY, 106 RESTRICTED, 107 OTHER, 108 CONNECTION_MANAGER_NOT_SUPPORTED, 109 ANSWERED_ELSEWHERE, 110 CALL_PULLED 111 }) 112 public @interface DisconnectCauseCode {} 113 114 /** 115 * Reason code (returned via {@link #getReason()}) which indicates that a call could not be 116 * completed because the cellular radio is off or out of service, the device is connected to 117 * a wifi network, but the user has not enabled wifi calling. 118 */ 119 public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF"; 120 121 /** 122 * Reason code (returned via {@link #getReason()}), which indicates that the call was 123 * disconnected because IMS access is blocked. 124 */ 125 public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED"; 126 127 /** 128 * Reason code (returned via {@link #getReason()}), which indicates that the connection service 129 * is setting the call's state to {@link Call#STATE_DISCONNECTED} because it is internally 130 * changing the representation of an IMS conference call to simulate a single-party call. 131 * 132 * This reason code is only used for communication between a {@link ConnectionService} and 133 * Telecom and should not be surfaced to the user. 134 */ 135 public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL"; 136 137 /** 138 * This reason is set when a call is ended in order to place an emergency call when a 139 * {@link PhoneAccount} doesn't support holding an ongoing call to place an emergency call. This 140 * reason string should only be associated with the {@link #LOCAL} disconnect code returned from 141 * {@link #getCode()}. 142 */ 143 public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED"; 144 145 private @DisconnectCauseCode int mDisconnectCode; 146 private CharSequence mDisconnectLabel; 147 private CharSequence mDisconnectDescription; 148 private String mDisconnectReason; 149 private int mToneToPlay; 150 private int mTelephonyDisconnectCause; 151 private int mTelephonyPreciseDisconnectCause; 152 private ImsReasonInfo mImsReasonInfo; 153 154 /** 155 * Creates a new DisconnectCause. 156 * 157 * @param code The code for the disconnect cause. 158 */ DisconnectCause(@isconnectCauseCode int code)159 public DisconnectCause(@DisconnectCauseCode int code) { 160 this(code, null, null, null, ToneGenerator.TONE_UNKNOWN); 161 } 162 163 /** 164 * Creates a new DisconnectCause. 165 * 166 * @param code The code for the disconnect cause. 167 * @param reason The reason for the disconnect. 168 */ DisconnectCause(@isconnectCauseCode int code, String reason)169 public DisconnectCause(@DisconnectCauseCode int code, String reason) { 170 this(code, null, null, reason, ToneGenerator.TONE_UNKNOWN); 171 } 172 173 /** 174 * Creates a new DisconnectCause. 175 * 176 * @param code The code for the disconnect cause. 177 * @param label The localized label to show to the user to explain the disconnect. 178 * @param description The localized description to show to the user to explain the disconnect. 179 * @param reason The reason for the disconnect. 180 */ DisconnectCause(@isconnectCauseCode int code, CharSequence label, CharSequence description, String reason)181 public DisconnectCause(@DisconnectCauseCode int code, CharSequence label, 182 CharSequence description, String reason) { 183 this(code, label, description, reason, ToneGenerator.TONE_UNKNOWN); 184 } 185 186 /** 187 * Creates a new DisconnectCause. 188 * 189 * @param code The code for the disconnect cause. 190 * @param label The localized label to show to the user to explain the disconnect. 191 * @param description The localized description to show to the user to explain the disconnect. 192 * @param reason The reason for the disconnect. 193 * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}. 194 */ DisconnectCause(@isconnectCauseCode int code, CharSequence label, CharSequence description, String reason, int toneToPlay)195 public DisconnectCause(@DisconnectCauseCode int code, CharSequence label, 196 CharSequence description, String reason, int toneToPlay) { 197 this(code, label, description, reason, toneToPlay, 198 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 199 PreciseDisconnectCause.ERROR_UNSPECIFIED, null /* imsReasonInfo */); 200 } 201 202 /** 203 * Creates a new DisconnectCause instance. This is used by Telephony to pass in extra debug 204 * info to Telecom regarding the disconnect cause. 205 * 206 * @param code The code for the disconnect cause. 207 * @param label The localized label to show to the user to explain the disconnect. 208 * @param description The localized description to show to the user to explain the disconnect. 209 * @param reason The reason for the disconnect. 210 * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}. 211 * @param telephonyDisconnectCause The Telephony disconnect cause. 212 * @param telephonyPreciseDisconnectCause The Telephony precise disconnect cause. 213 * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available. 214 * @hide 215 */ DisconnectCause(@isconnectCauseCode int code, @Nullable CharSequence label, @Nullable CharSequence description, @Nullable String reason, int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause, @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause, @Nullable ImsReasonInfo imsReasonInfo)216 public DisconnectCause(@DisconnectCauseCode int code, @Nullable CharSequence label, 217 @Nullable CharSequence description, @Nullable String reason, 218 int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause, 219 @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause, 220 @Nullable ImsReasonInfo imsReasonInfo) { 221 mDisconnectCode = code; 222 mDisconnectLabel = label; 223 mDisconnectDescription = description; 224 mDisconnectReason = reason; 225 mToneToPlay = toneToPlay; 226 mTelephonyDisconnectCause = telephonyDisconnectCause; 227 mTelephonyPreciseDisconnectCause = telephonyPreciseDisconnectCause; 228 mImsReasonInfo = imsReasonInfo; 229 } 230 231 /** 232 * Returns the code for the reason for this disconnect. 233 * 234 * @return The disconnect code. 235 */ getCode()236 public @DisconnectCauseCode int getCode() { 237 return mDisconnectCode; 238 } 239 240 /** 241 * Returns a short label which explains the reason for the disconnect cause and is for display 242 * in the user interface. If not null, it is expected that the In-Call UI should display this 243 * text where it would normally display the call state ("Dialing", "Disconnected") and is 244 * therefore expected to be relatively small. The {@link ConnectionService } is responsible for 245 * providing and localizing this label. If there is no string provided, returns null. 246 * 247 * @return The disconnect label. 248 */ getLabel()249 public CharSequence getLabel() { 250 return mDisconnectLabel; 251 } 252 253 /** 254 * Returns a description which explains the reason for the disconnect cause and is for display 255 * in the user interface. This optional text is generally a longer and more descriptive version 256 * of {@link #getLabel}, however it can exist even if {@link #getLabel} is empty. The In-Call UI 257 * should display this relatively prominently; the traditional implementation displays this as 258 * an alert dialog. The {@link ConnectionService} is responsible for providing and localizing 259 * this message. If there is no string provided, returns null. 260 * 261 * @return The disconnect description. 262 */ getDescription()263 public CharSequence getDescription() { 264 return mDisconnectDescription; 265 } 266 267 /** 268 * Returns an explanation of the reason for the disconnect. This is not intended for display to 269 * the user and is used mainly for logging. 270 * 271 * @return The disconnect reason. 272 */ getReason()273 public String getReason() { 274 return mDisconnectReason; 275 } 276 277 /** 278 * Returns the telephony {@link android.telephony.DisconnectCause} for the call. This is only 279 * used internally by Telecom for providing extra debug information from Telephony. 280 * 281 * @return The disconnect cause. 282 * @hide 283 */ 284 @SystemApi 285 @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) getTelephonyDisconnectCause()286 public @Annotation.DisconnectCauses int getTelephonyDisconnectCause() { 287 return mTelephonyDisconnectCause; 288 } 289 290 /** 291 * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call. This is 292 * only used internally by Telecom for providing extra debug information from Telephony. 293 * 294 * @return The precise disconnect cause. 295 * @hide 296 */ 297 @SystemApi 298 @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) getTelephonyPreciseDisconnectCause()299 public @Annotation.PreciseDisconnectCauses int getTelephonyPreciseDisconnectCause() { 300 return mTelephonyPreciseDisconnectCause; 301 } 302 303 /** 304 * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection. This is 305 * only used internally by Telecom for providing extra debug information from Telephony. 306 * 307 * @return The {@link ImsReasonInfo} or {@code null} if not known. 308 * @hide 309 */ 310 @SystemApi 311 @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) getImsReasonInfo()312 public @Nullable ImsReasonInfo getImsReasonInfo() { 313 return mImsReasonInfo; 314 } 315 316 /** 317 * Returns the tone to play when disconnected. 318 * 319 * @return the tone as defined in {@link ToneGenerator} to play when disconnected. 320 */ getTone()321 public int getTone() { 322 return mToneToPlay; 323 } 324 325 /** 326 * @hide 327 */ 328 @SystemApi 329 @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) 330 public static final class Builder { 331 private @DisconnectCauseCode int mDisconnectCode; 332 private CharSequence mDisconnectLabel; 333 private CharSequence mDisconnectDescription; 334 private String mDisconnectReason; 335 private int mToneToPlay = ToneGenerator.TONE_UNKNOWN; 336 private int mTelephonyDisconnectCause; 337 private int mTelephonyPreciseDisconnectCause; 338 private ImsReasonInfo mImsReasonInfo; 339 Builder(@isconnectCauseCode int code)340 public Builder(@DisconnectCauseCode int code) { 341 mDisconnectCode = code; 342 } 343 344 /** 345 * Sets a label which explains the reason for the disconnect cause, used for display in the 346 * user interface. 347 * @param label The label to associate with the disconnect cause. 348 * @return The {@link DisconnectCause} builder instance. 349 */ setLabel(@ullable CharSequence label)350 public @NonNull DisconnectCause.Builder setLabel(@Nullable CharSequence label) { 351 mDisconnectLabel = label; 352 return this; 353 } 354 355 /** 356 * Sets a description which provides the reason for the disconnect cause, used for display 357 * in the user interface. 358 * @param description The description to associate with the disconnect cause. 359 * @return The {@link DisconnectCause} builder instance. 360 */ setDescription( @ullable CharSequence description)361 public @NonNull DisconnectCause.Builder setDescription( 362 @Nullable CharSequence description) { 363 mDisconnectDescription = description; 364 return this; 365 } 366 367 /** 368 * Sets a reason providing explanation for the disconnect (intended for logging and not for 369 * displaying in the user interface). 370 * @param reason The reason for the disconnect. 371 * @return The {@link DisconnectCause} builder instance. 372 */ setReason(@onNull String reason)373 public @NonNull DisconnectCause.Builder setReason(@NonNull String reason) { 374 mDisconnectReason = reason; 375 return this; 376 } 377 378 /** 379 * Sets the tone to play when disconnected. 380 * @param toneToPlay The tone as defined in {@link ToneGenerator} to play when disconnected. 381 * @return The {@link DisconnectCause} builder instance. 382 */ setTone(int toneToPlay)383 public @NonNull DisconnectCause.Builder setTone(int toneToPlay) { 384 mToneToPlay = toneToPlay; 385 return this; 386 } 387 388 /** 389 * Sets the telephony {@link android.telephony.DisconnectCause} for the call (used 390 * internally by Telecom for providing extra debug information from Telephony). 391 * @param telephonyDisconnectCause The disconnect cause as provided by Telephony. 392 * @return The {@link DisconnectCause} builder instance. 393 */ setTelephonyDisconnectCause( @nnotation.DisconnectCauses int telephonyDisconnectCause)394 public @NonNull DisconnectCause.Builder setTelephonyDisconnectCause( 395 @Annotation.DisconnectCauses int telephonyDisconnectCause) { 396 mTelephonyDisconnectCause = telephonyDisconnectCause; 397 return this; 398 } 399 400 /** 401 * Sets the telephony {@link android.telephony.PreciseDisconnectCause} for the call (used 402 * internally by Telecom for providing extra debug information from Telephony). 403 * @param telephonyPreciseDisconnectCause The precise disconnect cause as provided by 404 * Telephony. 405 * @return The {@link DisconnectCause} builder instance. 406 */ 407 setTelephonyPreciseDisconnectCause( @nnotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause)408 public @NonNull DisconnectCause.Builder setTelephonyPreciseDisconnectCause( 409 @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause) { 410 mTelephonyPreciseDisconnectCause = telephonyPreciseDisconnectCause; 411 return this; 412 } 413 414 /** 415 * Sets the telephony {@link ImsReasonInfo} associated with the call disconnection. This 416 * is only used internally by Telecom for providing extra debug information from Telephony. 417 * 418 * @param imsReasonInfo The {@link ImsReasonInfo} or {@code null} if not known. 419 * @return The {@link DisconnectCause} builder instance. 420 */ setImsReasonInfo( @ullable ImsReasonInfo imsReasonInfo)421 public @NonNull DisconnectCause.Builder setImsReasonInfo( 422 @Nullable ImsReasonInfo imsReasonInfo) { 423 mImsReasonInfo = imsReasonInfo; 424 return this; 425 } 426 427 /** 428 * Build the {@link DisconnectCause} from the provided Builder config. 429 * @return The {@link DisconnectCause} instance from provided builder. 430 */ build()431 public @NonNull DisconnectCause build() { 432 return new DisconnectCause(mDisconnectCode, mDisconnectLabel, mDisconnectDescription, 433 mDisconnectReason, mToneToPlay, mTelephonyDisconnectCause, 434 mTelephonyPreciseDisconnectCause, mImsReasonInfo); 435 } 436 } 437 438 public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR 439 = new Creator<DisconnectCause>() { 440 @Override 441 public DisconnectCause createFromParcel(Parcel source) { 442 int code = source.readInt(); 443 CharSequence label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); 444 CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); 445 String reason = source.readString(); 446 int tone = source.readInt(); 447 int telephonyDisconnectCause = source.readInt(); 448 int telephonyPreciseDisconnectCause = source.readInt(); 449 ImsReasonInfo imsReasonInfo = source.readParcelable(null, android.telephony.ims.ImsReasonInfo.class); 450 return new DisconnectCause(code, label, description, reason, tone, 451 telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo); 452 } 453 454 @Override 455 public DisconnectCause[] newArray(int size) { 456 return new DisconnectCause[size]; 457 } 458 }; 459 460 @Override writeToParcel(Parcel destination, int flags)461 public void writeToParcel(Parcel destination, int flags) { 462 destination.writeInt(mDisconnectCode); 463 TextUtils.writeToParcel(mDisconnectLabel, destination, flags); 464 TextUtils.writeToParcel(mDisconnectDescription, destination, flags); 465 destination.writeString(mDisconnectReason); 466 destination.writeInt(mToneToPlay); 467 destination.writeInt(mTelephonyDisconnectCause); 468 destination.writeInt(mTelephonyPreciseDisconnectCause); 469 destination.writeParcelable(mImsReasonInfo, 0); 470 } 471 472 @Override describeContents()473 public int describeContents() { 474 return 0; 475 } 476 477 @Override hashCode()478 public int hashCode() { 479 return Objects.hashCode(mDisconnectCode) 480 + Objects.hashCode(mDisconnectLabel) 481 + Objects.hashCode(mDisconnectDescription) 482 + Objects.hashCode(mDisconnectReason) 483 + Objects.hashCode(mToneToPlay) 484 + Objects.hashCode(mTelephonyDisconnectCause) 485 + Objects.hashCode(mTelephonyPreciseDisconnectCause) 486 + Objects.hashCode(mImsReasonInfo); 487 } 488 489 @Override equals(Object o)490 public boolean equals(Object o) { 491 if (o instanceof DisconnectCause) { 492 DisconnectCause d = (DisconnectCause) o; 493 return Objects.equals(mDisconnectCode, d.getCode()) 494 && Objects.equals(mDisconnectLabel, d.getLabel()) 495 && Objects.equals(mDisconnectDescription, d.getDescription()) 496 && Objects.equals(mDisconnectReason, d.getReason()) 497 && Objects.equals(mToneToPlay, d.getTone()) 498 && Objects.equals(mTelephonyDisconnectCause, d.getTelephonyDisconnectCause()) 499 && Objects.equals(mTelephonyPreciseDisconnectCause, 500 d.getTelephonyPreciseDisconnectCause()) 501 && Objects.equals(mImsReasonInfo, d.getImsReasonInfo()); 502 } 503 return false; 504 } 505 506 @Override toString()507 public String toString() { 508 String code = ""; 509 switch (mDisconnectCode) { 510 case UNKNOWN: 511 code = "UNKNOWN"; 512 break; 513 case ERROR: 514 code = "ERROR"; 515 break; 516 case LOCAL: 517 code = "LOCAL"; 518 break; 519 case REMOTE: 520 code = "REMOTE"; 521 break; 522 case CANCELED: 523 code = "CANCELED"; 524 break; 525 case MISSED: 526 code = "MISSED"; 527 break; 528 case REJECTED: 529 code = "REJECTED"; 530 break; 531 case BUSY: 532 code = "BUSY"; 533 break; 534 case RESTRICTED: 535 code = "RESTRICTED"; 536 break; 537 case OTHER: 538 code = "OTHER"; 539 break; 540 case CONNECTION_MANAGER_NOT_SUPPORTED: 541 code = "CONNECTION_MANAGER_NOT_SUPPORTED"; 542 break; 543 case CALL_PULLED: 544 code = "CALL_PULLED"; 545 break; 546 case ANSWERED_ELSEWHERE: 547 code = "ANSWERED_ELSEWHERE"; 548 break; 549 default: 550 code = "invalid code: " + mDisconnectCode; 551 break; 552 } 553 String label = mDisconnectLabel == null ? "" : mDisconnectLabel.toString(); 554 String description = mDisconnectDescription == null 555 ? "" : mDisconnectDescription.toString(); 556 String reason = mDisconnectReason == null ? "" : mDisconnectReason; 557 return "DisconnectCause [ Code: (" + code + ")" 558 + " Label: (" + label + ")" 559 + " Description: (" + description + ")" 560 + " Reason: (" + reason + ")" 561 + " Tone: (" + mToneToPlay + ") " 562 + " TelephonyCause: " + mTelephonyDisconnectCause + "/" 563 + mTelephonyPreciseDisconnectCause 564 + " ImsReasonInfo: " 565 + mImsReasonInfo 566 + "]"; 567 } 568 } 569