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