1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.phone; 18 19 import static android.view.View.LAYOUT_DIRECTION_LOCALE; 20 import static android.view.View.TEXT_DIRECTION_LOCALE; 21 22 import android.app.Activity; 23 import android.app.AlertDialog; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.res.TypedArray; 28 import android.preference.EditTextPreference; 29 import android.provider.ContactsContract.CommonDataKinds.Phone; 30 import android.telephony.PhoneNumberUtils; 31 import android.text.BidiFormatter; 32 import android.text.TextDirectionHeuristics; 33 import android.text.TextUtils; 34 import android.text.method.ArrowKeyMovementMethod; 35 import android.text.method.DialerKeyListener; 36 import android.util.AttributeSet; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.widget.EditText; 40 import android.widget.ImageButton; 41 import android.widget.TextView; 42 43 import com.android.internal.telephony.CommandsInterface; 44 45 public class EditPhoneNumberPreference extends EditTextPreference { 46 47 //allowed modes for this preference. 48 /** simple confirmation (OK / CANCEL) */ 49 private static final int CM_CONFIRM = 0; 50 /** toggle [(ENABLE / CANCEL) or (DISABLE / CANCEL)], use isToggled() to see requested state.*/ 51 private static final int CM_ACTIVATION = 1; 52 53 private int mConfirmationMode; 54 55 //String constants used in storing the value of the preference 56 // The preference is backed by a string that holds the encoded value, which reads: 57 // <VALUE_ON | VALUE_OFF><VALUE_SEPARATOR><mPhoneNumber> 58 // for example, an enabled preference with a number of 6502345678 would read: 59 // "1:6502345678" 60 private static final String VALUE_SEPARATOR = ":"; 61 private static final String VALUE_OFF = "0"; 62 private static final String VALUE_ON = "1"; 63 64 //UI layout 65 private ImageButton mContactPickButton; 66 67 //Listeners 68 /** Called when focus is changed between fields */ 69 private View.OnFocusChangeListener mDialogFocusChangeListener; 70 /** Called when the Dialog is closed. */ 71 private OnDialogClosedListener mDialogOnClosedListener; 72 /** 73 * Used to indicate that we are going to request for a 74 * default number. for the dialog. 75 */ 76 private GetDefaultNumberListener mGetDefaultNumberListener; 77 78 //Activity values 79 private Activity mParentActivity; 80 private Intent mContactListIntent; 81 /** Arbitrary activity-assigned preference id value */ 82 private int mPrefId; 83 84 //similar to toggle preference 85 private CharSequence mEnableText; 86 private CharSequence mDisableText; 87 private CharSequence mChangeNumberText; 88 private CharSequence mSummaryOn; 89 private CharSequence mSummaryOff; 90 91 // button that was clicked on dialog close. 92 private int mButtonClicked; 93 94 //relevant (parsed) value of the mText 95 private String mPhoneNumber; 96 private boolean mChecked; 97 98 private boolean mIsUnknownStatus; 99 100 /** 101 * Interface for the dialog closed listener, related to 102 * DialogPreference.onDialogClosed(), except we also pass in a buttonClicked 103 * value indicating which of the three possible buttons were pressed. 104 */ 105 public interface OnDialogClosedListener { onDialogClosed(EditPhoneNumberPreference preference, int buttonClicked)106 void onDialogClosed(EditPhoneNumberPreference preference, int buttonClicked); 107 } 108 109 /** 110 * Interface for the default number setting listener. Handles requests for 111 * the default display number for the dialog. 112 */ 113 public interface GetDefaultNumberListener { 114 /** 115 * Notify that we are looking for a default display value. 116 * @return null if there is no contribution from this interface, 117 * indicating that the orignal value of mPhoneNumber should be 118 * displayed unchanged. 119 */ onGetDefaultNumber(EditPhoneNumberPreference preference)120 String onGetDefaultNumber(EditPhoneNumberPreference preference); 121 } 122 123 /* 124 * Constructors 125 */ EditPhoneNumberPreference(Context context, AttributeSet attrs)126 public EditPhoneNumberPreference(Context context, AttributeSet attrs) { 127 super(context, attrs); 128 129 setDialogLayoutResource(R.layout.pref_dialog_editphonenumber); 130 131 //create intent to bring up contact list 132 mContactListIntent = new Intent(Intent.ACTION_PICK); 133 mContactListIntent.setType(Phone.CONTENT_TYPE); 134 135 //get the edit phone number default settings 136 TypedArray a = context.obtainStyledAttributes(attrs, 137 R.styleable.EditPhoneNumberPreference, 0, R.style.EditPhoneNumberPreference); 138 mEnableText = a.getString(R.styleable.EditPhoneNumberPreference_enableButtonText); 139 mDisableText = a.getString(R.styleable.EditPhoneNumberPreference_disableButtonText); 140 mChangeNumberText = a.getString(R.styleable.EditPhoneNumberPreference_changeNumButtonText); 141 mConfirmationMode = a.getInt(R.styleable.EditPhoneNumberPreference_confirmMode, 0); 142 a.recycle(); 143 144 //get the summary settings, use CheckBoxPreference as the standard. 145 a = context.obtainStyledAttributes(attrs, android.R.styleable.CheckBoxPreference, 0, 0); 146 mSummaryOn = a.getString(android.R.styleable.CheckBoxPreference_summaryOn); 147 mSummaryOff = a.getString(android.R.styleable.CheckBoxPreference_summaryOff); 148 a.recycle(); 149 } 150 EditPhoneNumberPreference(Context context)151 public EditPhoneNumberPreference(Context context) { 152 this(context, null); 153 } 154 155 156 /* 157 * Methods called on UI bindings 158 */ 159 @Override 160 //called when we're binding the view to the preference. onBindView(View view)161 protected void onBindView(View view) { 162 super.onBindView(view); 163 164 // Sync the summary view 165 TextView summaryView = (TextView) view.findViewById(android.R.id.summary); 166 if (summaryView != null) { 167 CharSequence sum; 168 int vis; 169 170 //set summary depending upon mode 171 if (mConfirmationMode == CM_ACTIVATION) { 172 if (mChecked) { 173 sum = (mSummaryOn == null) ? getSummary() : mSummaryOn; 174 } else { 175 sum = (mSummaryOff == null) ? getSummary() : mSummaryOff; 176 } 177 } else { 178 sum = getSummary(); 179 } 180 181 if (sum != null) { 182 summaryView.setText(sum); 183 vis = View.VISIBLE; 184 } else { 185 vis = View.GONE; 186 } 187 188 if (vis != summaryView.getVisibility()) { 189 summaryView.setVisibility(vis); 190 } 191 } 192 } 193 194 //called when we're binding the dialog to the preference's view. 195 @Override onBindDialogView(View view)196 protected void onBindDialogView(View view) { 197 // default the button clicked to be the cancel button. 198 mButtonClicked = DialogInterface.BUTTON_NEGATIVE; 199 200 super.onBindDialogView(view); 201 202 //get the edittext component within the number field 203 EditText editText = getEditText(); 204 //get the contact pick button within the number field 205 mContactPickButton = (ImageButton) view.findViewById(R.id.select_contact); 206 207 //setup number entry 208 if (editText != null) { 209 // see if there is a means to get a default number, 210 // and set it accordingly. 211 if (mGetDefaultNumberListener != null) { 212 String defaultNumber = mGetDefaultNumberListener.onGetDefaultNumber(this); 213 if (defaultNumber != null) { 214 mPhoneNumber = defaultNumber; 215 } 216 } 217 editText.setText(BidiFormatter.getInstance().unicodeWrap( 218 mPhoneNumber, TextDirectionHeuristics.LOCALE)); 219 editText.setTextDirection(TEXT_DIRECTION_LOCALE); 220 editText.setLayoutDirection(LAYOUT_DIRECTION_LOCALE); 221 editText.setMovementMethod(ArrowKeyMovementMethod.getInstance()); 222 editText.setKeyListener(DialerKeyListener.getInstance()); 223 editText.setOnFocusChangeListener(mDialogFocusChangeListener); 224 } 225 226 //set contact picker 227 if (mContactPickButton != null) { 228 mContactPickButton.setOnClickListener(new View.OnClickListener() { 229 public void onClick(View v) { 230 if (mParentActivity != null) { 231 mParentActivity.startActivityForResult(mContactListIntent, mPrefId); 232 } 233 } 234 }); 235 } 236 } 237 238 /** 239 * Overriding EditTextPreference's onAddEditTextToDialogView. 240 * 241 * This method attaches the EditText to the container specific to this 242 * preference's dialog layout. 243 */ 244 @Override onAddEditTextToDialogView(View dialogView, EditText editText)245 protected void onAddEditTextToDialogView(View dialogView, EditText editText) { 246 247 // look for the container object 248 ViewGroup container = (ViewGroup) dialogView 249 .findViewById(R.id.edit_container); 250 251 // add the edittext to the container. 252 if (container != null) { 253 container.addView(editText, ViewGroup.LayoutParams.MATCH_PARENT, 254 ViewGroup.LayoutParams.WRAP_CONTENT); 255 } 256 } 257 258 //control the appearance of the dialog depending upon the mode. 259 @Override onPrepareDialogBuilder(AlertDialog.Builder builder)260 protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { 261 // modified so that we just worry about the buttons being 262 // displayed, since there is no need to hide the edittext 263 // field anymore. 264 if (mConfirmationMode == CM_ACTIVATION) { 265 if (mIsUnknownStatus) { 266 builder.setPositiveButton(mEnableText, this); 267 builder.setNeutralButton(mDisableText, this); 268 if (mPrefId == CommandsInterface.CF_REASON_ALL) { 269 builder.setPositiveButton(null, null); 270 } 271 } else if (mChecked) { 272 builder.setPositiveButton(mChangeNumberText, this); 273 builder.setNeutralButton(mDisableText, this); 274 } else { 275 builder.setPositiveButton(mEnableText, this); 276 builder.setNeutralButton(null, null); 277 } 278 } 279 // set the call icon on the title. 280 builder.setIcon(R.mipmap.ic_launcher_phone); 281 } 282 283 284 /* 285 * Listeners and other state setting methods 286 */ 287 //set the on focus change listener to be assigned to the Dialog's edittext field. setDialogOnFocusChangeListener(View.OnFocusChangeListener l)288 public void setDialogOnFocusChangeListener(View.OnFocusChangeListener l) { 289 mDialogFocusChangeListener = l; 290 } 291 292 //set the listener to be called wht the dialog is closed. setDialogOnClosedListener(OnDialogClosedListener l)293 public void setDialogOnClosedListener(OnDialogClosedListener l) { 294 mDialogOnClosedListener = l; 295 } 296 297 //set the link back to the parent activity, so that we may run the contact picker. setParentActivity(Activity parent, int identifier)298 public void setParentActivity(Activity parent, int identifier) { 299 mParentActivity = parent; 300 mPrefId = identifier; 301 mGetDefaultNumberListener = null; 302 } 303 304 //set the link back to the parent activity, so that we may run the contact picker. 305 //also set the default number listener. setParentActivity(Activity parent, int identifier, GetDefaultNumberListener l)306 public void setParentActivity(Activity parent, int identifier, GetDefaultNumberListener l) { 307 mParentActivity = parent; 308 mPrefId = identifier; 309 mGetDefaultNumberListener = l; 310 } 311 312 /* 313 * Notification handlers 314 */ 315 //Notify the preference that the pick activity is complete. onPickActivityResult(String pickedValue)316 public void onPickActivityResult(String pickedValue) { 317 EditText editText = getEditText(); 318 if (editText != null) { 319 editText.setText(pickedValue); 320 } 321 } 322 323 //called when the dialog is clicked. 324 @Override onClick(DialogInterface dialog, int which)325 public void onClick(DialogInterface dialog, int which) { 326 // The neutral button (button3) is always the toggle. 327 if ((mConfirmationMode == CM_ACTIVATION) && (which == DialogInterface.BUTTON_NEUTRAL) 328 && !mIsUnknownStatus) { 329 //flip the toggle if we are in the correct mode. 330 setToggled(!isToggled()); 331 } 332 // record the button that was clicked. 333 mButtonClicked = which; 334 super.onClick(dialog, which); 335 } 336 337 @Override 338 //When the dialog is closed, perform the relevant actions, including setting 339 // phone numbers and calling the close action listener. onDialogClosed(boolean positiveResult)340 protected void onDialogClosed(boolean positiveResult) { 341 // A positive result is technically either button1 or button3. 342 if ((mButtonClicked == DialogInterface.BUTTON_POSITIVE) || 343 (mButtonClicked == DialogInterface.BUTTON_NEUTRAL)){ 344 setPhoneNumber(getEditText().getText().toString()); 345 super.onDialogClosed(positiveResult); 346 setText(getStringValue()); 347 } else { 348 super.onDialogClosed(positiveResult); 349 } 350 351 // send the clicked button over to the listener. 352 if (mDialogOnClosedListener != null) { 353 mDialogOnClosedListener.onDialogClosed(this, mButtonClicked); 354 } 355 } 356 357 358 /* 359 * Toggle handling code. 360 */ 361 //return the toggle value. isToggled()362 public boolean isToggled() { 363 return mChecked; 364 } 365 366 //set the toggle value. 367 // return the current preference to allow for chaining preferences. setToggled(boolean checked)368 public EditPhoneNumberPreference setToggled(boolean checked) { 369 mChecked = checked; 370 setText(getStringValue()); 371 notifyChanged(); 372 373 return this; 374 } 375 376 377 /** 378 * Phone number handling code 379 */ getPhoneNumber()380 public String getPhoneNumber() { 381 // return the phone number, after it has been stripped of all 382 // irrelevant text. 383 return PhoneNumberUtils.stripSeparators(mPhoneNumber); 384 } 385 386 /** The phone number including any formatting characters */ getRawPhoneNumber()387 protected String getRawPhoneNumber() { 388 return mPhoneNumber; 389 } 390 391 //set the phone number value. 392 // return the current preference to allow for chaining preferences. setPhoneNumber(String number)393 public EditPhoneNumberPreference setPhoneNumber(String number) { 394 mPhoneNumber = number; 395 setText(getStringValue()); 396 notifyChanged(); 397 398 return this; 399 } 400 401 402 /* 403 * Other code relevant to preference framework 404 */ 405 //when setting default / initial values, make sure we're setting things correctly. 406 @Override onSetInitialValue(boolean restoreValue, Object defaultValue)407 protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 408 setValueFromString(restoreValue ? getPersistedString(getStringValue()) 409 : (String) defaultValue); 410 } 411 412 /** 413 * Decides how to disable dependents. 414 */ 415 @Override shouldDisableDependents()416 public boolean shouldDisableDependents() { 417 // There is really only one case we care about, but for consistency 418 // we fill out the dependency tree for all of the cases. If this 419 // is in activation mode (CF), we look for the encoded toggle value 420 // in the string. If this in confirm mode (VM), then we just 421 // examine the number field. 422 // Note: The toggle value is stored in the string in an encoded 423 // manner (refer to setValueFromString and getStringValue below). 424 boolean shouldDisable = false; 425 if ((mConfirmationMode == CM_ACTIVATION) && (mEncodedText != null)) { 426 String[] inValues = mEncodedText.split(":", 2); 427 shouldDisable = inValues[0].equals(VALUE_ON); 428 } else { 429 shouldDisable = (TextUtils.isEmpty(mPhoneNumber) && (mConfirmationMode == CM_CONFIRM)); 430 } 431 return shouldDisable; 432 } 433 434 /** 435 * Override persistString so that we can get a hold of the EditTextPreference's 436 * text field. 437 */ 438 private String mEncodedText = null; 439 @Override persistString(String value)440 protected boolean persistString(String value) { 441 mEncodedText = value; 442 return super.persistString(value); 443 } 444 445 446 /* 447 * Summary On handling code 448 */ 449 //set the Summary for the on state (relevant only in CM_ACTIVATION mode) setSummaryOn(CharSequence summary)450 public EditPhoneNumberPreference setSummaryOn(CharSequence summary) { 451 mSummaryOn = summary; 452 if (isToggled()) { 453 notifyChanged(); 454 } 455 return this; 456 } 457 458 //set the Summary for the on state, given a string resource id 459 // (relevant only in CM_ACTIVATION mode) setSummaryOn(int summaryResId)460 public EditPhoneNumberPreference setSummaryOn(int summaryResId) { 461 return setSummaryOn(getContext().getString(summaryResId)); 462 } 463 464 //get the summary string for the on state getSummaryOn()465 public CharSequence getSummaryOn() { 466 return mSummaryOn; 467 } 468 469 470 /* 471 * Summary Off handling code 472 */ 473 //set the Summary for the off state (relevant only in CM_ACTIVATION mode) setSummaryOff(CharSequence summary)474 public EditPhoneNumberPreference setSummaryOff(CharSequence summary) { 475 mSummaryOff = summary; 476 if (!isToggled()) { 477 notifyChanged(); 478 } 479 return this; 480 } 481 482 //set the Summary for the off state, given a string resource id 483 // (relevant only in CM_ACTIVATION mode) setSummaryOff(int summaryResId)484 public EditPhoneNumberPreference setSummaryOff(int summaryResId) { 485 return setSummaryOff(getContext().getString(summaryResId)); 486 } 487 488 //get the summary string for the off state getSummaryOff()489 public CharSequence getSummaryOff() { 490 return mSummaryOff; 491 } 492 493 494 /* 495 * Methods to get and set from encoded strings. 496 */ 497 //set the values given an encoded string. setValueFromString(String value)498 protected void setValueFromString(String value) { 499 String[] inValues = value.split(":", 2); 500 setToggled(inValues[0].equals(VALUE_ON)); 501 setPhoneNumber(inValues[1]); 502 } 503 504 //retrieve the state of this preference in the form of an encoded string getStringValue()505 protected String getStringValue() { 506 return ((isToggled() ? VALUE_ON : VALUE_OFF) + VALUE_SEPARATOR + getPhoneNumber()); 507 } 508 509 /** 510 * Externally visible method to bring up the dialog. 511 * 512 * Generally used when we are navigating the user to this preference. 513 */ showPhoneNumberDialog()514 public void showPhoneNumberDialog() { 515 showDialog(null); 516 } 517 setUnknownStatus(boolean isUnknown)518 public void setUnknownStatus(boolean isUnknown) { 519 mIsUnknownStatus = isUnknown; 520 } 521 isUnknownStatus()522 public boolean isUnknownStatus() { 523 return mIsUnknownStatus; 524 } 525 } 526