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