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