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
17package com.android.phone.settings.fdn;
18
19import static android.view.Window.PROGRESS_VISIBILITY_OFF;
20import static android.view.Window.PROGRESS_VISIBILITY_ON;
21
22import android.app.Activity;
23import android.content.AsyncQueryHandler;
24import android.content.ContentResolver;
25import android.content.ContentValues;
26import android.content.Intent;
27import android.content.res.Resources;
28import android.database.Cursor;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.Handler;
32import android.provider.Contacts.PeopleColumns;
33import android.provider.Contacts.PhonesColumns;
34import android.provider.ContactsContract.CommonDataKinds;
35import android.telephony.PhoneNumberUtils;
36import android.text.Selection;
37import android.text.Spannable;
38import android.text.TextUtils;
39import android.text.method.DialerKeyListener;
40import android.util.Log;
41import android.view.Menu;
42import android.view.MenuItem;
43import android.view.View;
44import android.view.Window;
45import android.widget.Button;
46import android.widget.EditText;
47import android.widget.LinearLayout;
48import android.widget.TextView;
49import android.widget.Toast;
50
51import com.android.phone.PhoneGlobals;
52import com.android.phone.R;
53import com.android.phone.SubscriptionInfoHelper;
54import com.android.internal.telephony.PhoneFactory;
55
56/**
57 * Activity to let the user add or edit an FDN contact.
58 */
59public 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
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
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
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
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
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
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     */
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.setTextDirection(View.TEXT_DIRECTION_LTR);
247            mNumberField.setKeyListener(DialerKeyListener.getInstance());
248            mNumberField.setOnFocusChangeListener(mOnFocusChangeHandler);
249            mNumberField.setOnClickListener(mClicked);
250        }
251
252        if (!mAddContact) {
253            if (mNameField != null) {
254                mNameField.setText(mName);
255            }
256            if (mNumberField != null) {
257                mNumberField.setText(mNumber);
258            }
259        }
260
261        mButton = (Button) findViewById(R.id.button);
262        if (mButton != null) {
263            mButton.setOnClickListener(mClicked);
264        }
265
266        mPinFieldContainer = (LinearLayout) findViewById(R.id.pinc);
267
268    }
269
270    private String getNameFromTextField() {
271        return mNameField.getText().toString();
272    }
273
274    private String getNumberFromTextField() {
275        return mNumberField.getText().toString();
276    }
277
278    /**
279      * @param number is voice mail number
280      * @return true if number length is less than 20-digit limit
281      *
282      * TODO: Fix this logic.
283      */
284     private boolean isValidNumber(String number) {
285         return (number.length() <= 20) && (number.length() > 0);
286     }
287
288
289    private void addContact() {
290        if (DBG) log("addContact");
291
292        final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField());
293
294        if (!isValidNumber(number)) {
295            handleResult(false, true);
296            return;
297        }
298
299        Uri uri = FdnList.getContentUri(mSubscriptionInfoHelper);
300
301        ContentValues bundle = new ContentValues(3);
302        bundle.put("tag", getNameFromTextField());
303        bundle.put("number", number);
304        bundle.put("pin2", mPin2);
305
306        mQueryHandler = new QueryHandler(getContentResolver());
307        mQueryHandler.startInsert(0, null, uri, bundle);
308        displayProgress(true);
309        showStatus(getResources().getText(R.string.adding_fdn_contact));
310    }
311
312    private void updateContact() {
313        if (DBG) log("updateContact");
314
315        final String name = getNameFromTextField();
316        final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField());
317
318        if (!isValidNumber(number)) {
319            handleResult(false, true);
320            return;
321        }
322        Uri uri = FdnList.getContentUri(mSubscriptionInfoHelper);
323
324        ContentValues bundle = new ContentValues();
325        bundle.put("tag", mName);
326        bundle.put("number", mNumber);
327        bundle.put("newTag", name);
328        bundle.put("newNumber", number);
329        bundle.put("pin2", mPin2);
330
331        mQueryHandler = new QueryHandler(getContentResolver());
332        mQueryHandler.startUpdate(0, null, uri, bundle, null, null);
333        displayProgress(true);
334        showStatus(getResources().getText(R.string.updating_fdn_contact));
335    }
336
337    /**
338     * Handle the delete command, based upon the state of the Activity.
339     */
340    private void deleteSelected() {
341        // delete ONLY if this is NOT a new contact.
342        if (!mAddContact) {
343            Intent intent = mSubscriptionInfoHelper.getIntent(DeleteFdnContactScreen.class);
344            intent.putExtra(INTENT_EXTRA_NAME, mName);
345            intent.putExtra(INTENT_EXTRA_NUMBER, mNumber);
346            startActivity(intent);
347        }
348        finish();
349    }
350
351    private void authenticatePin2() {
352        Intent intent = new Intent();
353        intent.setClass(this, GetPin2Screen.class);
354        intent.setData(FdnList.getContentUri(mSubscriptionInfoHelper));
355        startActivityForResult(intent, PIN2_REQUEST_CODE);
356    }
357
358    private void displayProgress(boolean flag) {
359        // indicate we are busy.
360        mDataBusy = flag;
361        getWindow().setFeatureInt(
362                Window.FEATURE_INDETERMINATE_PROGRESS,
363                mDataBusy ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
364        // make sure we don't allow calls to save when we're
365        // not ready for them.
366        mButton.setClickable(!mDataBusy);
367    }
368
369    /**
370     * Removed the status field, with preference to displaying a toast
371     * to match the rest of settings UI.
372     */
373    private void showStatus(CharSequence statusMsg) {
374        if (statusMsg != null) {
375            Toast.makeText(this, statusMsg, Toast.LENGTH_LONG)
376                    .show();
377        }
378    }
379
380    private void handleResult(boolean success, boolean invalidNumber) {
381        if (success) {
382            if (DBG) log("handleResult: success!");
383            showStatus(getResources().getText(mAddContact ?
384                    R.string.fdn_contact_added : R.string.fdn_contact_updated));
385        } else {
386            if (DBG) log("handleResult: failed!");
387            if (invalidNumber) {
388                showStatus(getResources().getText(R.string.fdn_invalid_number));
389            } else {
390               if (PhoneFactory.getDefaultPhone().getIccCard().getIccPin2Blocked()) {
391                    showStatus(getResources().getText(R.string.fdn_enable_puk2_requested));
392                } else if (PhoneFactory.getDefaultPhone().getIccCard().getIccPuk2Blocked()) {
393                    showStatus(getResources().getText(R.string.puk2_blocked));
394                } else {
395                    // There's no way to know whether the failure is due to incorrect PIN2 or
396                    // an inappropriate phone number.
397                    showStatus(getResources().getText(R.string.pin2_or_fdn_invalid));
398                }
399            }
400        }
401
402        mHandler.postDelayed(new Runnable() {
403            @Override
404            public void run() {
405                finish();
406            }
407        }, 2000);
408
409    }
410
411    private final View.OnClickListener mClicked = new View.OnClickListener() {
412        @Override
413        public void onClick(View v) {
414            if (mPinFieldContainer.getVisibility() != View.VISIBLE) {
415                return;
416            }
417
418            if (v == mNameField) {
419                mNumberField.requestFocus();
420            } else if (v == mNumberField) {
421                mButton.requestFocus();
422            } else if (v == mButton) {
423                final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField());
424
425                if (!isValidNumber(number)) {
426                    handleResult(false, true);
427                    return;
428                }
429                // Authenticate the pin AFTER the contact information
430                // is entered, and if we're not busy.
431                if (!mDataBusy) {
432                    authenticatePin2();
433                }
434            }
435        }
436    };
437
438    private final View.OnFocusChangeListener mOnFocusChangeHandler =
439            new View.OnFocusChangeListener() {
440        @Override
441        public void onFocusChange(View v, boolean hasFocus) {
442            if (hasFocus) {
443                TextView textView = (TextView) v;
444                Selection.selectAll((Spannable) textView.getText());
445            }
446        }
447    };
448
449    private class QueryHandler extends AsyncQueryHandler {
450        public QueryHandler(ContentResolver cr) {
451            super(cr);
452        }
453
454        @Override
455        protected void onQueryComplete(int token, Object cookie, Cursor c) {
456        }
457
458        @Override
459        protected void onInsertComplete(int token, Object cookie, Uri uri) {
460            if (DBG) log("onInsertComplete");
461            displayProgress(false);
462            handleResult(uri != null, false);
463        }
464
465        @Override
466        protected void onUpdateComplete(int token, Object cookie, int result) {
467            if (DBG) log("onUpdateComplete");
468            displayProgress(false);
469            handleResult(result > 0, false);
470        }
471
472        @Override
473        protected void onDeleteComplete(int token, Object cookie, int result) {
474        }
475    }
476
477    private void log(String msg) {
478        Log.d(LOG_TAG, "[EditFdnContact] " + msg);
479    }
480}
481