1 /*
2  * Copyright (C) 2006 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.settings.fdn;
18 
19 import static android.view.Window.PROGRESS_VISIBILITY_OFF;
20 import static android.view.Window.PROGRESS_VISIBILITY_ON;
21 
22 import android.app.Activity;
23 import android.content.AsyncQueryHandler;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Intent;
27 import android.content.res.Resources;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.PersistableBundle;
33 import android.provider.ContactsContract.CommonDataKinds;
34 import android.telephony.PhoneNumberUtils;
35 import android.text.Editable;
36 import android.text.Selection;
37 import android.text.Spannable;
38 import android.text.TextUtils;
39 import android.text.TextWatcher;
40 import android.text.method.DialerKeyListener;
41 import android.util.Log;
42 import android.view.Menu;
43 import android.view.MenuItem;
44 import android.view.View;
45 import android.view.Window;
46 import android.widget.Button;
47 import android.widget.EditText;
48 import android.widget.LinearLayout;
49 import android.widget.TextView;
50 import android.widget.Toast;
51 
52 import com.android.internal.telephony.PhoneFactory;
53 import com.android.phone.PhoneGlobals;
54 import com.android.phone.R;
55 import com.android.phone.SubscriptionInfoHelper;
56 import android.telephony.CarrierConfigManager;
57 
58 /**
59  * Activity to let the user add or edit an FDN contact.
60  */
61 public class EditFdnContactScreen extends Activity {
62     private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
63     private static final boolean DBG = false;
64 
65     // Menu item codes
66     private static final int MENU_IMPORT = 1;
67     private static final int MENU_DELETE = 2;
68 
69     private static final String INTENT_EXTRA_NAME = "name";
70     private static final String INTENT_EXTRA_NUMBER = "number";
71 
72     private static final int PIN2_REQUEST_CODE = 100;
73 
74     private SubscriptionInfoHelper mSubscriptionInfoHelper;
75 
76     private String mName;
77     private String mNumber;
78     private String mPin2;
79     private boolean mAddContact;
80     private QueryHandler mQueryHandler;
81 
82     private EditText mNameField;
83     private EditText mNumberField;
84     private LinearLayout mPinFieldContainer;
85     private Button mButton;
86 
87     private Handler mHandler = new Handler();
88 
89     /**
90      * Constants used in importing from contacts
91      */
92     /** request code when invoking subactivity */
93     private static final int CONTACTS_PICKER_CODE = 200;
94     /** projection for phone number query */
95     private static final String[] NUM_PROJECTION = new String[] {CommonDataKinds.Phone.DISPLAY_NAME,
96             CommonDataKinds.Phone.NUMBER};
97     /** static intent to invoke phone number picker */
98     private static final Intent CONTACT_IMPORT_INTENT;
99     static {
100         CONTACT_IMPORT_INTENT = new Intent(Intent.ACTION_GET_CONTENT);
101         CONTACT_IMPORT_INTENT.setType(CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
102     }
103     /** flag to track saving state */
104     private boolean mDataBusy;
105     private int mFdnNumberLimitLength = 20;
106 
107     @Override
onCreate(Bundle icicle)108     protected void onCreate(Bundle icicle) {
109         super.onCreate(icicle);
110 
111         resolveIntent();
112 
113         getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
114         setContentView(R.layout.edit_fdn_contact_screen);
115         setupView();
116         setTitle(mAddContact ? R.string.add_fdn_contact : R.string.edit_fdn_contact);
117         PersistableBundle b = null;
118         if (mSubscriptionInfoHelper.hasSubId()) {
119             b = PhoneGlobals.getInstance().getCarrierConfigForSubId(
120                     mSubscriptionInfoHelper.getSubId());
121         } else {
122             b = PhoneGlobals.getInstance().getCarrierConfig();
123         }
124         if (b != null) {
125             mFdnNumberLimitLength = b.getInt(
126                     CarrierConfigManager.KEY_FDN_NUMBER_LENGTH_LIMIT_INT);
127         }
128 
129         displayProgress(false);
130     }
131 
132     /**
133      * We now want to bring up the pin request screen AFTER the
134      * contact information is displayed, to help with user
135      * experience.
136      *
137      * Also, process the results from the contact picker.
138      */
139     @Override
onActivityResult(int requestCode, int resultCode, Intent intent)140     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
141         if (DBG) log("onActivityResult request:" + requestCode + " result:" + resultCode);
142 
143         switch (requestCode) {
144             case PIN2_REQUEST_CODE:
145                 Bundle extras = (intent != null) ? intent.getExtras() : null;
146                 if (extras != null) {
147                     mPin2 = extras.getString("pin2");
148                     if (mAddContact) {
149                         addContact();
150                     } else {
151                         updateContact();
152                     }
153                 } else if (resultCode != RESULT_OK) {
154                     // if they cancelled, then we just cancel too.
155                     if (DBG) log("onActivityResult: cancelled.");
156                     finish();
157                 }
158                 break;
159 
160             // look for the data associated with this number, and update
161             // the display with it.
162             case CONTACTS_PICKER_CODE:
163                 if (resultCode != RESULT_OK) {
164                     if (DBG) log("onActivityResult: cancelled.");
165                     return;
166                 }
167                 Cursor cursor = null;
168                 try {
169                     cursor = getContentResolver().query(intent.getData(),
170                         NUM_PROJECTION, null, null, null);
171                     if ((cursor == null) || (!cursor.moveToFirst())) {
172                         Log.w(LOG_TAG,"onActivityResult: bad contact data, no results found.");
173                         return;
174                     }
175                     mNameField.setText(cursor.getString(0));
176                     mNumberField.setText(cursor.getString(1));
177                 } finally {
178                     if (cursor != null) {
179                         cursor.close();
180                     }
181                 }
182                 break;
183         }
184     }
185 
186     /**
187      * Overridden to display the import and delete commands.
188      */
189     @Override
onCreateOptionsMenu(Menu menu)190     public boolean onCreateOptionsMenu(Menu menu) {
191         super.onCreateOptionsMenu(menu);
192 
193         Resources r = getResources();
194 
195         // Added the icons to the context menu
196         menu.add(0, MENU_IMPORT, 0, r.getString(R.string.importToFDNfromContacts))
197                 .setIcon(R.drawable.ic_menu_contact);
198         menu.add(0, MENU_DELETE, 0, r.getString(R.string.menu_delete))
199                 .setIcon(android.R.drawable.ic_menu_delete);
200         return true;
201     }
202 
203     /**
204      * Allow the menu to be opened ONLY if we're not busy.
205      */
206     @Override
onPrepareOptionsMenu(Menu menu)207     public boolean onPrepareOptionsMenu(Menu menu) {
208         boolean result = super.onPrepareOptionsMenu(menu);
209         return mDataBusy ? false : result;
210     }
211 
212     /**
213      * Overridden to allow for handling of delete and import.
214      */
215     @Override
onOptionsItemSelected(MenuItem item)216     public boolean onOptionsItemSelected(MenuItem item) {
217         switch (item.getItemId()) {
218             case MENU_IMPORT:
219                 startActivityForResult(CONTACT_IMPORT_INTENT, CONTACTS_PICKER_CODE);
220                 return true;
221 
222             case MENU_DELETE:
223                 deleteSelected();
224                 return true;
225 
226             case android.R.id.home:
227                 onBackPressed();
228                 return true;
229         }
230 
231         return super.onOptionsItemSelected(item);
232     }
233 
resolveIntent()234     private void resolveIntent() {
235         Intent intent = getIntent();
236 
237         mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, intent);
238 
239         mName =  intent.getStringExtra(INTENT_EXTRA_NAME);
240         mNumber =  intent.getStringExtra(INTENT_EXTRA_NUMBER);
241 
242         mAddContact = TextUtils.isEmpty(mNumber);
243     }
244 
245     /**
246      * We have multiple layouts, one to indicate that the user needs to
247      * open the keyboard to enter information (if the keybord is hidden).
248      * So, we need to make sure that the layout here matches that in the
249      * layout file.
250      */
setupView()251     private void setupView() {
252         mNameField = (EditText) findViewById(R.id.fdn_name);
253         if (mNameField != null) {
254             mNameField.setOnFocusChangeListener(mOnFocusChangeHandler);
255             mNameField.setOnClickListener(mClicked);
256             mNameField.addTextChangedListener(mTextWatcher);
257         }
258 
259         mNumberField = (EditText) findViewById(R.id.fdn_number);
260         if (mNumberField != null) {
261             mNumberField.setTextDirection(View.TEXT_DIRECTION_LTR);
262             mNumberField.setKeyListener(DialerKeyListener.getInstance());
263             mNumberField.setOnFocusChangeListener(mOnFocusChangeHandler);
264             mNumberField.setOnClickListener(mClicked);
265             mNumberField.addTextChangedListener(mTextWatcher);
266         }
267 
268         if (!mAddContact) {
269             if (mNameField != null) {
270                 mNameField.setText(mName);
271             }
272             if (mNumberField != null) {
273                 mNumberField.setText(mNumber);
274             }
275         }
276 
277         mButton = (Button) findViewById(R.id.button);
278         if (mButton != null) {
279             mButton.setOnClickListener(mClicked);
280             setButtonEnabled();
281         }
282 
283         mPinFieldContainer = (LinearLayout) findViewById(R.id.pinc);
284 
285     }
286 
getNameFromTextField()287     private String getNameFromTextField() {
288         return mNameField.getText().toString();
289     }
290 
getNumberFromTextField()291     private String getNumberFromTextField() {
292         return mNumberField.getText().toString();
293     }
294 
295     /**
296      * Enable Save button if text has been added to both name and number
297      */
setButtonEnabled()298     private void setButtonEnabled() {
299         if (mButton != null && mNameField != null && mNumberField != null) {
300             mButton.setEnabled(mNameField.length() > 0 && mNumberField.length() > 0);
301         }
302     }
303 
304     /**
305       * @param number is voice mail number
306       * @return true if number length is less than 20-digit limit
307       *
308       * TODO: Fix this logic.
309       */
isValidNumber(String number)310      private boolean isValidNumber(String number) {
311          return (number.length() <= mFdnNumberLimitLength) && (number.length() > 0);
312      }
313 
314 
addContact()315     private void addContact() {
316         if (DBG) log("addContact");
317 
318         final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField());
319 
320         if (!isValidNumber(number)) {
321             handleResult(false, true);
322             return;
323         }
324 
325         Uri uri = FdnList.getContentUri(mSubscriptionInfoHelper);
326 
327         ContentValues bundle = new ContentValues(3);
328         bundle.put("tag", getNameFromTextField());
329         bundle.put("number", number);
330         bundle.put("pin2", mPin2);
331 
332         mQueryHandler = new QueryHandler(getContentResolver());
333         mQueryHandler.startInsert(0, null, uri, bundle);
334         displayProgress(true);
335         showStatus(getResources().getText(R.string.adding_fdn_contact));
336     }
337 
updateContact()338     private void updateContact() {
339         if (DBG) log("updateContact");
340 
341         final String name = getNameFromTextField();
342         final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField());
343 
344         if (!isValidNumber(number)) {
345             handleResult(false, true);
346             return;
347         }
348         Uri uri = FdnList.getContentUri(mSubscriptionInfoHelper);
349 
350         ContentValues bundle = new ContentValues();
351         bundle.put("tag", mName);
352         bundle.put("number", mNumber);
353         bundle.put("newTag", name);
354         bundle.put("newNumber", number);
355         bundle.put("pin2", mPin2);
356 
357         mQueryHandler = new QueryHandler(getContentResolver());
358         mQueryHandler.startUpdate(0, null, uri, bundle, null, null);
359         displayProgress(true);
360         showStatus(getResources().getText(R.string.updating_fdn_contact));
361     }
362 
363     /**
364      * Handle the delete command, based upon the state of the Activity.
365      */
deleteSelected()366     private void deleteSelected() {
367         // delete ONLY if this is NOT a new contact.
368         if (!mAddContact) {
369             Intent intent = mSubscriptionInfoHelper.getIntent(DeleteFdnContactScreen.class);
370             intent.putExtra(INTENT_EXTRA_NAME, mName);
371             intent.putExtra(INTENT_EXTRA_NUMBER, mNumber);
372             startActivity(intent);
373         }
374         finish();
375     }
376 
authenticatePin2()377     private void authenticatePin2() {
378         Intent intent = new Intent();
379         intent.setClass(this, GetPin2Screen.class);
380         intent.setData(FdnList.getContentUri(mSubscriptionInfoHelper));
381         startActivityForResult(intent, PIN2_REQUEST_CODE);
382     }
383 
displayProgress(boolean flag)384     private void displayProgress(boolean flag) {
385         // indicate we are busy.
386         mDataBusy = flag;
387         getWindow().setFeatureInt(
388                 Window.FEATURE_INDETERMINATE_PROGRESS,
389                 mDataBusy ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
390         // make sure we don't allow calls to save when we're
391         // not ready for them.
392         mButton.setClickable(!mDataBusy);
393     }
394 
395     /**
396      * Removed the status field, with preference to displaying a toast
397      * to match the rest of settings UI.
398      */
showStatus(CharSequence statusMsg)399     private void showStatus(CharSequence statusMsg) {
400         if (statusMsg != null) {
401             Toast.makeText(this, statusMsg, Toast.LENGTH_LONG)
402                     .show();
403         }
404     }
405 
handleResult(boolean success, boolean invalidNumber)406     private void handleResult(boolean success, boolean invalidNumber) {
407         if (success) {
408             if (DBG) log("handleResult: success!");
409             showStatus(getResources().getText(mAddContact ?
410                     R.string.fdn_contact_added : R.string.fdn_contact_updated));
411         } else {
412             if (DBG) log("handleResult: failed!");
413             if (invalidNumber) {
414                 showStatus(getResources().getString(R.string.fdn_invalid_number,
415                         mFdnNumberLimitLength));
416             } else {
417                if (PhoneFactory.getDefaultPhone().getIccCard().getIccPin2Blocked()) {
418                     showStatus(getResources().getText(R.string.fdn_enable_puk2_requested));
419                 } else if (PhoneFactory.getDefaultPhone().getIccCard().getIccPuk2Blocked()) {
420                     showStatus(getResources().getText(R.string.puk2_blocked));
421                 } else {
422                     // There's no way to know whether the failure is due to incorrect PIN2 or
423                     // an inappropriate phone number.
424                     showStatus(getResources().getText(R.string.pin2_or_fdn_invalid));
425                 }
426             }
427         }
428 
429         mHandler.postDelayed(new Runnable() {
430             @Override
431             public void run() {
432                 finish();
433             }
434         }, 2000);
435 
436     }
437 
438     private final View.OnClickListener mClicked = new View.OnClickListener() {
439         @Override
440         public void onClick(View v) {
441             if (mPinFieldContainer.getVisibility() != View.VISIBLE) {
442                 return;
443             }
444 
445             if (v == mNameField) {
446                 mButton.requestFocus();
447             } else if (v == mNumberField) {
448                 mButton.requestFocus();
449             } else if (v == mButton) {
450                 final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField());
451 
452                 if (!isValidNumber(number)) {
453                     handleResult(false, true);
454                     return;
455                 }
456                 // Authenticate the pin AFTER the contact information
457                 // is entered, and if we're not busy.
458                 if (!mDataBusy) {
459                     authenticatePin2();
460                 }
461             }
462         }
463     };
464 
465     private final View.OnFocusChangeListener mOnFocusChangeHandler =
466             new View.OnFocusChangeListener() {
467         @Override
468         public void onFocusChange(View v, boolean hasFocus) {
469             if (hasFocus) {
470                 TextView textView = (TextView) v;
471                 Selection.selectAll((Spannable) textView.getText());
472             }
473         }
474     };
475 
476     private final TextWatcher mTextWatcher = new TextWatcher() {
477         @Override
478         public void afterTextChanged(Editable arg0) {}
479 
480         @Override
481         public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {}
482 
483         @Override
484         public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
485             setButtonEnabled();
486         }
487     };
488 
489     private class QueryHandler extends AsyncQueryHandler {
QueryHandler(ContentResolver cr)490         public QueryHandler(ContentResolver cr) {
491             super(cr);
492         }
493 
494         @Override
onQueryComplete(int token, Object cookie, Cursor c)495         protected void onQueryComplete(int token, Object cookie, Cursor c) {
496         }
497 
498         @Override
onInsertComplete(int token, Object cookie, Uri uri)499         protected void onInsertComplete(int token, Object cookie, Uri uri) {
500             if (DBG) log("onInsertComplete");
501             displayProgress(false);
502             handleResult(uri != null, false);
503         }
504 
505         @Override
onUpdateComplete(int token, Object cookie, int result)506         protected void onUpdateComplete(int token, Object cookie, int result) {
507             if (DBG) log("onUpdateComplete");
508             displayProgress(false);
509             handleResult(result > 0, false);
510         }
511 
512         @Override
onDeleteComplete(int token, Object cookie, int result)513         protected void onDeleteComplete(int token, Object cookie, int result) {
514         }
515     }
516 
log(String msg)517     private void log(String msg) {
518         Log.d(LOG_TAG, "[EditFdnContact] " + msg);
519     }
520 }
521