EditPhoneNumberPreference.java revision c5c0a14a27ced2f41862597fc3cba1ce7a6c9832
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
17package com.android.phone;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.content.res.TypedArray;
25import android.preference.EditTextPreference;
26import android.provider.ContactsContract.CommonDataKinds.Phone;
27import android.telephony.PhoneNumberUtils;
28import android.text.TextUtils;
29import android.text.method.ArrowKeyMovementMethod;
30import android.text.method.DialerKeyListener;
31import android.util.AttributeSet;
32import android.view.View;
33import android.view.ViewGroup;
34import android.widget.EditText;
35import android.widget.ImageButton;
36import android.widget.TextView;
37
38public 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