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