• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ContactSelectionActivity()91     public ContactSelectionActivity() {
92         mIntentResolver = new ContactsIntentResolver(this);
93     }
94 
95     @Override
onAttachFragment(Fragment fragment)96     public void onAttachFragment(Fragment fragment) {
97         if (fragment instanceof ContactEntryListFragment<?>) {
98             mListFragment = (ContactEntryListFragment<?>) fragment;
99             setupActionListener();
100         }
101     }
102 
103     @Override
onCreate(Bundle savedState)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 
shouldShowCreateNewContactButton()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 
prepareSearchViewAndActionBar()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
onCreateOptionsMenu(Menu menu)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
onOptionsItemSelected(MenuItem item)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
onSaveInstanceState(Bundle outState)247     protected void onSaveInstanceState(Bundle outState) {
248         super.onSaveInstanceState(outState);
249         outState.putInt(KEY_ACTION_CODE, mActionCode);
250     }
251 
configureActivityTitle()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      */
configureListFragment()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 
getPhoneNumberPickerFragment(ContactsRequest request)388     private PhoneNumberPickerFragment getPhoneNumberPickerFragment(ContactsRequest request) {
389         if (mRequest.isLegacyCompatibilityMode()) {
390             return new LegacyPhoneNumberPickerFragment();
391         } else {
392             return new PhoneNumberPickerFragment();
393         }
394     }
395 
setupActionListener()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
onCreateNewContactAction()416         public void onCreateNewContactAction() {
417             startCreateNewContactActivity();
418         }
419 
420         @Override
onEditContactAction(Uri contactLookupUri)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
onPickContactAction(Uri contactUri)447         public void onPickContactAction(Uri contactUri) {
448             returnPickerResult(contactUri);
449         }
450 
451         @Override
onShortcutIntentCreated(Intent intent)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          */
launchAddToContactDialog(Bundle extras)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
onPickPhoneNumberAction(Uri dataUri)498         public void onPickPhoneNumberAction(Uri dataUri) {
499             returnPickerResult(dataUri);
500         }
501 
502         @Override
onShortcutIntentCreated(Intent intent)503         public void onShortcutIntentCreated(Intent intent) {
504             returnPickerResult(intent);
505         }
506 
onHomeInActionBarSelected()507         public void onHomeInActionBarSelected() {
508             ContactSelectionActivity.this.onBackPressed();
509         }
510     }
511 
512     private final class PostalAddressPickerActionListener implements
513             OnPostalAddressPickerActionListener {
514         @Override
onPickPostalAddressAction(Uri dataUri)515         public void onPickPostalAddressAction(Uri dataUri) {
516             returnPickerResult(dataUri);
517         }
518     }
519 
520     private final class EmailAddressPickerActionListener implements
521             OnEmailAddressPickerActionListener {
522         @Override
onPickEmailAddressAction(Uri dataUri)523         public void onPickEmailAddressAction(Uri dataUri) {
524             returnPickerResult(dataUri);
525         }
526     }
527 
startActivityAndForwardResult(final Intent intent)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
onQueryTextChange(String newText)541     public boolean onQueryTextChange(String newText) {
542         mListFragment.setQueryString(newText, true);
543         return false;
544     }
545 
546     @Override
onQueryTextSubmit(String query)547     public boolean onQueryTextSubmit(String query) {
548         return false;
549     }
550 
551     @Override
onClose()552     public boolean onClose() {
553         if (!TextUtils.isEmpty(mSearchView.getQuery())) {
554             mSearchView.setQuery(null, true);
555         }
556         return true;
557     }
558 
559     @Override
onFocusChange(View view, boolean hasFocus)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 
returnPickerResult(Uri data)570     public void returnPickerResult(Uri data) {
571         Intent intent = new Intent();
572         intent.setData(data);
573         returnPickerResult(intent);
574     }
575 
returnPickerResult(Intent intent)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
onClick(View view)583     public void onClick(View view) {
584         switch (view.getId()) {
585             case R.id.new_contact: {
586                 startCreateNewContactActivity();
587                 break;
588             }
589         }
590     }
591 
startCreateNewContactActivity()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 
showInputMethod(View view)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
onActivityResult(int requestCode, int resultCode, Intent data)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