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