• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.phone;
2 
3 import com.android.internal.telephony.CallForwardInfo;
4 import com.android.internal.telephony.CommandException;
5 import com.android.internal.telephony.CommandsInterface;
6 import com.android.internal.telephony.Phone;
7 
8 import android.app.AlertDialog;
9 import android.content.Context;
10 import android.content.DialogInterface;
11 import android.content.res.TypedArray;
12 import android.os.AsyncResult;
13 import android.os.Handler;
14 import android.os.Message;
15 import android.os.PersistableBundle;
16 import android.telephony.CarrierConfigManager;
17 import android.telephony.PhoneNumberUtils;
18 import android.telephony.TelephonyManager;
19 import android.text.BidiFormatter;
20 import android.text.SpannableString;
21 import android.text.TextDirectionHeuristics;
22 import android.text.TextUtils;
23 import android.util.AttributeSet;
24 import android.util.Log;
25 import android.view.View;
26 
27 import static com.android.phone.TimeConsumingPreferenceActivity.RESPONSE_ERROR;
28 import static com.android.phone.TimeConsumingPreferenceActivity.EXCEPTION_ERROR;
29 
30 public class CallForwardEditPreference extends EditPhoneNumberPreference {
31     private static final String LOG_TAG = "CallForwardEditPreference";
32 
33     private static final String SRC_TAGS[]       = {"{0}"};
34     private CharSequence mSummaryOnTemplate;
35     /**
36      * Remembers which button was clicked by a user. If no button is clicked yet, this should have
37      * {@link DialogInterface#BUTTON_NEGATIVE}, meaning "cancel".
38      *
39      * TODO: consider removing this variable and having getButtonClicked() in
40      * EditPhoneNumberPreference instead.
41      */
42     private int mButtonClicked;
43     private int mServiceClass;
44     private MyHandler mHandler = new MyHandler();
45     int reason;
46     private Phone mPhone;
47     CallForwardInfo callForwardInfo;
48     private TimeConsumingPreferenceListener mTcpListener;
49     // Should we replace CF queries containing an invalid number with "Voicemail"
50     private boolean mReplaceInvalidCFNumber = false;
51 
CallForwardEditPreference(Context context, AttributeSet attrs)52     public CallForwardEditPreference(Context context, AttributeSet attrs) {
53         super(context, attrs);
54 
55         mSummaryOnTemplate = this.getSummaryOn();
56 
57         TypedArray a = context.obtainStyledAttributes(attrs,
58                 R.styleable.CallForwardEditPreference, 0, R.style.EditPhoneNumberPreference);
59         mServiceClass = a.getInt(R.styleable.CallForwardEditPreference_serviceClass,
60                 CommandsInterface.SERVICE_CLASS_VOICE);
61         reason = a.getInt(R.styleable.CallForwardEditPreference_reason,
62                 CommandsInterface.CF_REASON_UNCONDITIONAL);
63         a.recycle();
64 
65         Log.d(LOG_TAG, "mServiceClass=" + mServiceClass + ", reason=" + reason);
66     }
67 
CallForwardEditPreference(Context context)68     public CallForwardEditPreference(Context context) {
69         this(context, null);
70     }
71 
init(TimeConsumingPreferenceListener listener, boolean skipReading, Phone phone, boolean replaceInvalidCFNumber)72     void init(TimeConsumingPreferenceListener listener, boolean skipReading, Phone phone,
73             boolean replaceInvalidCFNumber) {
74         mPhone = phone;
75         mTcpListener = listener;
76         mReplaceInvalidCFNumber = replaceInvalidCFNumber;
77 
78         if (!skipReading) {
79             mPhone.getCallForwardingOption(reason,
80                     mHandler.obtainMessage(MyHandler.MESSAGE_GET_CF,
81                             // unused in this case
82                             CommandsInterface.CF_ACTION_DISABLE,
83                             MyHandler.MESSAGE_GET_CF, null));
84             if (mTcpListener != null) {
85                 mTcpListener.onStarted(this, true);
86             }
87         }
88     }
89 
90     @Override
onBindDialogView(View view)91     protected void onBindDialogView(View view) {
92         // default the button clicked to be the cancel button.
93         mButtonClicked = DialogInterface.BUTTON_NEGATIVE;
94         super.onBindDialogView(view);
95     }
96 
97     @Override
onClick(DialogInterface dialog, int which)98     public void onClick(DialogInterface dialog, int which) {
99         super.onClick(dialog, which);
100         mButtonClicked = which;
101     }
102 
103     @Override
onDialogClosed(boolean positiveResult)104     protected void onDialogClosed(boolean positiveResult) {
105         super.onDialogClosed(positiveResult);
106 
107         Log.d(LOG_TAG, "mButtonClicked=" + mButtonClicked + ", positiveResult=" + positiveResult);
108         // Ignore this event if the user clicked the cancel button, or if the dialog is dismissed
109         // without any button being pressed (back button press or click event outside the dialog).
110         if (this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
111             int action = (isToggled() || (mButtonClicked == DialogInterface.BUTTON_POSITIVE)) ?
112                     CommandsInterface.CF_ACTION_REGISTRATION :
113                     CommandsInterface.CF_ACTION_DISABLE;
114             int time = (reason != CommandsInterface.CF_REASON_NO_REPLY) ? 0 : 20;
115             final String number = getPhoneNumber();
116 
117             Log.d(LOG_TAG, "callForwardInfo=" + callForwardInfo);
118 
119             if (action == CommandsInterface.CF_ACTION_REGISTRATION
120                     && callForwardInfo != null
121                     && callForwardInfo.status == 1
122                     && number.equals(callForwardInfo.number)) {
123                 // no change, do nothing
124                 Log.d(LOG_TAG, "no change, do nothing");
125             } else {
126                 // set to network
127                 Log.d(LOG_TAG, "reason=" + reason + ", action=" + action
128                         + ", number=" + number);
129 
130                 // Display no forwarding number while we're waiting for
131                 // confirmation
132                 setSummaryOn("");
133 
134                 // the interface of Phone.setCallForwardingOption has error:
135                 // should be action, reason...
136                 mPhone.setCallForwardingOption(action,
137                         reason,
138                         number,
139                         time,
140                         mHandler.obtainMessage(MyHandler.MESSAGE_SET_CF,
141                                 action,
142                                 MyHandler.MESSAGE_SET_CF));
143 
144                 if (mTcpListener != null) {
145                     mTcpListener.onStarted(this, false);
146                 }
147             }
148         }
149     }
150 
handleCallForwardResult(CallForwardInfo cf)151     void handleCallForwardResult(CallForwardInfo cf) {
152         callForwardInfo = cf;
153         Log.d(LOG_TAG, "handleGetCFResponse done, callForwardInfo=" + callForwardInfo);
154         // In some cases, the network can send call forwarding URIs for voicemail that violate the
155         // 3gpp spec. This can cause us to receive "numbers" that are sequences of letters. In this
156         // case, we must detect these series of characters and replace them with "Voicemail".
157         // PhoneNumberUtils#formatNumber returns null if the number is not valid.
158         if (mReplaceInvalidCFNumber && (PhoneNumberUtils.formatNumber(callForwardInfo.number,
159                 getCurrentCountryIso()) == null)) {
160             callForwardInfo.number = getContext().getString(R.string.voicemail);
161             Log.i(LOG_TAG, "handleGetCFResponse: Overridding CF number");
162         }
163 
164         setToggled(callForwardInfo.status == 1);
165         boolean displayVoicemailNumber = false;
166         if (TextUtils.isEmpty(callForwardInfo.number)) {
167             PersistableBundle carrierConfig =
168                     PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
169             if (carrierConfig != null) {
170                 displayVoicemailNumber = carrierConfig.getBoolean(CarrierConfigManager
171                         .KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL);
172                 Log.d(LOG_TAG, "display voicemail number as default");
173             }
174         }
175         String voicemailNumber = mPhone.getVoiceMailNumber();
176         setPhoneNumber(displayVoicemailNumber ? voicemailNumber : callForwardInfo.number);
177     }
178 
updateSummaryText()179     private void updateSummaryText() {
180         if (isToggled()) {
181             final String number = getRawPhoneNumber();
182             if (number != null && number.length() > 0) {
183                 // Wrap the number to preserve presentation in RTL languages.
184                 String wrappedNumber = BidiFormatter.getInstance().unicodeWrap(
185                         number, TextDirectionHeuristics.LTR);
186                 String values[] = { wrappedNumber };
187                 String summaryOn = String.valueOf(
188                         TextUtils.replace(mSummaryOnTemplate, SRC_TAGS, values));
189                 int start = summaryOn.indexOf(wrappedNumber);
190 
191                 SpannableString spannableSummaryOn = new SpannableString(summaryOn);
192                 PhoneNumberUtils.addTtsSpan(spannableSummaryOn,
193                         start, start + wrappedNumber.length());
194                 setSummaryOn(spannableSummaryOn);
195             } else {
196                 setSummaryOn(getContext().getString(R.string.sum_cfu_enabled_no_number));
197             }
198         }
199 
200     }
201 
202     /**
203      * @return The ISO 3166-1 two letters country code of the country the user is in based on the
204      *      network location.
205      */
getCurrentCountryIso()206     private String getCurrentCountryIso() {
207         final TelephonyManager telephonyManager =
208                 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
209         if (telephonyManager == null) {
210             return "";
211         }
212         return telephonyManager.getNetworkCountryIso().toUpperCase();
213     }
214 
215     // Message protocol:
216     // what: get vs. set
217     // arg1: action -- register vs. disable
218     // arg2: get vs. set for the preceding request
219     private class MyHandler extends Handler {
220         static final int MESSAGE_GET_CF = 0;
221         static final int MESSAGE_SET_CF = 1;
222 
223         @Override
handleMessage(Message msg)224         public void handleMessage(Message msg) {
225             switch (msg.what) {
226                 case MESSAGE_GET_CF:
227                     handleGetCFResponse(msg);
228                     break;
229                 case MESSAGE_SET_CF:
230                     handleSetCFResponse(msg);
231                     break;
232             }
233         }
234 
handleGetCFResponse(Message msg)235         private void handleGetCFResponse(Message msg) {
236             Log.d(LOG_TAG, "handleGetCFResponse: done");
237 
238             mTcpListener.onFinished(CallForwardEditPreference.this, msg.arg2 != MESSAGE_SET_CF);
239 
240             AsyncResult ar = (AsyncResult) msg.obj;
241 
242             callForwardInfo = null;
243             if (ar.exception != null) {
244                 Log.d(LOG_TAG, "handleGetCFResponse: ar.exception=" + ar.exception);
245                 if (ar.exception instanceof CommandException) {
246                     mTcpListener.onException(CallForwardEditPreference.this,
247                             (CommandException) ar.exception);
248                 } else {
249                     // Most likely an ImsException and we can't handle it the same way as
250                     // a CommandException. The best we can do is to handle the exception
251                     // the same way as mTcpListener.onException() does when it is not of type
252                     // FDN_CHECK_FAILURE.
253                     mTcpListener.onError(CallForwardEditPreference.this, EXCEPTION_ERROR);
254                 }
255             } else {
256                 if (ar.userObj instanceof Throwable) {
257                     mTcpListener.onError(CallForwardEditPreference.this, RESPONSE_ERROR);
258                 }
259                 CallForwardInfo cfInfoArray[] = (CallForwardInfo[]) ar.result;
260                 if (cfInfoArray.length == 0) {
261                     Log.d(LOG_TAG, "handleGetCFResponse: cfInfoArray.length==0");
262                     setEnabled(false);
263                     mTcpListener.onError(CallForwardEditPreference.this, RESPONSE_ERROR);
264                 } else {
265                     for (int i = 0, length = cfInfoArray.length; i < length; i++) {
266                         Log.d(LOG_TAG, "handleGetCFResponse, cfInfoArray[" + i + "]="
267                                 + cfInfoArray[i]);
268                         if ((mServiceClass & cfInfoArray[i].serviceClass) != 0) {
269                             // corresponding class
270                             CallForwardInfo info = cfInfoArray[i];
271                             handleCallForwardResult(info);
272 
273                             // Show an alert if we got a success response but
274                             // with unexpected values.
275                             // Currently only handle the fail-to-disable case
276                             // since we haven't observed fail-to-enable.
277                             if (msg.arg2 == MESSAGE_SET_CF &&
278                                     msg.arg1 == CommandsInterface.CF_ACTION_DISABLE &&
279                                     info.status == 1) {
280                                 // Skip showing error dialog since some operators return
281                                 // active status even if disable call forward succeeded.
282                                 // And they don't like the error dialog.
283                                 if (isSkipCFFailToDisableDialog()) {
284                                     Log.d(LOG_TAG, "Skipped Callforwarding fail-to-disable dialog");
285                                     continue;
286                                 }
287                                 CharSequence s;
288                                 switch (reason) {
289                                     case CommandsInterface.CF_REASON_BUSY:
290                                         s = getContext().getText(R.string.disable_cfb_forbidden);
291                                         break;
292                                     case CommandsInterface.CF_REASON_NO_REPLY:
293                                         s = getContext().getText(R.string.disable_cfnry_forbidden);
294                                         break;
295                                     default: // not reachable
296                                         s = getContext().getText(R.string.disable_cfnrc_forbidden);
297                                 }
298                                 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
299                                 builder.setNeutralButton(R.string.close_dialog, null);
300                                 builder.setTitle(getContext().getText(R.string.error_updating_title));
301                                 builder.setMessage(s);
302                                 builder.setCancelable(true);
303                                 builder.create().show();
304                             }
305                         }
306                     }
307                 }
308             }
309 
310             // Now whether or not we got a new number, reset our enabled
311             // summary text since it may have been replaced by an empty
312             // placeholder.
313             updateSummaryText();
314         }
315 
handleSetCFResponse(Message msg)316         private void handleSetCFResponse(Message msg) {
317             AsyncResult ar = (AsyncResult) msg.obj;
318 
319             if (ar.exception != null) {
320                 Log.d(LOG_TAG, "handleSetCFResponse: ar.exception=" + ar.exception);
321                 // setEnabled(false);
322             }
323             Log.d(LOG_TAG, "handleSetCFResponse: re get");
324             mPhone.getCallForwardingOption(reason,
325                     obtainMessage(MESSAGE_GET_CF, msg.arg1, MESSAGE_SET_CF, ar.exception));
326         }
327     }
328 
329     /*
330      * Get the config of whether skip showing CF fail-to-disable dialog
331      * from carrier config manager.
332      *
333      * @return boolean value of the config
334      */
isSkipCFFailToDisableDialog()335     private boolean isSkipCFFailToDisableDialog() {
336         PersistableBundle carrierConfig =
337                 PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
338         if (carrierConfig != null) {
339             return carrierConfig.getBoolean(
340                     CarrierConfigManager.KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL);
341         } else {
342             // by default we should not skip
343             return false;
344         }
345     }
346 }
347