• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.PersistableBundle;
22 import android.provider.Settings;
23 import android.telecom.DisconnectCause;
24 import android.telephony.CarrierConfigManager;
25 import android.telephony.SubscriptionManager;
26 import android.telephony.ims.ImsReasonInfo;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.telephony.CallFailCause;
30 import com.android.internal.telephony.Phone;
31 import com.android.internal.telephony.PhoneFactory;
32 import com.android.phone.ImsUtil;
33 import com.android.phone.PhoneGlobals;
34 import com.android.phone.common.R;
35 
36 public class DisconnectCauseUtil {
37 
38    /**
39     * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
40     * generic {@link android.telecom.DisconnectCause} object, possibly populated with a localized
41     * message and tone.
42     *
43     * @param telephonyDisconnectCause The code for the reason for the disconnect.
44     */
toTelecomDisconnectCause(int telephonyDisconnectCause)45     public static DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause) {
46         return toTelecomDisconnectCause(telephonyDisconnectCause,
47                 CallFailCause.NOT_VALID, null /* reason */);
48     }
49 
50    /**
51     * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
52     * generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
53     * message and tone.
54     *
55     * @param telephonyDisconnectCause The code for the reason for the disconnect.
56     * @param reason Description of the reason for the disconnect, not intended for the user to see..
57     */
toTelecomDisconnectCause( int telephonyDisconnectCause, String reason)58     public static DisconnectCause toTelecomDisconnectCause(
59             int telephonyDisconnectCause, String reason) {
60         return toTelecomDisconnectCause(telephonyDisconnectCause, CallFailCause.NOT_VALID, reason);
61     }
62 
63    /**
64     * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
65     * generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
66     * message and tone.
67     *
68     * @param telephonyDisconnectCause The code for the reason for the disconnect.
69     * @param telephonyPreciseDisconnectCause The code for the precise reason for the disconnect.
70     * @param reason Description of the reason for the disconnect, not intended for the user to see..
71     */
toTelecomDisconnectCause( int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason)72     public static DisconnectCause toTelecomDisconnectCause(
73             int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason) {
74         return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
75                 reason, SubscriptionManager.getDefaultVoicePhoneId(), null);
76     }
77 
78     /**
79      * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
80      * generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
81      * message and tone for Slot.
82      *
83      * @param telephonyDisconnectCause The code for the reason for the disconnect.
84      * @param phoneId To support localized message based on phoneId
85      */
toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, int phoneId)86     public static DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
87             String reason, int phoneId) {
88         return toTelecomDisconnectCause(telephonyDisconnectCause, CallFailCause.NOT_VALID,
89                 reason, phoneId, null);
90     }
91 
92    /**
93     * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
94     * generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
95     * message and tone for Slot.
96     * @param telephonyDisconnectCause The code for the reason for the disconnect.
97     * @param telephonyPreciseDisconnectCause The code for the precise reason for the disconnect.
98     * @param reason Description of the reason for the disconnect, not intended for the user to see.
99     * @param phoneId To support localized message based on phoneId
100     * @param imsReasonInfo
101     */
toTelecomDisconnectCause( int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason, int phoneId, ImsReasonInfo imsReasonInfo)102     public static DisconnectCause toTelecomDisconnectCause(
103             int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
104             int phoneId, ImsReasonInfo imsReasonInfo) {
105         return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
106                 reason, phoneId, imsReasonInfo, getCarrierConfigBundle(phoneId));
107     }
108 
109     /**
110      * Final pre-processing method in creating a DisconnectCause.  This method should NOT be called
111      * from another class directly.  It only has private-package visibility for testing.
112      *
113      * @param carrierConfig
114      */
115     @VisibleForTesting
toTelecomDisconnectCause( int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason, int phoneId, ImsReasonInfo imsReasonInfo, PersistableBundle carrierConfig)116     static DisconnectCause toTelecomDisconnectCause(
117             int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
118             int phoneId, ImsReasonInfo imsReasonInfo, PersistableBundle carrierConfig) {
119         Context context = PhoneGlobals.getInstance();
120 
121         return new DisconnectCause(
122                 toTelecomDisconnectCauseCode(telephonyDisconnectCause, carrierConfig),
123                 toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
124                         telephonyPreciseDisconnectCause, carrierConfig),
125                 toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause, phoneId),
126                 toTelecomDisconnectReason(context, telephonyDisconnectCause, reason, phoneId),
127                 toTelecomDisconnectCauseTone(telephonyDisconnectCause, carrierConfig),
128                 telephonyDisconnectCause,
129                 telephonyPreciseDisconnectCause,
130                 imsReasonInfo);
131     }
132 
133     /**
134      * Convert the {@link android.telephony.DisconnectCause} disconnect code into a
135      * {@link android.telecom.DisconnectCause} disconnect code.
136      * @return The disconnect code as defined in {@link android.telecom.DisconnectCause}.
137      */
toTelecomDisconnectCauseCode(int telephonyDisconnectCause, PersistableBundle carrierConfig)138     private static int toTelecomDisconnectCauseCode(int telephonyDisconnectCause,
139             PersistableBundle carrierConfig) {
140 
141         // special case: some carriers determine what disconnect causes play the BUSY tone.
142         // hence, must adjust the disconnectCause CODE to match the tone.
143         if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
144                 carrierConfig)) {
145             return DisconnectCause.BUSY;
146         }
147 
148         switch (telephonyDisconnectCause) {
149             case android.telephony.DisconnectCause.LOCAL:
150             //  The call was still disconnected locally, so this is not an error condition.
151             case android.telephony.DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED:
152                 return DisconnectCause.LOCAL;
153 
154             case android.telephony.DisconnectCause.NORMAL:
155             case android.telephony.DisconnectCause.NORMAL_UNSPECIFIED:
156                 return DisconnectCause.REMOTE;
157 
158             case android.telephony.DisconnectCause.OUTGOING_CANCELED:
159                 return DisconnectCause.CANCELED;
160 
161             case android.telephony.DisconnectCause.INCOMING_MISSED:
162                 return DisconnectCause.MISSED;
163 
164             case android.telephony.DisconnectCause.INCOMING_REJECTED:
165                 return DisconnectCause.REJECTED;
166 
167             case android.telephony.DisconnectCause.BUSY:
168                 return DisconnectCause.BUSY;
169 
170             case android.telephony.DisconnectCause.CALL_BARRED:
171             case android.telephony.DisconnectCause.CDMA_ACCESS_BLOCKED:
172             case android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY:
173             case android.telephony.DisconnectCause.CS_RESTRICTED:
174             case android.telephony.DisconnectCause.CS_RESTRICTED_EMERGENCY:
175             case android.telephony.DisconnectCause.CS_RESTRICTED_NORMAL:
176             case android.telephony.DisconnectCause.EMERGENCY_ONLY:
177             case android.telephony.DisconnectCause.FDN_BLOCKED:
178             case android.telephony.DisconnectCause.LIMIT_EXCEEDED:
179             case android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
180                 return DisconnectCause.RESTRICTED;
181 
182             case android.telephony.DisconnectCause.CDMA_ACCESS_FAILURE:
183             case android.telephony.DisconnectCause.CDMA_ALREADY_ACTIVATED:
184             case android.telephony.DisconnectCause.CDMA_CALL_LOST:
185             case android.telephony.DisconnectCause.CDMA_DROP:
186             case android.telephony.DisconnectCause.CDMA_INTERCEPT:
187             case android.telephony.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE:
188             case android.telephony.DisconnectCause.CDMA_PREEMPTED:
189             case android.telephony.DisconnectCause.CDMA_REORDER:
190             case android.telephony.DisconnectCause.CDMA_RETRY_ORDER:
191             case android.telephony.DisconnectCause.CDMA_SO_REJECT:
192             case android.telephony.DisconnectCause.CONGESTION:
193             case android.telephony.DisconnectCause.ICC_ERROR:
194             case android.telephony.DisconnectCause.INVALID_CREDENTIALS:
195             case android.telephony.DisconnectCause.INVALID_NUMBER:
196             case android.telephony.DisconnectCause.LOST_SIGNAL:
197             case android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
198             case android.telephony.DisconnectCause.NUMBER_UNREACHABLE:
199             case android.telephony.DisconnectCause.OUTGOING_FAILURE:
200             case android.telephony.DisconnectCause.OUT_OF_NETWORK:
201             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
202             case android.telephony.DisconnectCause.POWER_OFF:
203             case android.telephony.DisconnectCause.LOW_BATTERY:
204             case android.telephony.DisconnectCause.DIAL_LOW_BATTERY:
205             case android.telephony.DisconnectCause.EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE:
206             case android.telephony.DisconnectCause.WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION:
207             case android.telephony.DisconnectCause.SERVER_ERROR:
208             case android.telephony.DisconnectCause.SERVER_UNREACHABLE:
209             case android.telephony.DisconnectCause.TIMED_OUT:
210             case android.telephony.DisconnectCause.ALREADY_DIALING:
211             case android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING:
212             case android.telephony.DisconnectCause.CALLING_DISABLED:
213             case android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS:
214             case android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS:
215             case android.telephony.DisconnectCause.UNOBTAINABLE_NUMBER:
216             case android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING:
217             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_USSD:
218             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_SS:
219             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_DIAL:
220             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_DIAL_VIDEO:
221             case android.telephony.DisconnectCause.DIAL_VIDEO_MODIFIED_TO_SS:
222             case android.telephony.DisconnectCause.DIAL_VIDEO_MODIFIED_TO_USSD:
223             case android.telephony.DisconnectCause.DIAL_VIDEO_MODIFIED_TO_DIAL:
224             case android.telephony.DisconnectCause.DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO:
225             case android.telephony.DisconnectCause.ERROR_UNSPECIFIED:
226             case android.telephony.DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED:
227             case android.telephony.DisconnectCause.DATA_DISABLED:
228             case android.telephony.DisconnectCause.DATA_LIMIT_REACHED:
229             case android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING:
230             case android.telephony.DisconnectCause.IMEI_NOT_ACCEPTED:
231             case android.telephony.DisconnectCause.WIFI_LOST:
232             case android.telephony.DisconnectCause.IMS_ACCESS_BLOCKED:
233             case android.telephony.DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL:
234             case android.telephony.DisconnectCause.MEDIA_TIMEOUT:
235                 return DisconnectCause.ERROR;
236 
237             case android.telephony.DisconnectCause.DIALED_MMI:
238             case android.telephony.DisconnectCause.EXITED_ECM:
239             case android.telephony.DisconnectCause.MMI:
240             case android.telephony.DisconnectCause.IMS_MERGED_SUCCESSFULLY:
241                 return DisconnectCause.OTHER;
242 
243             case android.telephony.DisconnectCause.NOT_VALID:
244             case android.telephony.DisconnectCause.NOT_DISCONNECTED:
245                 return DisconnectCause.UNKNOWN;
246 
247             case android.telephony.DisconnectCause.CALL_PULLED:
248                 return DisconnectCause.CALL_PULLED;
249 
250             case android.telephony.DisconnectCause.ANSWERED_ELSEWHERE:
251                 return DisconnectCause.ANSWERED_ELSEWHERE;
252 
253             default:
254                 Log.w("DisconnectCauseUtil.toTelecomDisconnectCauseCode",
255                         "Unrecognized Telephony DisconnectCause "
256                         + telephonyDisconnectCause);
257                 return DisconnectCause.UNKNOWN;
258         }
259     }
260 
261     /**
262      * Returns a label for to the disconnect cause to be shown to the user.
263      */
toTelecomDisconnectCauseLabel( Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, PersistableBundle carrierConfig)264     private static CharSequence toTelecomDisconnectCauseLabel(
265             Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause,
266             PersistableBundle carrierConfig) {
267         CharSequence label;
268 
269         // special case: some carriers determine what disconnect causes play the BUSY tone.
270         // hence, must adjust the disconnectCause LABEL to match the tone.
271         if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
272                 carrierConfig)) {
273             return context.getResources().getString(R.string.callFailed_userBusy);
274         }
275 
276         if (telephonyPreciseDisconnectCause != CallFailCause.NOT_VALID) {
277             label = getLabelFromPreciseDisconnectCause(context, telephonyPreciseDisconnectCause,
278                     telephonyDisconnectCause);
279         } else {
280             label = getLabelFromDisconnectCause(context, telephonyDisconnectCause);
281         }
282         return label;
283     }
284 
285     /**
286      * Returns a label for to the generic disconnect cause to be shown to the user.
287      */
getLabelFromDisconnectCause( Context context, int telephonyDisconnectCause)288     private static CharSequence getLabelFromDisconnectCause(
289             Context context, int telephonyDisconnectCause) {
290         if (context == null) {
291             return "";
292         }
293 
294         Integer resourceId = null;
295         switch (telephonyDisconnectCause) {
296             case android.telephony.DisconnectCause.BUSY:
297                 resourceId = R.string.callFailed_userBusy;
298                 break;
299 
300             case android.telephony.DisconnectCause.CDMA_REORDER:
301                 resourceId = R.string.callFailed_NetworkBusy;
302                 break;
303 
304             case android.telephony.DisconnectCause.IMS_ACCESS_BLOCKED:
305                 resourceId = R.string.callFailed_NetworkCongested;
306                 break;
307 
308             case android.telephony.DisconnectCause.CONGESTION:
309                 resourceId = R.string.callFailed_congestion;
310                 break;
311 
312             case android.telephony.DisconnectCause.TIMED_OUT:
313                 resourceId = R.string.callFailed_timedOut;
314                 break;
315 
316             case android.telephony.DisconnectCause.SERVER_UNREACHABLE:
317                 resourceId = R.string.callFailed_server_unreachable;
318                 break;
319 
320             case android.telephony.DisconnectCause.NUMBER_UNREACHABLE:
321                 resourceId = R.string.callFailed_number_unreachable;
322                 break;
323 
324             case android.telephony.DisconnectCause.INVALID_CREDENTIALS:
325                 resourceId = R.string.callFailed_invalid_credentials;
326                 break;
327 
328             case android.telephony.DisconnectCause.SERVER_ERROR:
329                 resourceId = R.string.callFailed_server_error;
330                 break;
331 
332             case android.telephony.DisconnectCause.OUT_OF_NETWORK:
333                 resourceId = R.string.callFailed_out_of_network;
334                 break;
335 
336             case android.telephony.DisconnectCause.LOST_SIGNAL:
337             case android.telephony.DisconnectCause.CDMA_DROP:
338                 resourceId = R.string.callFailed_noSignal;
339                 break;
340 
341             case android.telephony.DisconnectCause.LIMIT_EXCEEDED:
342                 resourceId = R.string.callFailed_limitExceeded;
343                 break;
344 
345             case android.telephony.DisconnectCause.POWER_OFF:
346                 resourceId = R.string.callFailed_powerOff;
347                 break;
348 
349             case android.telephony.DisconnectCause.LOW_BATTERY:
350                 resourceId = R.string.callFailed_low_battery;
351                 break;
352 
353             case android.telephony.DisconnectCause.DIAL_LOW_BATTERY:
354                 resourceId = R.string.dialFailed_low_battery;
355                 break;
356 
357             case android.telephony.DisconnectCause.ICC_ERROR:
358                 resourceId = R.string.callFailed_simError;
359                 break;
360 
361             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
362                 resourceId = R.string.callFailed_outOfService;
363                 break;
364 
365             case android.telephony.DisconnectCause.INVALID_NUMBER:
366             case android.telephony.DisconnectCause.UNOBTAINABLE_NUMBER:
367                 resourceId = R.string.callFailed_unobtainable_number;
368                 break;
369 
370             case android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING:
371                 resourceId = R.string.incall_error_missing_voicemail_number;
372                 break;
373 
374             case android.telephony.DisconnectCause.CALL_PULLED:
375                 resourceId = R.string.callEnded_pulled;
376                 break;
377 
378             case android.telephony.DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED:
379                 resourceId = R.string.callFailed_maximum_reached;
380                 break;
381 
382             case android.telephony.DisconnectCause.DATA_DISABLED:
383                 resourceId = R.string.callFailed_data_disabled;
384                 break;
385 
386             case android.telephony.DisconnectCause.DATA_LIMIT_REACHED:
387                 resourceId = R.string.callFailed_data_limit_reached;
388                 break;
389             case android.telephony.DisconnectCause.WIFI_LOST:
390                 resourceId = R.string.callFailed_wifi_lost;
391                 break;
392             case android.telephony.DisconnectCause.ALREADY_DIALING:
393                 resourceId = R.string.callFailed_already_dialing;
394                 break;
395             case android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING:
396                 resourceId = R.string.callFailed_already_ringing;
397                 break;
398             case android.telephony.DisconnectCause.CALLING_DISABLED:
399                 resourceId = R.string.callFailed_calling_disabled;
400                 break;
401             case android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS:
402                 resourceId = R.string.callFailed_too_many_calls;
403                 break;
404             case android.telephony.DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL:
405                 int airplaneMode = Settings.Global.getInt(context.getContentResolver(),
406                         Settings.Global.AIRPLANE_MODE_ON, 0);
407                 resourceId = R.string.incall_error_call_failed;
408                 if (airplaneMode != 0) {
409                     resourceId = R.string.incall_error_power_off;
410                 }
411                 break;
412             case android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS:
413                 resourceId = R.string.callFailed_otasp_provisioning_in_process;
414                 break;
415             case android.telephony.DisconnectCause.EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE:
416                 resourceId = R.string.callFailed_emergency_call_over_wfc_not_available;
417                 break;
418             case android.telephony.DisconnectCause.WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION:
419                 resourceId = R.string.callFailed_wfc_service_not_available_in_this_location;
420                 break;
421             default:
422                 break;
423         }
424         return resourceId == null ? "" : context.getResources().getString(resourceId);
425     }
426 
427     /**
428      * Returns a label for to the precise disconnect cause to be shown to the user.
429      */
getLabelFromPreciseDisconnectCause( Context context, int telephonyPreciseDisconnectCause, int telephonyDisconnectCause)430     private static CharSequence getLabelFromPreciseDisconnectCause(
431             Context context, int telephonyPreciseDisconnectCause, int telephonyDisconnectCause) {
432         if (context == null) {
433             return "";
434         }
435 
436         Integer resourceId = null;
437         switch (telephonyPreciseDisconnectCause) {
438             case CallFailCause.UNOBTAINABLE_NUMBER:
439                 resourceId = R.string.clh_callFailed_unassigned_number_txt;
440                 break;
441             case CallFailCause.NO_ROUTE_TO_DEST:
442                 resourceId = R.string.clh_callFailed_no_route_to_destination_txt;
443                 break;
444             case CallFailCause.CHANNEL_UNACCEPTABLE:
445                 resourceId = R.string.clh_callFailed_channel_unacceptable_txt;
446                 break;
447             case CallFailCause.OPERATOR_DETERMINED_BARRING:
448                 resourceId = R.string.clh_callFailed_operator_determined_barring_txt;
449                 break;
450             case CallFailCause.NORMAL_CLEARING:
451                 resourceId = R.string.clh_callFailed_normal_call_clearing_txt;
452                 break;
453             case CallFailCause.USER_BUSY:
454                 resourceId = R.string.clh_callFailed_user_busy_txt;
455                 break;
456             case CallFailCause.NO_USER_RESPONDING:
457                 resourceId = R.string.clh_callFailed_no_user_responding_txt;
458                 break;
459             case CallFailCause.USER_ALERTING_NO_ANSWER:
460                 resourceId = R.string.clh_callFailed_user_alerting_txt;
461                 break;
462             case CallFailCause.CALL_REJECTED:
463                 resourceId = R.string.clh_callFailed_call_rejected_txt;
464                 break;
465             case CallFailCause.NUMBER_CHANGED:
466                 resourceId = R.string.clh_callFailed_number_changed_txt;
467                 break;
468             case CallFailCause.PRE_EMPTION:
469                 resourceId = R.string.clh_callFailed_pre_emption_txt;
470                 break;
471             case CallFailCause.NON_SELECTED_USER_CLEARING:
472                 resourceId = R.string.clh_callFailed_non_selected_user_clearing_txt;
473                 break;
474             case CallFailCause.DESTINATION_OUT_OF_ORDER:
475                 resourceId = R.string.clh_callFailed_destination_out_of_order_txt;
476                 break;
477             case CallFailCause.INVALID_NUMBER_FORMAT:
478                 resourceId = R.string.clh_callFailed_invalid_number_format_txt;
479                 break;
480             case CallFailCause.FACILITY_REJECTED:
481                 resourceId = R.string.clh_callFailed_facility_rejected_txt;
482                 break;
483             case CallFailCause.STATUS_ENQUIRY:
484                 resourceId = R.string.clh_callFailed_response_to_STATUS_ENQUIRY_txt;
485                 break;
486             case CallFailCause.NORMAL_UNSPECIFIED:
487                 resourceId = R.string.clh_callFailed_normal_unspecified_txt;
488                 break;
489             case CallFailCause.NO_CIRCUIT_AVAIL:
490                 resourceId = R.string.clh_callFailed_no_circuit_available_txt;
491                 break;
492             case CallFailCause.NETWORK_OUT_OF_ORDER:
493                 resourceId = R.string.clh_callFailed_network_out_of_order_txt;
494                 break;
495             case CallFailCause.TEMPORARY_FAILURE:
496                 resourceId = R.string.clh_callFailed_temporary_failure_txt;
497                 break;
498             case CallFailCause.SWITCHING_CONGESTION:
499                 resourceId = R.string.clh_callFailed_switching_equipment_congestion_txt;
500                 break;
501             case CallFailCause.ACCESS_INFORMATION_DISCARDED:
502                 resourceId = R.string.clh_callFailed_access_information_discarded_txt;
503                 break;
504             case CallFailCause.CHANNEL_NOT_AVAIL:
505                 resourceId = R.string.clh_callFailed_requested_circuit_txt;
506                 break;
507             case CallFailCause.RESOURCES_UNAVAILABLE_UNSPECIFIED:
508                 resourceId = R.string.clh_callFailed_resources_unavailable_unspecified_txt;
509                 break;
510             case CallFailCause.QOS_NOT_AVAIL:
511                 resourceId = R.string.clh_callFailed_quality_of_service_unavailable_txt;
512                 break;
513             case CallFailCause.REQUESTED_FACILITY_NOT_SUBSCRIBED:
514                 resourceId = R.string.clh_callFailed_requested_facility_not_subscribed_txt;
515                 break;
516             case CallFailCause.INCOMING_CALL_BARRED_WITHIN_CUG:
517                 resourceId = R.string.clh_callFailed_incoming_calls_barred_within_the_CUG_txt;
518                 break;
519             case CallFailCause.BEARER_CAPABILITY_NOT_AUTHORISED:
520                 resourceId = R.string.clh_callFailed_bearer_capability_not_authorized_txt;
521                 break;
522             case CallFailCause.BEARER_NOT_AVAIL:
523                 resourceId = R.string.clh_callFailed_bearer_capability_not_presently_available_txt;
524                 break;
525             case CallFailCause.SERVICE_OR_OPTION_NOT_AVAILABLE:
526                 resourceId =
527                         R.string.clh_callFailed_service_or_option_not_available_unspecified_txt;
528                 break;
529             case CallFailCause.BEARER_SERVICE_NOT_IMPLEMENTED:
530                 resourceId = R.string.clh_callFailed_bearer_service_not_implemented_txt;
531                 break;
532             case CallFailCause.ACM_LIMIT_EXCEEDED:
533                 resourceId = R.string.clh_callFailed_ACM_equal_to_or_greater_than_ACMmax_txt;
534                 break;
535             case CallFailCause.REQUESTED_FACILITY_NOT_IMPLEMENTED:
536                 resourceId = R.string.clh_callFailed_requested_facility_not_implemented_txt;
537                 break;
538             case CallFailCause.ONLY_RESTRICTED_DIGITAL_INFO_BC_AVAILABLE:
539                 resourceId = R.string
540                         .clh_callFailed_only_restricted_digital_information_bearer_capability_is_available_txt;
541                 break;
542             case CallFailCause.SERVICE_OR_OPTION_NOT_IMPLEMENTED:
543                 resourceId =
544                         R.string.clh_callFailed_service_or_option_not_implemented_unspecified_txt;
545                 break;
546             case CallFailCause.INVALID_TRANSACTION_ID_VALUE:
547                 resourceId = R.string.clh_callFailed_invalid_transaction_identifier_value_txt;
548                 break;
549             case CallFailCause.USER_NOT_MEMBER_OF_CUG:
550                 resourceId = R.string.clh_callFailed_user_not_member_of_CUG_txt;
551                 break;
552             case CallFailCause.INCOMPATIBLE_DESTINATION:
553                 resourceId = R.string.clh_callFailed_incompatible_destination_txt;
554                 break;
555             case CallFailCause.INVALID_TRANSIT_NETWORK_SELECTION:
556                 resourceId = R.string.clh_callFailed_invalid_transit_network_selection_txt;
557                 break;
558             case CallFailCause.SEMANTICALLY_INCORRECT_MESSAGE:
559                 resourceId = R.string.clh_callFailed_semantically_incorrect_message_txt;
560                 break;
561             case CallFailCause.INVALID_MANDATORY_INFORMATION:
562                 resourceId = R.string.clh_callFailed_invalid_mandatory_information_txt;
563                 break;
564             case CallFailCause.MESSAGE_TYPE_NON_EXISTENT:
565                 resourceId =
566                         R.string.clh_callFailed_message_type_non_existent_or_not_implemented_txt;
567                 break;
568             case CallFailCause.MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROT_STATE:
569                 resourceId = R.string
570                         .clh_callFailed_message_type_not_compatible_with_protocol_state_txt;
571                 break;
572             case CallFailCause.IE_NON_EXISTENT_OR_NOT_IMPLEMENTED:
573                 resourceId = R.string
574                         .clh_callFailed_information_element_non_existent_or_not_implemented_txt;
575                 break;
576             case CallFailCause.CONDITIONAL_IE_ERROR:
577                 resourceId = R.string.clh_callFailed_conditional_IE_error_txt;
578                 break;
579             case CallFailCause.MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE:
580                 resourceId = R.string.clh_callFailed_message_not_compatible_with_protocol_state_txt;
581                 break;
582             case CallFailCause.RECOVERY_ON_TIMER_EXPIRY:
583                 resourceId = R.string.clh_callFailed_recovery_on_timer_expiry_txt;
584                 break;
585             case CallFailCause.PROTOCOL_ERROR_UNSPECIFIED:
586                 resourceId = R.string.clh_callFailed_protocol_Error_unspecified_txt;
587                 break;
588             case CallFailCause.INTERWORKING_UNSPECIFIED:
589                 resourceId = R.string.clh_callFailed_interworking_unspecified_txt;
590                 break;
591             default:
592                 switch (telephonyDisconnectCause) {
593                     case android.telephony.DisconnectCause.POWER_OFF:
594                         resourceId = R.string.clh_callFailed_powerOff_txt;
595                         break;
596                     case android.telephony.DisconnectCause.ICC_ERROR:
597                         resourceId = R.string.clh_callFailed_simError_txt;
598                         break;
599                     case android.telephony.DisconnectCause.OUT_OF_SERVICE:
600                         resourceId = R.string.clh_incall_error_out_of_service_txt;
601                         break;
602                     default:
603                         resourceId = R.string.clh_card_title_call_ended_txt;
604                         break;
605                 }
606                 break;
607         }
608         return context.getResources().getString(resourceId);
609     }
610 
611     /**
612      * Returns a description of the disconnect cause to be shown to the user.
613      */
toTelecomDisconnectCauseDescription( Context context, int telephonyDisconnectCause, int phoneId)614     private static CharSequence toTelecomDisconnectCauseDescription(
615             Context context, int telephonyDisconnectCause, int phoneId) {
616         if (context == null ) {
617             return "";
618         }
619 
620         Integer resourceId = null;
621         switch (telephonyDisconnectCause) {
622             case android.telephony.DisconnectCause.CALL_BARRED:
623                 resourceId = R.string.callFailed_cb_enabled;
624                 break;
625 
626             case android.telephony.DisconnectCause.CDMA_ALREADY_ACTIVATED:
627                 resourceId = R.string.callFailed_cdma_activation;
628                 break;
629 
630             case android.telephony.DisconnectCause.CDMA_REORDER:
631                 resourceId = R.string.callFailed_NetworkBusy;
632                 break;
633 
634             case android.telephony.DisconnectCause.IMS_ACCESS_BLOCKED:
635                 resourceId = R.string.callFailed_NetworkCongested;
636                 break;
637 
638             case android.telephony.DisconnectCause.FDN_BLOCKED:
639                 resourceId = R.string.callFailed_fdn_only;
640                 break;
641 
642             case android.telephony.DisconnectCause.CS_RESTRICTED:
643                 resourceId = R.string.callFailed_dsac_restricted;
644                 break;
645 
646             case android.telephony.DisconnectCause.CS_RESTRICTED_EMERGENCY:
647                 resourceId = R.string.callFailed_dsac_restricted_emergency;
648                 break;
649 
650             case android.telephony.DisconnectCause.CS_RESTRICTED_NORMAL:
651                 resourceId = R.string.callFailed_dsac_restricted_normal;
652                 break;
653 
654             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_USSD:
655                 resourceId = R.string.callFailed_dialToUssd;
656                 break;
657 
658             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_SS:
659                 resourceId = R.string.callFailed_dialToSs;
660                 break;
661 
662             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_DIAL:
663                 resourceId = R.string.callFailed_dialToDial;
664                 break;
665 
666             case android.telephony.DisconnectCause.DIAL_MODIFIED_TO_DIAL_VIDEO:
667                 resourceId = R.string.callFailed_dialToDialVideo;
668                 break;
669 
670             case android.telephony.DisconnectCause.DIAL_VIDEO_MODIFIED_TO_SS:
671                 resourceId = R.string.callFailed_dialVideoToSs;
672                 break;
673 
674             case android.telephony.DisconnectCause.DIAL_VIDEO_MODIFIED_TO_USSD:
675                 resourceId = R.string.callFailed_dialVideoToUssd;
676                 break;
677 
678             case android.telephony.DisconnectCause.DIAL_VIDEO_MODIFIED_TO_DIAL:
679                 resourceId = R.string.callFailed_dialVideoToDial;
680                 break;
681 
682             case android.telephony.DisconnectCause.DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO:
683                 resourceId = R.string.callFailed_dialVideoToDialVideo;
684                 break;
685 
686             case android.telephony.DisconnectCause.OUTGOING_FAILURE:
687                 // We couldn't successfully place the call; there was some
688                 // failure in the telephony layer.
689                 // TODO: Need UI spec for this failure case; for now just
690                 // show a generic error.
691                 resourceId = R.string.incall_error_call_failed;
692                 break;
693 
694             case android.telephony.DisconnectCause.POWER_OFF:
695                 // Radio is explicitly powered off because the device's radio is off.
696 
697                 // TODO: Offer the option to turn the radio on, and automatically retry the call
698                 // once network registration is complete.
699 
700                 if (isRadioOffForThermalMitigation(phoneId)) {
701                     resourceId = R.string.incall_error_power_off_thermal;
702                 } else if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
703                     resourceId = R.string.incall_error_promote_wfc;
704                 } else if (ImsUtil.isWfcModeWifiOnly(context, phoneId)) {
705                     resourceId = R.string.incall_error_wfc_only_no_wireless_network;
706                 } else if (ImsUtil.isWfcEnabled(context, phoneId)) {
707                     resourceId = R.string.incall_error_power_off_wfc;
708                 } else {
709                     resourceId = R.string.incall_error_power_off;
710                 }
711                 break;
712 
713             case android.telephony.DisconnectCause.LOW_BATTERY:
714                 resourceId = R.string.callFailed_low_battery;
715                 break;
716 
717             case android.telephony.DisconnectCause.DIAL_LOW_BATTERY:
718                 resourceId = R.string.dialFailed_low_battery;
719                 break;
720 
721             case android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY:
722                 // Only emergency calls are allowed when in emergency callback mode.
723                 resourceId = R.string.incall_error_ecm_emergency_only;
724                 break;
725 
726             case android.telephony.DisconnectCause.EMERGENCY_ONLY:
727                 // Only emergency numbers are allowed, but we tried to dial
728                 // a non-emergency number.
729                 resourceId = R.string.incall_error_emergency_only;
730                 break;
731 
732             case android.telephony.DisconnectCause.ICC_ERROR:
733                 resourceId = R.string.callFailed_simError;
734                 break;
735 
736             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
737                 // No network connection.
738                 if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
739                     resourceId = R.string.incall_error_promote_wfc;
740                 } else if (ImsUtil.isWfcModeWifiOnly(context, phoneId)) {
741                     resourceId = R.string.incall_error_wfc_only_no_wireless_network;
742                 } else if (ImsUtil.isWfcEnabled(context, phoneId)) {
743                     resourceId = R.string.incall_error_out_of_service_wfc;
744                 } else {
745                     resourceId = R.string.incall_error_out_of_service;
746                 }
747                 break;
748 
749             case android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED:
750                 // The supplied Intent didn't contain a valid phone number.
751                 // (This is rare and should only ever happen with broken
752                 // 3rd-party apps.) For now just show a generic error.
753                 resourceId = R.string.incall_error_no_phone_number_supplied;
754                 break;
755 
756             case android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING:
757                 resourceId = R.string.incall_error_missing_voicemail_number;
758                 break;
759 
760             case android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
761                 resourceId = R.string.callFailed_video_call_tty_enabled;
762                 break;
763 
764             case android.telephony.DisconnectCause.CALL_PULLED:
765                 resourceId = R.string.callEnded_pulled;
766                 break;
767 
768             case android.telephony.DisconnectCause.MAXIMUM_NUMBER_OF_CALLS_REACHED:
769                 resourceId = R.string.callFailed_maximum_reached;
770 
771             case android.telephony.DisconnectCause.OUTGOING_CANCELED:
772                 // We don't want to show any dialog for the canceled case since the call was
773                 // either canceled by the user explicitly (end-call button pushed immediately)
774                 // or some other app canceled the call and immediately issued a new CALL to
775                 // replace it.
776                 break;
777 
778             case android.telephony.DisconnectCause.DATA_DISABLED:
779                 resourceId = R.string.callFailed_data_disabled;
780                 break;
781 
782             case android.telephony.DisconnectCause.DATA_LIMIT_REACHED:
783                 resourceId = R.string.callFailed_data_limit_reached_description;
784                 break;
785             case android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING:
786                 resourceId = com.android.internal.R.string.mmiErrorWhileRoaming;
787                 break;
788 
789             case android.telephony.DisconnectCause.IMEI_NOT_ACCEPTED:
790                 resourceId = R.string.callFailed_imei_not_accepted;
791                 break;
792 
793             case android.telephony.DisconnectCause.WIFI_LOST:
794                 resourceId = R.string.callFailed_wifi_lost;
795                 break;
796             case android.telephony.DisconnectCause.ALREADY_DIALING:
797                 resourceId = R.string.callFailed_already_dialing;
798                 break;
799             case android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING:
800                 resourceId = R.string.callFailed_already_ringing;
801                 break;
802             case android.telephony.DisconnectCause.CALLING_DISABLED:
803                 resourceId = R.string.callFailed_calling_disabled;
804                 break;
805             case android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS:
806                 resourceId = R.string.callFailed_too_many_calls;
807                 break;
808             case android.telephony.DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL:
809                 int airplaneMode = Settings.Global.getInt(context.getContentResolver(),
810                         Settings.Global.AIRPLANE_MODE_ON, 0);
811                 resourceId = R.string.incall_error_call_failed;
812                 if (airplaneMode != 0) {
813                     resourceId = R.string.incall_error_power_off;
814                 }
815                 break;
816             case android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS:
817                 resourceId = R.string.callFailed_otasp_provisioning_in_process;
818                 break;
819             case android.telephony.DisconnectCause.EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE:
820                 resourceId = R.string.callFailed_emergency_call_over_wfc_not_available;
821                 break;
822             case android.telephony.DisconnectCause.WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION:
823                 resourceId = R.string.callFailed_wfc_service_not_available_in_this_location;
824                 break;
825             default:
826                 break;
827         }
828         return resourceId == null ? "" : context.getResources().getString(resourceId);
829     }
830 
isRadioOffForThermalMitigation(int phoneId)831     private static boolean isRadioOffForThermalMitigation(int phoneId) {
832         Phone phone = PhoneFactory.getPhone(phoneId);
833         return phone == null ? false : phone.isRadioOffForThermalMitigation();
834     }
835 
836     /**
837      * Maps the telephony {@link android.telephony.DisconnectCause} into a reason string which is
838      * returned in the Telecom {@link DisconnectCause#getReason()}.
839      *
840      * @param context The current context.
841      * @param telephonyDisconnectCause The {@link android.telephony.DisconnectCause} code.
842      * @param reason A reason provided by the caller; only used if a more specific reason cannot
843      *               be determined here.
844      * @return The disconnect reason.
845      */
toTelecomDisconnectReason(Context context, int telephonyDisconnectCause, String reason, int phoneId)846     private static String toTelecomDisconnectReason(Context context, int telephonyDisconnectCause,
847             String reason, int phoneId) {
848 
849         if (context == null) {
850             return "";
851         }
852 
853         switch (telephonyDisconnectCause) {
854             case android.telephony.DisconnectCause.POWER_OFF:
855                 // Airplane mode (radio off)
856                 // intentional fall-through
857             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
858                 // No network connection.
859                 if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
860                     return android.telecom.DisconnectCause.REASON_WIFI_ON_BUT_WFC_OFF;
861                 }
862                 break;
863             case android.telephony.DisconnectCause.IMS_ACCESS_BLOCKED:
864                 return DisconnectCause.REASON_IMS_ACCESS_BLOCKED;
865             case android.telephony.DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED:
866                 return DisconnectCause.REASON_EMERGENCY_CALL_PLACED;
867         }
868 
869         // If no specific code-mapping found, then fall back to using the reason.
870         String causeAsString = android.telephony.DisconnectCause.toString(telephonyDisconnectCause);
871         if (reason == null) {
872             return causeAsString;
873         } else {
874             return reason + ", " + causeAsString;
875         }
876     }
877 
878     /**
879      * Returns the tone to play for the disconnect cause, or UNKNOWN if none should be played.
880      */
toTelecomDisconnectCauseTone(int telephonyDisconnectCause, PersistableBundle carrierConfig)881     private static int toTelecomDisconnectCauseTone(int telephonyDisconnectCause,
882             PersistableBundle carrierConfig) {
883 
884         // special case: some carriers determine what disconnect causes play the BUSY tone.
885         if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
886                 carrierConfig)) {
887             return ToneGenerator.TONE_SUP_BUSY;
888         }
889 
890         switch (telephonyDisconnectCause) {
891             case android.telephony.DisconnectCause.CONGESTION:
892                 return ToneGenerator.TONE_SUP_CONGESTION;
893 
894             case android.telephony.DisconnectCause.CDMA_REORDER:
895                 return ToneGenerator.TONE_CDMA_REORDER;
896 
897             case android.telephony.DisconnectCause.CDMA_INTERCEPT:
898                 return ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
899 
900             case android.telephony.DisconnectCause.CDMA_DROP:
901             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
902                 return ToneGenerator.TONE_CDMA_CALLDROP_LITE;
903 
904             case android.telephony.DisconnectCause.UNOBTAINABLE_NUMBER:
905                 return ToneGenerator.TONE_SUP_ERROR;
906 
907             case android.telephony.DisconnectCause.IMS_MERGED_SUCCESSFULLY:
908                 // Do not play any tones if disconnected because of a successful merge.
909                 return -1;
910 
911             case android.telephony.DisconnectCause.ERROR_UNSPECIFIED:
912             case android.telephony.DisconnectCause.LOCAL:
913             case android.telephony.DisconnectCause.NORMAL:
914             case android.telephony.DisconnectCause.NORMAL_UNSPECIFIED:
915             case android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
916             case android.telephony.DisconnectCause.WIFI_LOST:
917             default:
918                 return ToneGenerator.TONE_PROP_PROMPT;
919         }
920     }
921 
922     /**
923      * Helper method that examines the carrierConfig KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY
924      * containing the DisconnectCauses that are classified as DisconnectCause.BUSY
925      * @param telephonyDisconnectCause
926      * @param carrierConfig object that holds all the carrier specific settings
927      * @return whether the cause is in the carrier config busy tone array
928      */
doesCarrierClassifyDisconnectCauseAsBusyCause( int telephonyDisconnectCause, PersistableBundle carrierConfig)929     private static boolean doesCarrierClassifyDisconnectCauseAsBusyCause(
930             int telephonyDisconnectCause, PersistableBundle carrierConfig) {
931         int[] busyToneArray = carrierConfig.getIntArray(
932                 CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY);
933         for (int busyTone : busyToneArray) {
934             if (busyTone == telephonyDisconnectCause) {
935                 return true;
936             }
937         }
938         return false;
939     }
940 
getCarrierConfigBundle(int phoneId)941     private static PersistableBundle getCarrierConfigBundle(int phoneId) {
942         Phone phone = PhoneFactory.getPhone(phoneId);
943         PersistableBundle config;
944 
945         if (phone != null) {
946             config = PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
947         } else {
948             config = PhoneGlobals.getInstance().getCarrierConfig();
949         }
950 
951         return config;
952     }
953 
954 }
955