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