ContactSelectionActivity.java revision 1db00f68b34f6cf7e9d19fedb559cf12f8c05e9c
1/*
2 * Copyright (C) 2007 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.contacts.activities;
18
19import com.android.contacts.ContactsActivity;
20import com.android.contacts.R;
21import com.android.contacts.list.ContactEntryListFragment;
22import com.android.contacts.list.ContactPickerFragment;
23import com.android.contacts.list.ContactsIntentResolver;
24import com.android.contacts.list.ContactsRequest;
25import com.android.contacts.list.DirectoryListLoader;
26import com.android.contacts.list.EmailAddressPickerFragment;
27import com.android.contacts.list.OnContactPickerActionListener;
28import com.android.contacts.list.OnEmailAddressPickerActionListener;
29import com.android.contacts.list.OnPhoneNumberPickerActionListener;
30import com.android.contacts.list.OnPostalAddressPickerActionListener;
31import com.android.contacts.list.PhoneNumberPickerFragment;
32import com.android.contacts.list.PostalAddressPickerFragment;
33import com.android.contacts.widget.ContextMenuAdapter;
34
35import android.app.ActionBar;
36import android.app.ActionBar.LayoutParams;
37import android.app.Activity;
38import android.app.Fragment;
39import android.content.Context;
40import android.content.Intent;
41import android.net.Uri;
42import android.os.Bundle;
43import android.provider.ContactsContract.Contacts;
44import android.provider.ContactsContract.Intents.Insert;
45import android.text.TextUtils;
46import android.util.Log;
47import android.view.LayoutInflater;
48import android.view.Menu;
49import android.view.MenuInflater;
50import android.view.MenuItem;
51import android.view.View;
52import android.view.View.OnClickListener;
53import android.view.View.OnFocusChangeListener;
54import android.view.inputmethod.InputMethodManager;
55import android.widget.Button;
56import android.widget.SearchView;
57import android.widget.SearchView.OnCloseListener;
58import android.widget.SearchView.OnQueryTextListener;
59
60import java.util.Set;
61
62/**
63 * Displays a list of contacts (or phone numbers or postal addresses) for the
64 * purposes of selecting one.
65 */
66public class ContactSelectionActivity extends ContactsActivity
67        implements View.OnCreateContextMenuListener, OnQueryTextListener, OnClickListener,
68                OnCloseListener, OnFocusChangeListener {
69    private static final String TAG = "ContactSelectionActivity";
70
71    private static final int SUBACTIVITY_ADD_TO_EXISTING_CONTACT = 0;
72
73    private static final String KEY_ACTION_CODE = "actionCode";
74    private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
75
76    // Delay to allow the UI to settle before making search view visible
77    private static final int FOCUS_DELAY = 200;
78
79    private ContactsIntentResolver mIntentResolver;
80    protected ContactEntryListFragment<?> mListFragment;
81
82    private int mActionCode = -1;
83
84    private ContactsRequest mRequest;
85    private SearchView mSearchView;
86    /**
87     * Can be null. If null, the "Create New Contact" button should be on the menu.
88     */
89    private Button mCreateNewContactButton;
90
91    public ContactSelectionActivity() {
92        mIntentResolver = new ContactsIntentResolver(this);
93    }
94
95    @Override
96    public void onAttachFragment(Fragment fragment) {
97        if (fragment instanceof ContactEntryListFragment<?>) {
98            mListFragment = (ContactEntryListFragment<?>) fragment;
99            setupActionListener();
100        }
101    }
102
103    @Override
104    protected void onCreate(Bundle savedState) {
105        super.onCreate(savedState);
106
107        if (savedState != null) {
108            mActionCode = savedState.getInt(KEY_ACTION_CODE);
109        }
110
111        // Extract relevant information from the intent
112        mRequest = mIntentResolver.resolveIntent(getIntent());
113        if (!mRequest.isValid()) {
114            setResult(RESULT_CANCELED);
115            finish();
116            return;
117        }
118
119        Intent redirect = mRequest.getRedirectIntent();
120        if (redirect != null) {
121            // Need to start a different activity
122            startActivity(redirect);
123            finish();
124            return;
125        }
126
127        configureActivityTitle();
128
129        setContentView(R.layout.contact_picker);
130
131        if (mActionCode != mRequest.getActionCode()) {
132            mActionCode = mRequest.getActionCode();
133            configureListFragment();
134        }
135
136        prepareSearchViewAndActionBar();
137
138        mCreateNewContactButton = (Button) findViewById(R.id.new_contact);
139        if (mCreateNewContactButton != null) {
140            if (shouldShowCreateNewContactButton()) {
141                mCreateNewContactButton.setVisibility(View.VISIBLE);
142                mCreateNewContactButton.setOnClickListener(this);
143            } else {
144                mCreateNewContactButton.setVisibility(View.GONE);
145            }
146        }
147    }
148
149    private boolean shouldShowCreateNewContactButton() {
150        return (mActionCode == ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT
151                || (mActionCode == ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT
152                        && !mRequest.isSearchMode()));
153    }
154
155    private void prepareSearchViewAndActionBar() {
156        // Postal address picker doesn't support search, so just show "HomeAsUp" button and title.
157        if (mRequest.getActionCode() == ContactsRequest.ACTION_PICK_POSTAL) {
158            findViewById(R.id.search_view).setVisibility(View.GONE);
159            final ActionBar actionBar = getActionBar();
160            if (actionBar != null) {
161                actionBar.setDisplayShowHomeEnabled(true);
162                actionBar.setDisplayHomeAsUpEnabled(true);
163                actionBar.setDisplayShowTitleEnabled(true);
164            }
165            return;
166        }
167
168        // If ActionBar is available, show SearchView on it. If not, show SearchView inside the
169        // Activity's layout.
170        final ActionBar actionBar = getActionBar();
171        if (actionBar != null) {
172            final View searchViewOnLayout = findViewById(R.id.search_view);
173            if (searchViewOnLayout != null) {
174                searchViewOnLayout.setVisibility(View.GONE);
175            }
176
177            final View searchViewContainer = LayoutInflater.from(actionBar.getThemedContext())
178                    .inflate(R.layout.custom_action_bar, null);
179            mSearchView = (SearchView) searchViewContainer.findViewById(R.id.search_view);
180
181            // In order to make the SearchView look like "shown via search menu", we need to
182            // manually setup its state. See also DialtactsActivity.java and ActionBarAdapter.java.
183            mSearchView.setIconifiedByDefault(true);
184            mSearchView.setQueryHint(getString(R.string.hint_findContacts));
185            mSearchView.setIconified(false);
186
187            mSearchView.setOnQueryTextListener(this);
188            mSearchView.setOnCloseListener(this);
189            mSearchView.setOnQueryTextFocusChangeListener(this);
190
191            actionBar.setCustomView(searchViewContainer,
192                    new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
193            actionBar.setDisplayShowCustomEnabled(true);
194            actionBar.setDisplayShowHomeEnabled(true);
195            actionBar.setDisplayHomeAsUpEnabled(true);
196        } else {
197            mSearchView = (SearchView) findViewById(R.id.search_view);
198            mSearchView.setQueryHint(getString(R.string.hint_findContacts));
199            mSearchView.setOnQueryTextListener(this);
200
201            // This is a hack to prevent the search view from grabbing focus
202            // at this point.  If search view were visible, it would always grabs focus
203            // because it is the first focusable widget in the window.
204            mSearchView.setVisibility(View.INVISIBLE);
205            mSearchView.postDelayed(new Runnable() {
206                @Override
207                public void run() {
208                    mSearchView.setVisibility(View.VISIBLE);
209                }
210            }, FOCUS_DELAY);
211        }
212    }
213
214    @Override
215    public boolean onCreateOptionsMenu(Menu menu) {
216        // If we want "Create New Contact" button but there's no such a button in the layout,
217        // try showing a menu for it.
218        if (shouldShowCreateNewContactButton() && mCreateNewContactButton == null) {
219            MenuInflater inflater = getMenuInflater();
220            inflater.inflate(R.menu.contact_picker_options, menu);
221        }
222        return true;
223    }
224
225    @Override
226    public void onStart() {
227        super.onStart();
228
229        if (mSearchView != null && mSearchView.getVisibility() == View.VISIBLE) {
230            if (mSearchView.hasFocus()) {
231                showInputMethod(mSearchView.findFocus());
232            } else {
233                mSearchView.requestFocus();
234            }
235        }
236    }
237
238    @Override
239    public boolean onOptionsItemSelected(MenuItem item) {
240        switch (item.getItemId()) {
241            case android.R.id.home:
242                // Go back to previous screen, intending "cancel"
243                setResult(RESULT_CANCELED);
244                finish();
245                return true;
246            case R.id.create_new_contact: {
247                startCreateNewContactActivity();
248                return true;
249            }
250        }
251        return super.onOptionsItemSelected(item);
252    }
253
254    @Override
255    protected void onSaveInstanceState(Bundle outState) {
256        super.onSaveInstanceState(outState);
257        outState.putInt(KEY_ACTION_CODE, mActionCode);
258    }
259
260    private void configureActivityTitle() {
261        if (mRequest.getActivityTitle() != null) {
262            setTitle(mRequest.getActivityTitle());
263            return;
264        }
265
266        int actionCode = mRequest.getActionCode();
267        switch (actionCode) {
268            case ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT: {
269                setTitle(R.string.contactPickerActivityTitle);
270                break;
271            }
272
273            case ContactsRequest.ACTION_PICK_CONTACT: {
274                setTitle(R.string.contactPickerActivityTitle);
275                break;
276            }
277
278            case ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT: {
279                setTitle(R.string.contactPickerActivityTitle);
280                break;
281            }
282
283            case ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT: {
284                setTitle(R.string.shortcutActivityTitle);
285                break;
286            }
287
288            case ContactsRequest.ACTION_PICK_PHONE: {
289                setTitle(R.string.contactPickerActivityTitle);
290                break;
291            }
292
293            case ContactsRequest.ACTION_PICK_EMAIL: {
294                setTitle(R.string.contactPickerActivityTitle);
295                break;
296            }
297
298            case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: {
299                setTitle(R.string.callShortcutActivityTitle);
300                break;
301            }
302
303            case ContactsRequest.ACTION_CREATE_SHORTCUT_SMS: {
304                setTitle(R.string.messageShortcutActivityTitle);
305                break;
306            }
307
308            case ContactsRequest.ACTION_PICK_POSTAL: {
309                setTitle(R.string.contactPickerActivityTitle);
310                break;
311            }
312        }
313    }
314
315    /**
316     * Creates the fragment based on the current request.
317     */
318    public void configureListFragment() {
319        switch (mActionCode) {
320            case ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT: {
321                ContactPickerFragment fragment = new ContactPickerFragment();
322                fragment.setEditMode(true);
323                fragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
324                mListFragment = fragment;
325                break;
326            }
327
328            case ContactsRequest.ACTION_PICK_CONTACT: {
329                ContactPickerFragment fragment = new ContactPickerFragment();
330                fragment.setIncludeProfile(mRequest.shouldIncludeProfile());
331                mListFragment = fragment;
332                break;
333            }
334
335            case ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT: {
336                ContactPickerFragment fragment = new ContactPickerFragment();
337                mListFragment = fragment;
338                break;
339            }
340
341            case ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT: {
342                ContactPickerFragment fragment = new ContactPickerFragment();
343                fragment.setShortcutRequested(true);
344                mListFragment = fragment;
345                break;
346            }
347
348            case ContactsRequest.ACTION_PICK_PHONE: {
349                PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment();
350                mListFragment = fragment;
351                break;
352            }
353
354            case ContactsRequest.ACTION_PICK_EMAIL: {
355                mListFragment = new EmailAddressPickerFragment();
356                break;
357            }
358
359            case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: {
360                PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment();
361                fragment.setShortcutAction(Intent.ACTION_CALL);
362
363                mListFragment = fragment;
364                break;
365            }
366
367            case ContactsRequest.ACTION_CREATE_SHORTCUT_SMS: {
368                PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment();
369                fragment.setShortcutAction(Intent.ACTION_SENDTO);
370
371                mListFragment = fragment;
372                break;
373            }
374
375            case ContactsRequest.ACTION_PICK_POSTAL: {
376                PostalAddressPickerFragment fragment = new PostalAddressPickerFragment();
377                mListFragment = fragment;
378                break;
379            }
380
381            default:
382                throw new IllegalStateException("Invalid action code: " + mActionCode);
383        }
384
385        mListFragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
386        mListFragment.setDirectoryResultLimit(DEFAULT_DIRECTORY_RESULT_LIMIT);
387
388        getFragmentManager().beginTransaction()
389                .replace(R.id.list_container, mListFragment)
390                .commitAllowingStateLoss();
391    }
392
393    public void setupActionListener() {
394        if (mListFragment instanceof ContactPickerFragment) {
395            ((ContactPickerFragment) mListFragment).setOnContactPickerActionListener(
396                    new ContactPickerActionListener());
397        } else if (mListFragment instanceof PhoneNumberPickerFragment) {
398            ((PhoneNumberPickerFragment) mListFragment).setOnPhoneNumberPickerActionListener(
399                    new PhoneNumberPickerActionListener());
400        } else if (mListFragment instanceof PostalAddressPickerFragment) {
401            ((PostalAddressPickerFragment) mListFragment).setOnPostalAddressPickerActionListener(
402                    new PostalAddressPickerActionListener());
403        } else if (mListFragment instanceof EmailAddressPickerFragment) {
404            ((EmailAddressPickerFragment) mListFragment).setOnEmailAddressPickerActionListener(
405                    new EmailAddressPickerActionListener());
406        } else {
407            throw new IllegalStateException("Unsupported list fragment type: " + mListFragment);
408        }
409    }
410
411    private final class ContactPickerActionListener implements OnContactPickerActionListener {
412        @Override
413        public void onCreateNewContactAction() {
414            startCreateNewContactActivity();
415        }
416
417        @Override
418        public void onEditContactAction(Uri contactLookupUri) {
419            Bundle extras = getIntent().getExtras();
420            if (launchAddToContactDialog(extras)) {
421                // Show a confirmation dialog to add the value(s) to the existing contact.
422                Intent intent = new Intent(ContactSelectionActivity.this,
423                        ConfirmAddDetailActivity.class);
424                intent.setData(contactLookupUri);
425                if (extras != null) {
426                    intent.putExtras(extras);
427                }
428                // Wait for the activity result because we want to keep the picker open (in case the
429                // user cancels adding the info to a contact and wants to pick someone else).
430                startActivityForResult(intent, SUBACTIVITY_ADD_TO_EXISTING_CONTACT);
431            } else {
432                // Otherwise launch the full contact editor.
433                startActivityAndForwardResult(new Intent(Intent.ACTION_EDIT, contactLookupUri));
434            }
435        }
436
437        @Override
438        public void onPickContactAction(Uri contactUri) {
439            returnPickerResult(contactUri);
440        }
441
442        @Override
443        public void onShortcutIntentCreated(Intent intent) {
444            returnPickerResult(intent);
445        }
446
447        /**
448         * Returns true if is a single email or single phone number provided in the {@link Intent}
449         * extras bundle so that a pop-up confirmation dialog can be used to add the data to
450         * a contact. Otherwise return false if there are other intent extras that require launching
451         * the full contact editor.
452         */
453        private boolean launchAddToContactDialog(Bundle extras) {
454            if (extras == null) {
455                return false;
456            }
457            Set<String> intentExtraKeys = extras.keySet();
458            int numIntentExtraKeys = intentExtraKeys.size();
459            if (numIntentExtraKeys == 2) {
460                boolean hasPhone = intentExtraKeys.contains(Insert.PHONE) &&
461                        intentExtraKeys.contains(Insert.PHONE_TYPE);
462                boolean hasEmail = intentExtraKeys.contains(Insert.EMAIL) &&
463                        intentExtraKeys.contains(Insert.EMAIL_TYPE);
464                return hasPhone || hasEmail;
465            } else if (numIntentExtraKeys == 1) {
466                return intentExtraKeys.contains(Insert.PHONE) ||
467                        intentExtraKeys.contains(Insert.EMAIL);
468            }
469            // Having 0 or more than 2 intent extra keys means that we should launch
470            // the full contact editor to properly handle the intent extras.
471            return false;
472        }
473    }
474
475    private final class PhoneNumberPickerActionListener implements
476            OnPhoneNumberPickerActionListener {
477        @Override
478        public void onPickPhoneNumberAction(Uri dataUri) {
479            returnPickerResult(dataUri);
480        }
481
482        @Override
483        public void onShortcutIntentCreated(Intent intent) {
484            returnPickerResult(intent);
485        }
486
487        public void onHomeInActionBarSelected() {
488            ContactSelectionActivity.this.onBackPressed();
489        }
490    }
491
492    private final class PostalAddressPickerActionListener implements
493            OnPostalAddressPickerActionListener {
494        @Override
495        public void onPickPostalAddressAction(Uri dataUri) {
496            returnPickerResult(dataUri);
497        }
498    }
499
500    private final class EmailAddressPickerActionListener implements
501            OnEmailAddressPickerActionListener {
502        @Override
503        public void onPickEmailAddressAction(Uri dataUri) {
504            returnPickerResult(dataUri);
505        }
506    }
507
508    public void startActivityAndForwardResult(final Intent intent) {
509        intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
510
511        // Forward extras to the new activity
512        Bundle extras = getIntent().getExtras();
513        if (extras != null) {
514            intent.putExtras(extras);
515        }
516        startActivity(intent);
517        finish();
518    }
519
520    @Override
521    public boolean onContextItemSelected(MenuItem item) {
522        ContextMenuAdapter menuAdapter = mListFragment.getContextMenuAdapter();
523        if (menuAdapter != null) {
524            return menuAdapter.onContextItemSelected(item);
525        }
526
527        return super.onContextItemSelected(item);
528    }
529
530    @Override
531    public boolean onQueryTextChange(String newText) {
532        mListFragment.setQueryString(newText, true);
533        return false;
534    }
535
536    @Override
537    public boolean onQueryTextSubmit(String query) {
538        return false;
539    }
540
541    @Override
542    public boolean onClose() {
543        if (!TextUtils.isEmpty(mSearchView.getQuery())) {
544            mSearchView.setQuery(null, true);
545        }
546        return true;
547    }
548
549    @Override
550    public void onFocusChange(View view, boolean hasFocus) {
551        switch (view.getId()) {
552            case R.id.search_view: {
553                if (hasFocus) {
554                    showInputMethod(mSearchView.findFocus());
555                }
556            }
557        }
558    }
559
560    public void returnPickerResult(Uri data) {
561        Intent intent = new Intent();
562        intent.setData(data);
563        returnPickerResult(intent);
564    }
565
566    public void returnPickerResult(Intent intent) {
567        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
568        setResult(RESULT_OK, intent);
569        finish();
570    }
571
572    @Override
573    public void onClick(View view) {
574        switch (view.getId()) {
575            case R.id.new_contact: {
576                startCreateNewContactActivity();
577                break;
578            }
579        }
580    }
581
582    private void startCreateNewContactActivity() {
583        Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
584        startActivityAndForwardResult(intent);
585    }
586
587    private void showInputMethod(View view) {
588        final InputMethodManager imm = (InputMethodManager)
589                getSystemService(Context.INPUT_METHOD_SERVICE);
590        if (imm != null) {
591            if (!imm.showSoftInput(view, 0)) {
592                Log.w(TAG, "Failed to show soft input method.");
593            }
594        }
595    }
596
597    @Override
598    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
599        super.onActivityResult(requestCode, resultCode, data);
600        if (requestCode == SUBACTIVITY_ADD_TO_EXISTING_CONTACT) {
601            if (resultCode == Activity.RESULT_OK) {
602                if (data != null) {
603                    startActivity(data);
604                }
605                finish();
606            }
607        }
608    }
609}
610