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