Home | History | Annotate | Download | only in phone
      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 {
     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          */
    112         String onGetDefaultNumber(EditPhoneNumberPreference preference);
    113     }
    114 
    115     /*
    116      * Constructors
    117      */
    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 
    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.
    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
    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
    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
    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.
    271     public void setDialogOnFocusChangeListener(View.OnFocusChangeListener l) {
    272         mDialogFocusChangeListener = l;
    273     }
    274 
    275     //set the listener to be called wht the dialog is closed.
    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.
    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.
    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.
    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
    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.
    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.
    344     public boolean isToggled() {
    345         return mChecked;
    346     }
    347 
    348     //set the toggle value.
    349     // return the current preference to allow for chaining preferences.
    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      */
    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 */
    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.
    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
    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
    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
    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)
    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)
    442     public EditPhoneNumberPreference setSummaryOn(int summaryResId) {
    443         return setSummaryOn(getContext().getString(summaryResId));
    444     }
    445 
    446     //get the summary string for the on state
    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)
    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)
    466     public EditPhoneNumberPreference setSummaryOff(int summaryResId) {
    467         return setSummaryOff(getContext().getString(summaryResId));
    468     }
    469 
    470     //get the summary string for the off state
    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.
    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
    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      */
    496     public void showPhoneNumberDialog() {
    497         showDialog(null);
    498     }
    499 }
    500