• 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 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 
ContactSelectionActivity()92     public ContactSelectionActivity() {
93         mIntentResolver = new ContactsIntentResolver(this);
94     }
95 
96     @Override
onAttachFragment(Fragment fragment)97     public void onAttachFragment(Fragment fragment) {
98         if (fragment instanceof ContactEntryListFragment<?>) {
99             mListFragment = (ContactEntryListFragment<?>) fragment;
100             setupActionListener();
101         }
102     }
103 
104     @Override
onCreate(Bundle savedState)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 
shouldShowCreateNewContactButton()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 
prepareSearchViewAndActionBar()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
onCreateOptionsMenu(Menu menu)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
onOptionsItemSelected(MenuItem item)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
onSaveInstanceState(Bundle outState)248     protected void onSaveInstanceState(Bundle outState) {
249         super.onSaveInstanceState(outState);
250         outState.putInt(KEY_ACTION_CODE, mActionCode);
251     }
252 
configureActivityTitle()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      */
configureListFragment()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 
setupActionListener()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
onCreateNewContactAction()406         public void onCreateNewContactAction() {
407             startCreateNewContactActivity();
408         }
409 
410         @Override
onEditContactAction(Uri contactLookupUri)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
onPickContactAction(Uri contactUri)437         public void onPickContactAction(Uri contactUri) {
438             returnPickerResult(contactUri);
439         }
440 
441         @Override
onShortcutIntentCreated(Intent intent)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          */
launchAddToContactDialog(Bundle extras)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
onPickPhoneNumberAction(Uri dataUri)488         public void onPickPhoneNumberAction(Uri dataUri) {
489             returnPickerResult(dataUri);
490         }
491 
492         @Override
onShortcutIntentCreated(Intent intent)493         public void onShortcutIntentCreated(Intent intent) {
494             returnPickerResult(intent);
495         }
496 
onHomeInActionBarSelected()497         public void onHomeInActionBarSelected() {
498             ContactSelectionActivity.this.onBackPressed();
499         }
500     }
501 
502     private final class PostalAddressPickerActionListener implements
503             OnPostalAddressPickerActionListener {
504         @Override
onPickPostalAddressAction(Uri dataUri)505         public void onPickPostalAddressAction(Uri dataUri) {
506             returnPickerResult(dataUri);
507         }
508     }
509 
510     private final class EmailAddressPickerActionListener implements
511             OnEmailAddressPickerActionListener {
512         @Override
onPickEmailAddressAction(Uri dataUri)513         public void onPickEmailAddressAction(Uri dataUri) {
514             returnPickerResult(dataUri);
515         }
516     }
517 
startActivityAndForwardResult(final Intent intent)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
onContextItemSelected(MenuItem item)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
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