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