Home | History | Annotate | Download | only in phone
      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.telephony.PhoneNumberUtils;
     16 import android.telephony.TelephonyManager;
     17 import android.text.BidiFormatter;
     18 import android.text.SpannableString;
     19 import android.text.TextDirectionHeuristics;
     20 import android.text.TextUtils;
     21 import android.util.AttributeSet;
     22 import android.util.Log;
     23 import android.view.View;
     24 
     25 import static com.android.phone.TimeConsumingPreferenceActivity.RESPONSE_ERROR;
     26 import static com.android.phone.TimeConsumingPreferenceActivity.EXCEPTION_ERROR;
     27 
     28 public 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