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