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.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 {
    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          */
    114         String onGetDefaultNumber(EditPhoneNumberPreference preference);
    115     }
    116 
    117     /*
    118      * Constructors
    119      */
    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_GET_CONTENT);
    127         mContactListIntent.setType(Phone.CONTENT_ITEM_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 
    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.
    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
    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
    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
    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(null, null);
    262                 builder.setNeutralButton(mEnableText, this);
    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.
    274     public void setDialogOnFocusChangeListener(View.OnFocusChangeListener l) {
    275         mDialogFocusChangeListener = l;
    276     }
    277 
    278     //set the listener to be called wht the dialog is closed.
    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.
    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.
    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.
    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
    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.
    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.
    347     public boolean isToggled() {
    348         return mChecked;
    349     }
    350 
    351     //set the toggle value.
    352     // return the current preference to allow for chaining preferences.
    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      */
    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 */
    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.
    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
    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
    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
    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)
    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)
    445     public EditPhoneNumberPreference setSummaryOn(int summaryResId) {
    446         return setSummaryOn(getContext().getString(summaryResId));
    447     }
    448 
    449     //get the summary string for the on state
    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)
    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)
    469     public EditPhoneNumberPreference setSummaryOff(int summaryResId) {
    470         return setSummaryOff(getContext().getString(summaryResId));
    471     }
    472 
    473     //get the summary string for the off state
    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.
    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
    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      */
    499     public void showPhoneNumberDialog() {
    500         showDialog(null);
    501     }
    502 }
    503