• 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.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 
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 (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 
prepareSearchViewAndActionBar()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 
configureSearchMode()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
onOptionsItemSelected(MenuItem item)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
onSaveInstanceState(Bundle outState)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 
configureActivityTitle()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      */
configureListFragment()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 
getPhoneNumberPickerFragment(ContactsRequest request)368     private PhoneNumberPickerFragment getPhoneNumberPickerFragment(ContactsRequest request) {
369         if (mRequest.isLegacyCompatibilityMode()) {
370             return new LegacyPhoneNumberPickerFragment();
371         } else {
372             return new PhoneNumberPickerFragment();
373         }
374     }
375 
setupActionListener()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
onCreateNewContactAction()399         public void onCreateNewContactAction() {
400             startCreateNewContactActivity();
401         }
402 
403         @Override
onEditContactAction(Uri contactLookupUri)404         public void onEditContactAction(Uri contactLookupUri) {
405             startActivityAndForwardResult(EditorIntents.createEditContactIntent(
406                     contactLookupUri, /* materialPalette =*/ null, /* photoId =*/ -1));
407         }
408 
409         @Override
onPickContactAction(Uri contactUri)410         public void onPickContactAction(Uri contactUri) {
411             returnPickerResult(contactUri);
412         }
413 
414         @Override
onShortcutIntentCreated(Intent intent)415         public void onShortcutIntentCreated(Intent intent) {
416             returnPickerResult(intent);
417         }
418     }
419 
420     private final class PhoneNumberPickerActionListener implements
421             OnPhoneNumberPickerActionListener {
422         @Override
onPickDataUri(Uri dataUri, boolean isVideoCall, int callInitiationType)423         public void onPickDataUri(Uri dataUri, boolean isVideoCall, int callInitiationType) {
424             returnPickerResult(dataUri);
425         }
426 
427         @Override
onPickPhoneNumber(String phoneNumber, boolean isVideoCall, int callInitiationType)428         public void onPickPhoneNumber(String phoneNumber, boolean isVideoCall,
429                                       int callInitiationType) {
430             Log.w(TAG, "Unsupported call.");
431         }
432 
433         @Override
onShortcutIntentCreated(Intent intent)434         public void onShortcutIntentCreated(Intent intent) {
435             returnPickerResult(intent);
436         }
437 
438         @Override
onHomeInActionBarSelected()439         public void onHomeInActionBarSelected() {
440             ContactSelectionActivity.this.onBackPressed();
441         }
442     }
443 
444     private final class JoinContactActionListener implements OnContactPickerActionListener {
445         @Override
onPickContactAction(Uri contactUri)446         public void onPickContactAction(Uri contactUri) {
447             Intent intent = new Intent(null, contactUri);
448             setResult(RESULT_OK, intent);
449             finish();
450         }
451 
452         @Override
onShortcutIntentCreated(Intent intent)453         public void onShortcutIntentCreated(Intent intent) {
454         }
455 
456         @Override
onCreateNewContactAction()457         public void onCreateNewContactAction() {
458         }
459 
460         @Override
onEditContactAction(Uri contactLookupUri)461         public void onEditContactAction(Uri contactLookupUri) {
462         }
463     }
464 
465     private final class PostalAddressPickerActionListener implements
466             OnPostalAddressPickerActionListener {
467         @Override
onPickPostalAddressAction(Uri dataUri)468         public void onPickPostalAddressAction(Uri dataUri) {
469             returnPickerResult(dataUri);
470         }
471     }
472 
473     private final class EmailAddressPickerActionListener implements
474             OnEmailAddressPickerActionListener {
475         @Override
onPickEmailAddressAction(Uri dataUri)476         public void onPickEmailAddressAction(Uri dataUri) {
477             returnPickerResult(dataUri);
478         }
479     }
480 
startActivityAndForwardResult(final Intent intent)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
onQueryTextChange(String newText)500     public boolean onQueryTextChange(String newText) {
501         mListFragment.setQueryString(newText, true);
502         return false;
503     }
504 
505     @Override
onQueryTextSubmit(String query)506     public boolean onQueryTextSubmit(String query) {
507         return false;
508     }
509 
510     @Override
onClose()511     public boolean onClose() {
512         if (!TextUtils.isEmpty(mSearchView.getQuery())) {
513             mSearchView.setQuery(null, true);
514         }
515         return true;
516     }
517 
518     @Override
onFocusChange(View view, boolean hasFocus)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 
returnPickerResult(Uri data)529     public void returnPickerResult(Uri data) {
530         Intent intent = new Intent();
531         intent.setData(data);
532         returnPickerResult(intent);
533     }
534 
returnPickerResult(Intent intent)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
onClick(View view)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 
getTargetContactId()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 
startCreateNewContactActivity()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 
showInputMethod(View view)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
onCreateOptionsMenu(Menu menu)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
onBackPressed()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