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 CallForwardEditPreference(Context context, AttributeSet attrs)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 CallForwardEditPreference(Context context)66 public CallForwardEditPreference(Context context) { 67 this(context, null); 68 } 69 init(TimeConsumingPreferenceListener listener, boolean skipReading, Phone phone, boolean replaceInvalidCFNumber)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 onBindDialogView(View view)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 onClick(DialogInterface dialog, int which)96 public void onClick(DialogInterface dialog, int which) { 97 super.onClick(dialog, which); 98 mButtonClicked = which; 99 } 100 101 @Override onDialogClosed(boolean positiveResult)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 handleCallForwardResult(CallForwardInfo cf)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 updateSummaryText()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 */ getCurrentCountryIso()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 handleMessage(Message msg)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 handleGetCFResponse(Message msg)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 handleSetCFResponse(Message msg)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