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