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