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