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