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