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