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.ActivityNotFoundException;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.net.Uri;
     27 import android.os.Bundle;
     28 import android.provider.ContactsContract.Contacts;
     29 import android.provider.ContactsContract.Intents.Insert;
     30 import android.text.TextUtils;
     31 import android.util.Log;
     32 import android.view.LayoutInflater;
     33 import android.view.Menu;
     34 import android.view.MenuInflater;
     35 import android.view.MenuItem;
     36 import android.view.View;
     37 import android.view.View.OnClickListener;
     38 import android.view.View.OnFocusChangeListener;
     39 import android.view.inputmethod.InputMethodManager;
     40 import android.widget.SearchView;
     41 import android.widget.SearchView.OnCloseListener;
     42 import android.widget.SearchView.OnQueryTextListener;
     43 import android.widget.Toast;
     44 
     45 import com.android.contacts.ContactsActivity;
     46 import com.android.contacts.R;
     47 import com.android.contacts.common.activity.RequestPermissionsActivity;
     48 import com.android.contacts.common.list.ContactEntryListFragment;
     49 import com.android.contacts.editor.EditorIntents;
     50 import com.android.contacts.list.ContactPickerFragment;
     51 import com.android.contacts.list.ContactsIntentResolver;
     52 import com.android.contacts.list.ContactsRequest;
     53 import com.android.contacts.common.list.DirectoryListLoader;
     54 import com.android.contacts.list.EmailAddressPickerFragment;
     55 import com.android.contacts.list.JoinContactListFragment;
     56 import com.android.contacts.list.LegacyPhoneNumberPickerFragment;
     57 import com.android.contacts.list.OnContactPickerActionListener;
     58 import com.android.contacts.list.OnEmailAddressPickerActionListener;
     59 import com.android.contacts.list.UiIntentActions;
     60 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
     61 import com.android.contacts.list.OnPostalAddressPickerActionListener;
     62 import com.android.contacts.common.list.PhoneNumberPickerFragment;
     63 import com.android.contacts.list.PostalAddressPickerFragment;
     64 
     65 import java.util.Set;
     66 
     67 /**
     68  * Displays a list of contacts (or phone numbers or postal addresses) for the
     69  * purposes of selecting one.
     70  */
     71 public class ContactSelectionActivity extends ContactsActivity
     72         implements View.OnCreateContextMenuListener, OnQueryTextListener, OnClickListener,
     73                 OnCloseListener, OnFocusChangeListener {
     74     private static final String TAG = "ContactSelectionActivity";
     75 
     76     private static final String KEY_ACTION_CODE = "actionCode";
     77     private static final String KEY_SEARCH_MODE = "searchMode";
     78     private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
     79 
     80     private ContactsIntentResolver mIntentResolver;
     81     protected ContactEntryListFragment<?> mListFragment;
     82 
     83     private int mActionCode = -1;
     84     private boolean mIsSearchMode;
     85     private boolean mIsSearchSupported;
     86 
     87     private ContactsRequest mRequest;
     88     private SearchView mSearchView;
     89     private View mSearchViewContainer;
     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 (RequestPermissionsActivity.startPermissionActivity(this)) {
    108             return;
    109         }
    110 
    111         if (savedState != null) {
    112             mActionCode = savedState.getInt(KEY_ACTION_CODE);
    113             mIsSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
    114         }
    115 
    116         // Extract relevant information from the intent
    117         mRequest = mIntentResolver.resolveIntent(getIntent());
    118         if (!mRequest.isValid()) {
    119             setResult(RESULT_CANCELED);
    120             finish();
    121             return;
    122         }
    123 
    124         configureActivityTitle();
    125 
    126         setContentView(R.layout.contact_picker);
    127 
    128         if (mActionCode != mRequest.getActionCode()) {
    129             mActionCode = mRequest.getActionCode();
    130             configureListFragment();
    131         }
    132 
    133         prepareSearchViewAndActionBar();
    134     }
    135 
    136     private void prepareSearchViewAndActionBar() {
    137         final ActionBar actionBar = getActionBar();
    138         mSearchViewContainer = LayoutInflater.from(actionBar.getThemedContext())
    139                 .inflate(R.layout.custom_action_bar, null);
    140         mSearchView = (SearchView) mSearchViewContainer.findViewById(R.id.search_view);
    141 
    142         // Postal address pickers (and legacy pickers) don't support search, so just show
    143         // "HomeAsUp" button and title.
    144         if (mRequest.getActionCode() == ContactsRequest.ACTION_PICK_POSTAL ||
    145                 mRequest.isLegacyCompatibilityMode()) {
    146             mSearchView.setVisibility(View.GONE);
    147             if (actionBar != null) {
    148                 actionBar.setDisplayShowHomeEnabled(true);
    149                 actionBar.setDisplayHomeAsUpEnabled(true);
    150                 actionBar.setDisplayShowTitleEnabled(true);
    151             }
    152             mIsSearchSupported = false;
    153             configureSearchMode();
    154             return;
    155         }
    156 
    157         actionBar.setDisplayShowHomeEnabled(true);
    158         actionBar.setDisplayHomeAsUpEnabled(true);
    159 
    160         // In order to make the SearchView look like "shown via search menu", we need to
    161         // manually setup its state. See also DialtactsActivity.java and ActionBarAdapter.java.
    162         mSearchView.setIconifiedByDefault(true);
    163         mSearchView.setQueryHint(getString(R.string.hint_findContacts));
    164         mSearchView.setIconified(false);
    165         mSearchView.setFocusable(true);
    166 
    167         mSearchView.setOnQueryTextListener(this);
    168         mSearchView.setOnCloseListener(this);
    169         mSearchView.setOnQueryTextFocusChangeListener(this);
    170 
    171         actionBar.setCustomView(mSearchViewContainer,
    172                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    173         actionBar.setDisplayShowCustomEnabled(true);
    174 
    175         mIsSearchSupported = true;
    176         configureSearchMode();
    177     }
    178 
    179     private void configureSearchMode() {
    180         final ActionBar actionBar = getActionBar();
    181         if (mIsSearchMode) {
    182             actionBar.setDisplayShowTitleEnabled(false);
    183             mSearchViewContainer.setVisibility(View.VISIBLE);
    184             mSearchView.requestFocus();
    185         } else {
    186             actionBar.setDisplayShowTitleEnabled(true);
    187             mSearchViewContainer.setVisibility(View.GONE);
    188             mSearchView.setQuery(null, true);
    189         }
    190         invalidateOptionsMenu();
    191     }
    192 
    193     @Override
    194     public boolean onOptionsItemSelected(MenuItem item) {
    195         switch (item.getItemId()) {
    196             case android.R.id.home:
    197                 // Go back to previous screen, intending "cancel"
    198                 setResult(RESULT_CANCELED);
    199                 onBackPressed();
    200                 return true;
    201             case R.id.menu_search:
    202                 mIsSearchMode = !mIsSearchMode;
    203                 configureSearchMode();
    204                 return true;
    205         }
    206         return super.onOptionsItemSelected(item);
    207     }
    208 
    209     @Override
    210     protected void onSaveInstanceState(Bundle outState) {
    211         super.onSaveInstanceState(outState);
    212         outState.putInt(KEY_ACTION_CODE, mActionCode);
    213         outState.putBoolean(KEY_SEARCH_MODE, mIsSearchMode);
    214     }
    215 
    216     private void configureActivityTitle() {
    217         if (!TextUtils.isEmpty(mRequest.getActivityTitle())) {
    218             setTitle(mRequest.getActivityTitle());
    219             return;
    220         }
    221 
    222         int actionCode = mRequest.getActionCode();
    223         switch (actionCode) {
    224             case ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT: {
    225                 setTitle(R.string.contactInsertOrEditActivityTitle);
    226                 break;
    227             }
    228 
    229             case ContactsRequest.ACTION_PICK_CONTACT: {
    230                 setTitle(R.string.contactPickerActivityTitle);
    231                 break;
    232             }
    233 
    234             case ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT: {
    235                 setTitle(R.string.contactPickerActivityTitle);
    236                 break;
    237             }
    238 
    239             case ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT: {
    240                 setTitle(R.string.shortcutActivityTitle);
    241                 break;
    242             }
    243 
    244             case ContactsRequest.ACTION_PICK_PHONE: {
    245                 setTitle(R.string.contactPickerActivityTitle);
    246                 break;
    247             }
    248 
    249             case ContactsRequest.ACTION_PICK_EMAIL: {
    250                 setTitle(R.string.contactPickerActivityTitle);
    251                 break;
    252             }
    253 
    254             case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: {
    255                 setTitle(R.string.callShortcutActivityTitle);
    256                 break;
    257             }
    258 
    259             case ContactsRequest.ACTION_CREATE_SHORTCUT_SMS: {
    260                 setTitle(R.string.messageShortcutActivityTitle);
    261                 break;
    262             }
    263 
    264             case ContactsRequest.ACTION_PICK_POSTAL: {
    265                 setTitle(R.string.contactPickerActivityTitle);
    266                 break;
    267             }
    268 
    269             case ContactsRequest.ACTION_PICK_JOIN: {
    270                 setTitle(R.string.titleJoinContactDataWith);
    271                 break;
    272             }
    273         }
    274     }
    275 
    276     /**
    277      * Creates the fragment based on the current request.
    278      */
    279     public void configureListFragment() {
    280         switch (mActionCode) {
    281             case ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT: {
    282                 ContactPickerFragment fragment = new ContactPickerFragment();
    283                 fragment.setEditMode(true);
    284                 fragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
    285                 fragment.setCreateContactEnabled(!mRequest.isSearchMode());
    286                 mListFragment = fragment;
    287                 break;
    288             }
    289 
    290             case ContactsRequest.ACTION_DEFAULT:
    291             case ContactsRequest.ACTION_PICK_CONTACT: {
    292                 ContactPickerFragment fragment = new ContactPickerFragment();
    293                 fragment.setIncludeProfile(mRequest.shouldIncludeProfile());
    294                 mListFragment = fragment;
    295                 break;
    296             }
    297 
    298             case ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT: {
    299                 ContactPickerFragment fragment = new ContactPickerFragment();
    300                 fragment.setCreateContactEnabled(!mRequest.isSearchMode());
    301                 mListFragment = fragment;
    302                 break;
    303             }
    304 
    305             case ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT: {
    306                 ContactPickerFragment fragment = new ContactPickerFragment();
    307                 fragment.setShortcutRequested(true);
    308                 mListFragment = fragment;
    309                 break;
    310             }
    311 
    312             case ContactsRequest.ACTION_PICK_PHONE: {
    313                 PhoneNumberPickerFragment fragment = getPhoneNumberPickerFragment(mRequest);
    314                 mListFragment = fragment;
    315                 break;
    316             }
    317 
    318             case ContactsRequest.ACTION_PICK_EMAIL: {
    319                 mListFragment = new EmailAddressPickerFragment();
    320                 break;
    321             }
    322 
    323             case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: {
    324                 PhoneNumberPickerFragment fragment = getPhoneNumberPickerFragment(mRequest);
    325                 fragment.setShortcutAction(Intent.ACTION_CALL);
    326 
    327                 mListFragment = fragment;
    328                 break;
    329             }
    330 
    331             case ContactsRequest.ACTION_CREATE_SHORTCUT_SMS: {
    332                 PhoneNumberPickerFragment fragment = getPhoneNumberPickerFragment(mRequest);
    333                 fragment.setShortcutAction(Intent.ACTION_SENDTO);
    334 
    335                 mListFragment = fragment;
    336                 break;
    337             }
    338 
    339             case ContactsRequest.ACTION_PICK_POSTAL: {
    340                 PostalAddressPickerFragment fragment = new PostalAddressPickerFragment();
    341 
    342                 mListFragment = fragment;
    343                 break;
    344             }
    345 
    346             case ContactsRequest.ACTION_PICK_JOIN: {
    347                 JoinContactListFragment joinFragment = new JoinContactListFragment();
    348                 joinFragment.setTargetContactId(getTargetContactId());
    349                 mListFragment = joinFragment;
    350                 break;
    351             }
    352 
    353             default:
    354                 throw new IllegalStateException("Invalid action code: " + mActionCode);
    355         }
    356 
    357         // Setting compatibility is no longer needed for PhoneNumberPickerFragment since that logic
    358         // has been separated into LegacyPhoneNumberPickerFragment.  But we still need to set
    359         // compatibility for other fragments.
    360         mListFragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
    361         mListFragment.setDirectoryResultLimit(DEFAULT_DIRECTORY_RESULT_LIMIT);
    362 
    363         getFragmentManager().beginTransaction()
    364                 .replace(R.id.list_container, mListFragment)
    365                 .commitAllowingStateLoss();
    366     }
    367 
    368     private PhoneNumberPickerFragment getPhoneNumberPickerFragment(ContactsRequest request) {
    369         if (mRequest.isLegacyCompatibilityMode()) {
    370             return new LegacyPhoneNumberPickerFragment();
    371         } else {
    372             return new PhoneNumberPickerFragment();
    373         }
    374     }
    375 
    376     public void setupActionListener() {
    377         if (mListFragment instanceof ContactPickerFragment) {
    378             ((ContactPickerFragment) mListFragment).setOnContactPickerActionListener(
    379                     new ContactPickerActionListener());
    380         } else if (mListFragment instanceof PhoneNumberPickerFragment) {
    381             ((PhoneNumberPickerFragment) mListFragment).setOnPhoneNumberPickerActionListener(
    382                     new PhoneNumberPickerActionListener());
    383         } else if (mListFragment instanceof PostalAddressPickerFragment) {
    384             ((PostalAddressPickerFragment) mListFragment).setOnPostalAddressPickerActionListener(
    385                     new PostalAddressPickerActionListener());
    386         } else if (mListFragment instanceof EmailAddressPickerFragment) {
    387             ((EmailAddressPickerFragment) mListFragment).setOnEmailAddressPickerActionListener(
    388                     new EmailAddressPickerActionListener());
    389         } else if (mListFragment instanceof JoinContactListFragment) {
    390             ((JoinContactListFragment) mListFragment).setOnContactPickerActionListener(
    391                     new JoinContactActionListener());
    392         } else {
    393             throw new IllegalStateException("Unsupported list fragment type: " + mListFragment);
    394         }
    395     }
    396 
    397     private final class ContactPickerActionListener implements OnContactPickerActionListener {
    398         @Override
    399         public void onCreateNewContactAction() {
    400             startCreateNewContactActivity();
    401         }
    402 
    403         @Override
    404         public void onEditContactAction(Uri contactLookupUri) {
    405             startActivityAndForwardResult(EditorIntents.createEditContactIntent(
    406                     contactLookupUri, /* materialPalette =*/ null, /* photoId =*/ -1));
    407         }
    408 
    409         @Override
    410         public void onPickContactAction(Uri contactUri) {
    411             returnPickerResult(contactUri);
    412         }
    413 
    414         @Override
    415         public void onShortcutIntentCreated(Intent intent) {
    416             returnPickerResult(intent);
    417         }
    418     }
    419 
    420     private final class PhoneNumberPickerActionListener implements
    421             OnPhoneNumberPickerActionListener {
    422         @Override
    423         public void onPickDataUri(Uri dataUri, boolean isVideoCall, int callInitiationType) {
    424             returnPickerResult(dataUri);
    425         }
    426 
    427         @Override
    428         public void onPickPhoneNumber(String phoneNumber, boolean isVideoCall,
    429                                       int callInitiationType) {
    430             Log.w(TAG, "Unsupported call.");
    431         }
    432 
    433         @Override
    434         public void onShortcutIntentCreated(Intent intent) {
    435             returnPickerResult(intent);
    436         }
    437 
    438         @Override
    439         public void onHomeInActionBarSelected() {
    440             ContactSelectionActivity.this.onBackPressed();
    441         }
    442     }
    443 
    444     private final class JoinContactActionListener implements OnContactPickerActionListener {
    445         @Override
    446         public void onPickContactAction(Uri contactUri) {
    447             Intent intent = new Intent(null, contactUri);
    448             setResult(RESULT_OK, intent);
    449             finish();
    450         }
    451 
    452         @Override
    453         public void onShortcutIntentCreated(Intent intent) {
    454         }
    455 
    456         @Override
    457         public void onCreateNewContactAction() {
    458         }
    459 
    460         @Override
    461         public void onEditContactAction(Uri contactLookupUri) {
    462         }
    463     }
    464 
    465     private final class PostalAddressPickerActionListener implements
    466             OnPostalAddressPickerActionListener {
    467         @Override
    468         public void onPickPostalAddressAction(Uri dataUri) {
    469             returnPickerResult(dataUri);
    470         }
    471     }
    472 
    473     private final class EmailAddressPickerActionListener implements
    474             OnEmailAddressPickerActionListener {
    475         @Override
    476         public void onPickEmailAddressAction(Uri dataUri) {
    477             returnPickerResult(dataUri);
    478         }
    479     }
    480 
    481     public void startActivityAndForwardResult(final Intent intent) {
    482         intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    483 
    484         // Forward extras to the new activity
    485         Bundle extras = getIntent().getExtras();
    486         if (extras != null) {
    487             intent.putExtras(extras);
    488         }
    489         try {
    490             startActivity(intent);
    491         } catch (ActivityNotFoundException e) {
    492             Log.e(TAG, "startActivity() failed: " + e);
    493             Toast.makeText(ContactSelectionActivity.this, R.string.missing_app,
    494                     Toast.LENGTH_SHORT).show();
    495         }
    496         finish();
    497     }
    498 
    499     @Override
    500     public boolean onQueryTextChange(String newText) {
    501         mListFragment.setQueryString(newText, true);
    502         return false;
    503     }
    504 
    505     @Override
    506     public boolean onQueryTextSubmit(String query) {
    507         return false;
    508     }
    509 
    510     @Override
    511     public boolean onClose() {
    512         if (!TextUtils.isEmpty(mSearchView.getQuery())) {
    513             mSearchView.setQuery(null, true);
    514         }
    515         return true;
    516     }
    517 
    518     @Override
    519     public void onFocusChange(View view, boolean hasFocus) {
    520         switch (view.getId()) {
    521             case R.id.search_view: {
    522                 if (hasFocus) {
    523                     showInputMethod(mSearchView.findFocus());
    524                 }
    525             }
    526         }
    527     }
    528 
    529     public void returnPickerResult(Uri data) {
    530         Intent intent = new Intent();
    531         intent.setData(data);
    532         returnPickerResult(intent);
    533     }
    534 
    535     public void returnPickerResult(Intent intent) {
    536         intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    537         setResult(RESULT_OK, intent);
    538         finish();
    539     }
    540 
    541     @Override
    542     public void onClick(View view) {
    543         switch (view.getId()) {
    544             case R.id.floating_action_button: {
    545                 startCreateNewContactActivity();
    546                 break;
    547             }
    548         }
    549     }
    550 
    551     private long getTargetContactId() {
    552         Intent intent = getIntent();
    553         final long targetContactId = intent.getLongExtra(
    554                 UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, -1);
    555         if (targetContactId == -1) {
    556             Log.e(TAG, "Intent " + intent.getAction() + " is missing required extra: "
    557                     + UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY);
    558             setResult(RESULT_CANCELED);
    559             finish();
    560             return -1;
    561         }
    562         return targetContactId;
    563     }
    564 
    565     private void startCreateNewContactActivity() {
    566         Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
    567         intent.putExtra(ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
    568         startActivityAndForwardResult(intent);
    569     }
    570 
    571     private void showInputMethod(View view) {
    572         final InputMethodManager imm = (InputMethodManager)
    573                 getSystemService(Context.INPUT_METHOD_SERVICE);
    574         if (imm != null) {
    575             if (!imm.showSoftInput(view, 0)) {
    576                 Log.w(TAG, "Failed to show soft input method.");
    577             }
    578         }
    579     }
    580 
    581     @Override
    582     public boolean onCreateOptionsMenu(Menu menu) {
    583         super.onCreateOptionsMenu(menu);
    584 
    585         final MenuInflater inflater = getMenuInflater();
    586         inflater.inflate(R.menu.search_menu, menu);
    587 
    588         final MenuItem searchItem = menu.findItem(R.id.menu_search);
    589         searchItem.setVisible(!mIsSearchMode && mIsSearchSupported);
    590         return true;
    591     }
    592 
    593     @Override
    594     public void onBackPressed() {
    595         if (!isSafeToCommitTransactions()) {
    596             return;
    597         }
    598 
    599         if (mIsSearchMode) {
    600             mIsSearchMode = false;
    601             configureSearchMode();
    602         } else {
    603             super.onBackPressed();
    604         }
    605     }
    606 }
    607