ContactSelectionActivity.java revision 1db00f68b34f6cf7e9d19fedb559cf12f8c05e9c
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 17package com.android.contacts.activities; 18 19import com.android.contacts.ContactsActivity; 20import com.android.contacts.R; 21import com.android.contacts.list.ContactEntryListFragment; 22import com.android.contacts.list.ContactPickerFragment; 23import com.android.contacts.list.ContactsIntentResolver; 24import com.android.contacts.list.ContactsRequest; 25import com.android.contacts.list.DirectoryListLoader; 26import com.android.contacts.list.EmailAddressPickerFragment; 27import com.android.contacts.list.OnContactPickerActionListener; 28import com.android.contacts.list.OnEmailAddressPickerActionListener; 29import com.android.contacts.list.OnPhoneNumberPickerActionListener; 30import com.android.contacts.list.OnPostalAddressPickerActionListener; 31import com.android.contacts.list.PhoneNumberPickerFragment; 32import com.android.contacts.list.PostalAddressPickerFragment; 33import com.android.contacts.widget.ContextMenuAdapter; 34 35import android.app.ActionBar; 36import android.app.ActionBar.LayoutParams; 37import android.app.Activity; 38import android.app.Fragment; 39import android.content.Context; 40import android.content.Intent; 41import android.net.Uri; 42import android.os.Bundle; 43import android.provider.ContactsContract.Contacts; 44import android.provider.ContactsContract.Intents.Insert; 45import android.text.TextUtils; 46import android.util.Log; 47import android.view.LayoutInflater; 48import android.view.Menu; 49import android.view.MenuInflater; 50import android.view.MenuItem; 51import android.view.View; 52import android.view.View.OnClickListener; 53import android.view.View.OnFocusChangeListener; 54import android.view.inputmethod.InputMethodManager; 55import android.widget.Button; 56import android.widget.SearchView; 57import android.widget.SearchView.OnCloseListener; 58import android.widget.SearchView.OnQueryTextListener; 59 60import 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 */ 66public 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 Button mCreateNewContactButton; 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 (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 = (Button) 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 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 155 private void prepareSearchViewAndActionBar() { 156 // Postal address picker doesn't support search, so just show "HomeAsUp" button and title. 157 if (mRequest.getActionCode() == ContactsRequest.ACTION_PICK_POSTAL) { 158 findViewById(R.id.search_view).setVisibility(View.GONE); 159 final ActionBar actionBar = getActionBar(); 160 if (actionBar != null) { 161 actionBar.setDisplayShowHomeEnabled(true); 162 actionBar.setDisplayHomeAsUpEnabled(true); 163 actionBar.setDisplayShowTitleEnabled(true); 164 } 165 return; 166 } 167 168 // If ActionBar is available, show SearchView on it. If not, show SearchView inside the 169 // Activity's layout. 170 final ActionBar actionBar = getActionBar(); 171 if (actionBar != null) { 172 final View searchViewOnLayout = findViewById(R.id.search_view); 173 if (searchViewOnLayout != null) { 174 searchViewOnLayout.setVisibility(View.GONE); 175 } 176 177 final View searchViewContainer = LayoutInflater.from(actionBar.getThemedContext()) 178 .inflate(R.layout.custom_action_bar, null); 179 mSearchView = (SearchView) searchViewContainer.findViewById(R.id.search_view); 180 181 // In order to make the SearchView look like "shown via search menu", we need to 182 // manually setup its state. See also DialtactsActivity.java and ActionBarAdapter.java. 183 mSearchView.setIconifiedByDefault(true); 184 mSearchView.setQueryHint(getString(R.string.hint_findContacts)); 185 mSearchView.setIconified(false); 186 187 mSearchView.setOnQueryTextListener(this); 188 mSearchView.setOnCloseListener(this); 189 mSearchView.setOnQueryTextFocusChangeListener(this); 190 191 actionBar.setCustomView(searchViewContainer, 192 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 193 actionBar.setDisplayShowCustomEnabled(true); 194 actionBar.setDisplayShowHomeEnabled(true); 195 actionBar.setDisplayHomeAsUpEnabled(true); 196 } else { 197 mSearchView = (SearchView) findViewById(R.id.search_view); 198 mSearchView.setQueryHint(getString(R.string.hint_findContacts)); 199 mSearchView.setOnQueryTextListener(this); 200 201 // This is a hack to prevent the search view from grabbing focus 202 // at this point. If search view were visible, it would always grabs focus 203 // because it is the first focusable widget in the window. 204 mSearchView.setVisibility(View.INVISIBLE); 205 mSearchView.postDelayed(new Runnable() { 206 @Override 207 public void run() { 208 mSearchView.setVisibility(View.VISIBLE); 209 } 210 }, FOCUS_DELAY); 211 } 212 } 213 214 @Override 215 public boolean onCreateOptionsMenu(Menu menu) { 216 // If we want "Create New Contact" button but there's no such a button in the layout, 217 // try showing a menu for it. 218 if (shouldShowCreateNewContactButton() && mCreateNewContactButton == null) { 219 MenuInflater inflater = getMenuInflater(); 220 inflater.inflate(R.menu.contact_picker_options, menu); 221 } 222 return true; 223 } 224 225 @Override 226 public void onStart() { 227 super.onStart(); 228 229 if (mSearchView != null && mSearchView.getVisibility() == View.VISIBLE) { 230 if (mSearchView.hasFocus()) { 231 showInputMethod(mSearchView.findFocus()); 232 } else { 233 mSearchView.requestFocus(); 234 } 235 } 236 } 237 238 @Override 239 public boolean onOptionsItemSelected(MenuItem item) { 240 switch (item.getItemId()) { 241 case android.R.id.home: 242 // Go back to previous screen, intending "cancel" 243 setResult(RESULT_CANCELED); 244 finish(); 245 return true; 246 case R.id.create_new_contact: { 247 startCreateNewContactActivity(); 248 return true; 249 } 250 } 251 return super.onOptionsItemSelected(item); 252 } 253 254 @Override 255 protected void onSaveInstanceState(Bundle outState) { 256 super.onSaveInstanceState(outState); 257 outState.putInt(KEY_ACTION_CODE, mActionCode); 258 } 259 260 private void configureActivityTitle() { 261 if (mRequest.getActivityTitle() != null) { 262 setTitle(mRequest.getActivityTitle()); 263 return; 264 } 265 266 int actionCode = mRequest.getActionCode(); 267 switch (actionCode) { 268 case ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT: { 269 setTitle(R.string.contactPickerActivityTitle); 270 break; 271 } 272 273 case ContactsRequest.ACTION_PICK_CONTACT: { 274 setTitle(R.string.contactPickerActivityTitle); 275 break; 276 } 277 278 case ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT: { 279 setTitle(R.string.contactPickerActivityTitle); 280 break; 281 } 282 283 case ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT: { 284 setTitle(R.string.shortcutActivityTitle); 285 break; 286 } 287 288 case ContactsRequest.ACTION_PICK_PHONE: { 289 setTitle(R.string.contactPickerActivityTitle); 290 break; 291 } 292 293 case ContactsRequest.ACTION_PICK_EMAIL: { 294 setTitle(R.string.contactPickerActivityTitle); 295 break; 296 } 297 298 case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: { 299 setTitle(R.string.callShortcutActivityTitle); 300 break; 301 } 302 303 case ContactsRequest.ACTION_CREATE_SHORTCUT_SMS: { 304 setTitle(R.string.messageShortcutActivityTitle); 305 break; 306 } 307 308 case ContactsRequest.ACTION_PICK_POSTAL: { 309 setTitle(R.string.contactPickerActivityTitle); 310 break; 311 } 312 } 313 } 314 315 /** 316 * Creates the fragment based on the current request. 317 */ 318 public void configureListFragment() { 319 switch (mActionCode) { 320 case ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT: { 321 ContactPickerFragment fragment = new ContactPickerFragment(); 322 fragment.setEditMode(true); 323 fragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); 324 mListFragment = fragment; 325 break; 326 } 327 328 case ContactsRequest.ACTION_PICK_CONTACT: { 329 ContactPickerFragment fragment = new ContactPickerFragment(); 330 fragment.setIncludeProfile(mRequest.shouldIncludeProfile()); 331 mListFragment = fragment; 332 break; 333 } 334 335 case ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT: { 336 ContactPickerFragment fragment = new ContactPickerFragment(); 337 mListFragment = fragment; 338 break; 339 } 340 341 case ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT: { 342 ContactPickerFragment fragment = new ContactPickerFragment(); 343 fragment.setShortcutRequested(true); 344 mListFragment = fragment; 345 break; 346 } 347 348 case ContactsRequest.ACTION_PICK_PHONE: { 349 PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment(); 350 mListFragment = fragment; 351 break; 352 } 353 354 case ContactsRequest.ACTION_PICK_EMAIL: { 355 mListFragment = new EmailAddressPickerFragment(); 356 break; 357 } 358 359 case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: { 360 PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment(); 361 fragment.setShortcutAction(Intent.ACTION_CALL); 362 363 mListFragment = fragment; 364 break; 365 } 366 367 case ContactsRequest.ACTION_CREATE_SHORTCUT_SMS: { 368 PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment(); 369 fragment.setShortcutAction(Intent.ACTION_SENDTO); 370 371 mListFragment = fragment; 372 break; 373 } 374 375 case ContactsRequest.ACTION_PICK_POSTAL: { 376 PostalAddressPickerFragment fragment = new PostalAddressPickerFragment(); 377 mListFragment = fragment; 378 break; 379 } 380 381 default: 382 throw new IllegalStateException("Invalid action code: " + mActionCode); 383 } 384 385 mListFragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode()); 386 mListFragment.setDirectoryResultLimit(DEFAULT_DIRECTORY_RESULT_LIMIT); 387 388 getFragmentManager().beginTransaction() 389 .replace(R.id.list_container, mListFragment) 390 .commitAllowingStateLoss(); 391 } 392 393 public void setupActionListener() { 394 if (mListFragment instanceof ContactPickerFragment) { 395 ((ContactPickerFragment) mListFragment).setOnContactPickerActionListener( 396 new ContactPickerActionListener()); 397 } else if (mListFragment instanceof PhoneNumberPickerFragment) { 398 ((PhoneNumberPickerFragment) mListFragment).setOnPhoneNumberPickerActionListener( 399 new PhoneNumberPickerActionListener()); 400 } else if (mListFragment instanceof PostalAddressPickerFragment) { 401 ((PostalAddressPickerFragment) mListFragment).setOnPostalAddressPickerActionListener( 402 new PostalAddressPickerActionListener()); 403 } else if (mListFragment instanceof EmailAddressPickerFragment) { 404 ((EmailAddressPickerFragment) mListFragment).setOnEmailAddressPickerActionListener( 405 new EmailAddressPickerActionListener()); 406 } else { 407 throw new IllegalStateException("Unsupported list fragment type: " + mListFragment); 408 } 409 } 410 411 private final class ContactPickerActionListener implements OnContactPickerActionListener { 412 @Override 413 public void onCreateNewContactAction() { 414 startCreateNewContactActivity(); 415 } 416 417 @Override 418 public void onEditContactAction(Uri contactLookupUri) { 419 Bundle extras = getIntent().getExtras(); 420 if (launchAddToContactDialog(extras)) { 421 // Show a confirmation dialog to add the value(s) to the existing contact. 422 Intent intent = new Intent(ContactSelectionActivity.this, 423 ConfirmAddDetailActivity.class); 424 intent.setData(contactLookupUri); 425 if (extras != null) { 426 intent.putExtras(extras); 427 } 428 // Wait for the activity result because we want to keep the picker open (in case the 429 // user cancels adding the info to a contact and wants to pick someone else). 430 startActivityForResult(intent, SUBACTIVITY_ADD_TO_EXISTING_CONTACT); 431 } else { 432 // Otherwise launch the full contact editor. 433 startActivityAndForwardResult(new Intent(Intent.ACTION_EDIT, contactLookupUri)); 434 } 435 } 436 437 @Override 438 public void onPickContactAction(Uri contactUri) { 439 returnPickerResult(contactUri); 440 } 441 442 @Override 443 public void onShortcutIntentCreated(Intent intent) { 444 returnPickerResult(intent); 445 } 446 447 /** 448 * Returns true if is a single email or single phone number provided in the {@link Intent} 449 * extras bundle so that a pop-up confirmation dialog can be used to add the data to 450 * a contact. Otherwise return false if there are other intent extras that require launching 451 * the full contact editor. 452 */ 453 private boolean launchAddToContactDialog(Bundle extras) { 454 if (extras == null) { 455 return false; 456 } 457 Set<String> intentExtraKeys = extras.keySet(); 458 int numIntentExtraKeys = intentExtraKeys.size(); 459 if (numIntentExtraKeys == 2) { 460 boolean hasPhone = intentExtraKeys.contains(Insert.PHONE) && 461 intentExtraKeys.contains(Insert.PHONE_TYPE); 462 boolean hasEmail = intentExtraKeys.contains(Insert.EMAIL) && 463 intentExtraKeys.contains(Insert.EMAIL_TYPE); 464 return hasPhone || hasEmail; 465 } else if (numIntentExtraKeys == 1) { 466 return intentExtraKeys.contains(Insert.PHONE) || 467 intentExtraKeys.contains(Insert.EMAIL); 468 } 469 // Having 0 or more than 2 intent extra keys means that we should launch 470 // the full contact editor to properly handle the intent extras. 471 return false; 472 } 473 } 474 475 private final class PhoneNumberPickerActionListener implements 476 OnPhoneNumberPickerActionListener { 477 @Override 478 public void onPickPhoneNumberAction(Uri dataUri) { 479 returnPickerResult(dataUri); 480 } 481 482 @Override 483 public void onShortcutIntentCreated(Intent intent) { 484 returnPickerResult(intent); 485 } 486 487 public void onHomeInActionBarSelected() { 488 ContactSelectionActivity.this.onBackPressed(); 489 } 490 } 491 492 private final class PostalAddressPickerActionListener implements 493 OnPostalAddressPickerActionListener { 494 @Override 495 public void onPickPostalAddressAction(Uri dataUri) { 496 returnPickerResult(dataUri); 497 } 498 } 499 500 private final class EmailAddressPickerActionListener implements 501 OnEmailAddressPickerActionListener { 502 @Override 503 public void onPickEmailAddressAction(Uri dataUri) { 504 returnPickerResult(dataUri); 505 } 506 } 507 508 public void startActivityAndForwardResult(final Intent intent) { 509 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 510 511 // Forward extras to the new activity 512 Bundle extras = getIntent().getExtras(); 513 if (extras != null) { 514 intent.putExtras(extras); 515 } 516 startActivity(intent); 517 finish(); 518 } 519 520 @Override 521 public boolean onContextItemSelected(MenuItem item) { 522 ContextMenuAdapter menuAdapter = mListFragment.getContextMenuAdapter(); 523 if (menuAdapter != null) { 524 return menuAdapter.onContextItemSelected(item); 525 } 526 527 return super.onContextItemSelected(item); 528 } 529 530 @Override 531 public boolean onQueryTextChange(String newText) { 532 mListFragment.setQueryString(newText, true); 533 return false; 534 } 535 536 @Override 537 public boolean onQueryTextSubmit(String query) { 538 return false; 539 } 540 541 @Override 542 public boolean onClose() { 543 if (!TextUtils.isEmpty(mSearchView.getQuery())) { 544 mSearchView.setQuery(null, true); 545 } 546 return true; 547 } 548 549 @Override 550 public void onFocusChange(View view, boolean hasFocus) { 551 switch (view.getId()) { 552 case R.id.search_view: { 553 if (hasFocus) { 554 showInputMethod(mSearchView.findFocus()); 555 } 556 } 557 } 558 } 559 560 public void returnPickerResult(Uri data) { 561 Intent intent = new Intent(); 562 intent.setData(data); 563 returnPickerResult(intent); 564 } 565 566 public void returnPickerResult(Intent intent) { 567 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 568 setResult(RESULT_OK, intent); 569 finish(); 570 } 571 572 @Override 573 public void onClick(View view) { 574 switch (view.getId()) { 575 case R.id.new_contact: { 576 startCreateNewContactActivity(); 577 break; 578 } 579 } 580 } 581 582 private void startCreateNewContactActivity() { 583 Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 584 startActivityAndForwardResult(intent); 585 } 586 587 private void showInputMethod(View view) { 588 final InputMethodManager imm = (InputMethodManager) 589 getSystemService(Context.INPUT_METHOD_SERVICE); 590 if (imm != null) { 591 if (!imm.showSoftInput(view, 0)) { 592 Log.w(TAG, "Failed to show soft input method."); 593 } 594 } 595 } 596 597 @Override 598 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 599 super.onActivityResult(requestCode, resultCode, data); 600 if (requestCode == SUBACTIVITY_ADD_TO_EXISTING_CONTACT) { 601 if (resultCode == Activity.RESULT_OK) { 602 if (data != null) { 603 startActivity(data); 604 } 605 finish(); 606 } 607 } 608 } 609} 610