DialtactsActivity.java revision e00c9fe163d19ee380b922e3fcbe736216d78ccc
1/* 2 * Copyright (C) 2013 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.dialer; 18 19import android.animation.Animator; 20import android.animation.Animator.AnimatorListener; 21import android.animation.AnimatorListenerAdapter; 22import android.app.Activity; 23import android.app.Fragment; 24import android.app.FragmentManager; 25import android.app.FragmentTransaction; 26import android.app.SearchManager; 27import android.content.ActivityNotFoundException; 28import android.content.Context; 29import android.content.Intent; 30import android.content.pm.PackageManager; 31import android.content.pm.ResolveInfo; 32import android.graphics.Outline; 33import android.net.Uri; 34import android.os.Bundle; 35import android.os.RemoteException; 36import android.os.ServiceManager; 37import android.provider.ContactsContract.Contacts; 38import android.provider.ContactsContract.Intents; 39import android.speech.RecognizerIntent; 40import android.telephony.TelephonyManager; 41import android.text.TextUtils; 42import android.util.Log; 43import android.view.Menu; 44import android.view.MenuInflater; 45import android.view.MenuItem; 46import android.view.View; 47import android.view.View.OnLayoutChangeListener; 48import android.view.ViewGroup.LayoutParams; 49import android.view.ViewTreeObserver.OnGlobalLayoutListener; 50import android.view.animation.AccelerateInterpolator; 51import android.view.animation.DecelerateInterpolator; 52import android.view.animation.Interpolator; 53import android.view.inputmethod.InputMethodManager; 54import android.widget.AbsListView.OnScrollListener; 55import android.widget.LinearLayout; 56import android.widget.PopupMenu; 57import android.widget.SearchView; 58import android.widget.SearchView.OnQueryTextListener; 59import android.widget.Toast; 60 61import com.android.contacts.common.CallUtil; 62import com.android.contacts.common.activity.TransactionSafeActivity; 63import com.android.contacts.common.dialog.ClearFrequentsDialog; 64import com.android.contacts.common.interactions.ImportExportDialogFragment; 65import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 66import com.android.dialer.calllog.CallLogActivity; 67import com.android.dialer.database.DialerDatabaseHelper; 68import com.android.dialer.dialpad.DialpadFragment; 69import com.android.dialer.dialpad.SmartDialNameMatcher; 70import com.android.dialer.dialpad.SmartDialPrefix; 71import com.android.dialer.interactions.PhoneNumberInteraction; 72import com.android.dialer.list.AllContactsActivity; 73import com.android.dialer.list.DragDropController; 74import com.android.dialer.list.ListsFragment; 75import com.android.dialer.list.OnDragDropListener; 76import com.android.dialer.list.OnListFragmentScrolledListener; 77import com.android.dialer.list.PhoneFavoriteFragment; 78import com.android.dialer.list.PhoneFavoriteTileView; 79import com.android.dialer.list.RegularSearchFragment; 80import com.android.dialer.list.RemoveView; 81import com.android.dialer.list.SearchFragment; 82import com.android.dialer.list.SmartDialSearchFragment; 83import com.android.dialerbind.DatabaseHelperManager; 84import com.android.internal.telephony.ITelephony; 85 86import java.util.ArrayList; 87import java.util.List; 88 89/** 90 * The dialer tab's title is 'phone', a more common name (see strings.xml). 91 */ 92public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, 93 DialpadFragment.OnDialpadQueryChangedListener, 94 OnListFragmentScrolledListener, 95 DialpadFragment.HostInterface, 96 PhoneFavoriteFragment.OnShowAllContactsListener, 97 PhoneFavoriteFragment.HostInterface, 98 OnDragDropListener, View.OnLongClickListener, 99 OnPhoneNumberPickerActionListener { 100 private static final String TAG = "DialtactsActivity"; 101 102 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 103 104 public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; 105 106 /** Used to open Call Setting */ 107 private static final String PHONE_PACKAGE = "com.android.phone"; 108 private static final String CALL_SETTINGS_CLASS_NAME = 109 "com.android.phone.CallFeaturesSetting"; 110 /** @see #getCallOrigin() */ 111 private static final String CALL_ORIGIN_DIALTACTS = 112 "com.android.dialer.DialtactsActivity"; 113 114 private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; 115 private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; 116 private static final String KEY_SEARCH_QUERY = "search_query"; 117 private static final String KEY_FIRST_LAUNCH = "first_launch"; 118 119 private static final String TAG_DIALPAD_FRAGMENT = "dialpad"; 120 private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; 121 private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; 122 private static final String TAG_FAVORITES_FRAGMENT = "favorites"; 123 124 /** 125 * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. 126 */ 127 private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; 128 129 private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; 130 131 private static final int ANIMATION_DURATION = 250; 132 133 /** 134 * Fragment containing the dialpad that slides into view 135 */ 136 private DialpadFragment mDialpadFragment; 137 138 /** 139 * Fragment for searching phone numbers using the alphanumeric keyboard. 140 */ 141 private RegularSearchFragment mRegularSearchFragment; 142 143 /** 144 * Fragment for searching phone numbers using the dialpad. 145 */ 146 private SmartDialSearchFragment mSmartDialSearchFragment; 147 148 /** 149 * Fragment containing the speed dial list, recents list, and all contacts list. 150 */ 151 private ListsFragment mListsFragment; 152 153 private View mFakeActionBar; 154 private View mMenuButton; 155 private View mCallHistoryButton; 156 private View mDialpadButton; 157 private View mDialButton; 158 private PopupMenu mDialpadOverflowMenu; 159 160 private View mFragmentsFrame; 161 162 private boolean mInDialpadSearch; 163 private boolean mInRegularSearch; 164 private boolean mClearSearchOnPause; 165 166 /** 167 * True if the dialpad is only temporarily showing due to being in call 168 */ 169 private boolean mInCallDialpadUp; 170 171 /** 172 * True when this activity has been launched for the first time. 173 */ 174 private boolean mFirstLaunch; 175 176 // This view points to the Framelayout that houses both the search view and remove view 177 // containers. 178 private View mSearchAndRemoveViewContainer; 179 private SearchView mSearchView; 180 /** 181 * View that contains the "Remove" dialog that shows up when the user long presses a contact. 182 * If the user releases a contact when hovering on top of this, the contact is unfavorited and 183 * removed from the speed dial list. 184 */ 185 private RemoveView mRemoveViewContainer; 186 187 final Interpolator hideActionBarInterpolator = new AccelerateInterpolator(1.5f); 188 final Interpolator showActionBarInterpolator = new DecelerateInterpolator(1.5f); 189 private String mSearchQuery; 190 191 private DialerDatabaseHelper mDialerDatabaseHelper; 192 193 private class OverflowPopupMenu extends PopupMenu { 194 public OverflowPopupMenu(Context context, View anchor) { 195 super(context, anchor); 196 } 197 198 @Override 199 public void show() { 200 final Menu menu = getMenu(); 201 final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); 202 // TODO: Check mPhoneFavoriteFragment.hasFrequents() 203 clearFrequents.setVisible(true); 204 super.show(); 205 } 206 } 207 208 /** 209 * Listener used when one of phone numbers in search UI is selected. This will initiate a 210 * phone call using the phone number. 211 */ 212 private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener = 213 new OnPhoneNumberPickerActionListener() { 214 @Override 215 public void onPickPhoneNumberAction(Uri dataUri) { 216 // Specify call-origin so that users will see the previous tab instead of 217 // CallLog screen (search UI will be automatically exited). 218 PhoneNumberInteraction.startInteractionForPhoneCall( 219 DialtactsActivity.this, dataUri, getCallOrigin()); 220 mClearSearchOnPause = true; 221 } 222 223 @Override 224 public void onCallNumberDirectly(String phoneNumber) { 225 Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin()); 226 startActivity(intent); 227 mClearSearchOnPause = true; 228 } 229 230 @Override 231 public void onShortcutIntentCreated(Intent intent) { 232 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 233 } 234 235 @Override 236 public void onHomeInActionBarSelected() { 237 exitSearchUi(); 238 } 239 }; 240 241 /** 242 * Listener used to send search queries to the phone search fragment. 243 */ 244 private final OnQueryTextListener mPhoneSearchQueryTextListener = new OnQueryTextListener() { 245 @Override 246 public boolean onQueryTextSubmit(String query) { 247 return false; 248 } 249 250 @Override 251 public boolean onQueryTextChange(String newText) { 252 if (newText.equals(mSearchQuery)) { 253 // If the query hasn't changed (perhaps due to activity being destroyed 254 // and restored, or user launching the same DIAL intent twice), then there is 255 // no need to do anything here. 256 return true; 257 } 258 mSearchQuery = newText; 259 if (DEBUG) { 260 Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText); 261 } 262 final boolean dialpadSearch = isDialpadShowing(); 263 264 // Show search result with non-empty text. Show a bare list otherwise. 265 if (TextUtils.isEmpty(newText) && getInSearchUi()) { 266 exitSearchUi(); 267 return true; 268 } else if (!TextUtils.isEmpty(newText)) { 269 final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) || 270 (!dialpadSearch && mInRegularSearch); 271 if (!sameSearchMode) { 272 // call enterSearchUi only if we are switching search modes, or entering 273 // search ui for the first time 274 enterSearchUi(dialpadSearch, newText); 275 } 276 277 if (dialpadSearch && mSmartDialSearchFragment != null) { 278 mSmartDialSearchFragment.setQueryString(newText, false); 279 } else if (mRegularSearchFragment != null) { 280 mRegularSearchFragment.setQueryString(newText, false); 281 } 282 return true; 283 } 284 return true; 285 } 286 }; 287 288 private boolean isDialpadShowing() { 289 return mDialpadFragment != null && mDialpadFragment.isVisible(); 290 } 291 292 @Override 293 protected void onCreate(Bundle savedInstanceState) { 294 super.onCreate(savedInstanceState); 295 mFirstLaunch = true; 296 297 setContentView(R.layout.dialtacts_activity); 298 getWindow().setBackgroundDrawable(null); 299 300 getActionBar().setDisplayShowHomeEnabled(false); 301 getActionBar().setDisplayShowTitleEnabled(false); 302 303 // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState 304 // is null. Otherwise the fragment manager takes care of recreating these fragments. 305 if (savedInstanceState == null) { 306 getFragmentManager().beginTransaction() 307 .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT) 308 .add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT) 309 .commit(); 310 } else { 311 mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); 312 mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); 313 mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); 314 mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); 315 } 316 317 mFragmentsFrame = findViewById(R.id.dialtacts_frame); 318 319 mFakeActionBar = findViewById(R.id.fake_action_bar); 320 321 mCallHistoryButton = findViewById(R.id.call_history_button); 322 mCallHistoryButton.setOnClickListener(this); 323 mDialButton = findViewById(R.id.dial_button); 324 mDialButton.setOnClickListener(this); 325 mDialpadButton = findViewById(R.id.dialpad_button); 326 mDialpadButton.setOnClickListener(this); 327 mMenuButton = findViewById(R.id.overflow_menu_button); 328 mMenuButton.setOnClickListener(this); 329 330 mRemoveViewContainer = (RemoveView) findViewById(R.id.remove_view_container); 331 mSearchAndRemoveViewContainer = findViewById(R.id.search_and_remove_view_container); 332 333 mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); 334 SmartDialPrefix.initializeNanpSettings(this); 335 } 336 337 @Override 338 protected void onResume() { 339 super.onResume(); 340 if (mFirstLaunch) { 341 displayFragment(getIntent()); 342 } else if (!phoneIsInUse() && mInCallDialpadUp) { 343 hideDialpadFragment(false, true); 344 mInCallDialpadUp = false; 345 } 346 mFirstLaunch = false; 347 mDialerDatabaseHelper.startSmartDialUpdateThread(); 348 } 349 350 @Override 351 protected void onPause() { 352 if (mClearSearchOnPause) { 353 hideDialpadAndSearchUi(); 354 mClearSearchOnPause = false; 355 } 356 super.onPause(); 357 } 358 359 @Override 360 protected void onSaveInstanceState(Bundle outState) { 361 super.onSaveInstanceState(outState); 362 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 363 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 364 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 365 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 366 } 367 368 @Override 369 public void onAttachFragment(Fragment fragment) { 370 if (fragment instanceof DialpadFragment) { 371 mDialpadFragment = (DialpadFragment) fragment; 372 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 373 transaction.hide(mDialpadFragment); 374 transaction.commit(); 375 } else if (fragment instanceof SmartDialSearchFragment) { 376 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 377 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener( 378 mPhoneNumberPickerActionListener); 379 if (mFragmentsFrame != null) { 380 mFragmentsFrame.setAlpha(1.0f); 381 } 382 } else if (fragment instanceof SearchFragment) { 383 mRegularSearchFragment = (RegularSearchFragment) fragment; 384 mRegularSearchFragment.setOnPhoneNumberPickerActionListener( 385 mPhoneNumberPickerActionListener); 386 if (mFragmentsFrame != null) { 387 mFragmentsFrame.setAlpha(1.0f); 388 } 389 } else if (fragment instanceof ListsFragment) { 390 mListsFragment = (ListsFragment) fragment; 391 } 392 } 393 394 protected void handleMenuSettings() { 395 openTelephonySetting(this); 396 } 397 398 public static void openTelephonySetting(Activity activity) { 399 final Intent settingsIntent = getCallSettingsIntent(); 400 activity.startActivity(settingsIntent); 401 } 402 403 @Override 404 public void onClick(View view) { 405 switch (view.getId()) { 406 case R.id.overflow_menu_button: 407 mDialpadOverflowMenu.show(); 408 break; 409 case R.id.dialpad_button: 410 // Reset the boolean flag that tracks whether the dialpad was up because 411 // we were in call. Regardless of whether it was true before, we want to 412 // show the dialpad because the user has explicitly clicked the dialpad 413 // button. 414 mInCallDialpadUp = false; 415 showDialpadFragment(true); 416 break; 417 case R.id.call_history_button: 418 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 419 // CONTENT_TYPE, so that we always open our call log from our dialer 420 final Intent intent = new Intent(this, CallLogActivity.class); 421 startActivity(intent); 422 break; 423 case R.id.dial_button: 424 // Dial button was pressed; tell the Dialpad fragment 425 mDialpadFragment.dialButtonPressed(); 426 break; 427 case R.id.search_close_button: 428 // Clear the search field 429 if (!TextUtils.isEmpty(mSearchView.getQuery())) { 430 mDialpadFragment.clearDialpad(); 431 mSearchView.setQuery("", false); 432 } 433 break; 434 case R.id.voice_search_button: 435 try { 436 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 437 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 438 } catch (ActivityNotFoundException e) { 439 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 440 Toast.LENGTH_SHORT).show(); 441 } 442 break; 443 default: { 444 Log.wtf(TAG, "Unexpected onClick event from " + view); 445 break; 446 } 447 } 448 } 449 450 @Override 451 public boolean onOptionsItemSelected(MenuItem item) { 452 switch (item.getItemId()) { 453 case R.id.menu_history: 454 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 455 // CONTENT_TYPE, so that we always open our call log from our dialer 456 final Intent intent = new Intent(this, CallLogActivity.class); 457 startActivity(intent); 458 break; 459 case R.id.menu_add_contact: 460 try { 461 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 462 } catch (ActivityNotFoundException e) { 463 Toast toast = Toast.makeText(this, 464 R.string.add_contact_not_available, 465 Toast.LENGTH_SHORT); 466 toast.show(); 467 } 468 break; 469 case R.id.menu_import_export: 470 // We hard-code the "contactsAreAvailable" argument because doing it properly would 471 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 472 // now in Dialtacts for (potential) performance reasons. Compare with how it is 473 // done in {@link PeopleActivity}. 474 ImportExportDialogFragment.show(getFragmentManager(), true, 475 DialtactsActivity.class); 476 return true; 477 case R.id.menu_clear_frequents: 478 // TODO: This should be enabled/disabled based on 479 // PhoneFavoritesFragments.hasFrequents 480 ClearFrequentsDialog.show(getFragmentManager()); 481 return true; 482 case R.id.menu_call_settings: 483 handleMenuSettings(); 484 return true; 485 case R.id.menu_all_contacts: 486 onShowAllContacts(); 487 return true; 488 } 489 return false; 490 } 491 492 @Override 493 public boolean onLongClick(View view) { 494 switch (view.getId()) { 495 case R.id.dial_button: { 496 // Dial button was pressed; tell the Dialpad fragment 497 mDialpadFragment.dialButtonPressed(); 498 return true; // Consume the event 499 } 500 default: { 501 Log.wtf(TAG, "Unexpected onClick event from " + view); 502 break; 503 } 504 } 505 return false; 506 } 507 508 @Override 509 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 510 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 511 if (resultCode == RESULT_OK) { 512 final ArrayList<String> matches = data.getStringArrayListExtra( 513 RecognizerIntent.EXTRA_RESULTS); 514 if (matches.size() > 0) { 515 final String match = matches.get(0); 516 mSearchView.setQuery(match, false); 517 } else { 518 Log.e(TAG, "Voice search - nothing heard"); 519 } 520 } else { 521 Log.e(TAG, "Voice search failed"); 522 } 523 } 524 super.onActivityResult(requestCode, resultCode, data); 525 } 526 527 private void showDialpadFragment(boolean animate) { 528 mDialpadFragment.setAdjustTranslationForAnimation(animate); 529 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 530 if (animate) { 531 ft.setCustomAnimations(R.anim.slide_in, 0); 532 } else { 533 mDialpadFragment.setYFraction(0); 534 } 535 ft.show(mDialpadFragment); 536 ft.commit(); 537 } 538 539 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 540 if (mDialpadFragment == null) return; 541 if (clearDialpad) { 542 mDialpadFragment.clearDialpad(); 543 } 544 if (!mDialpadFragment.isVisible()) return; 545 mDialpadFragment.setAdjustTranslationForAnimation(animate); 546 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 547 if (animate) { 548 ft.setCustomAnimations(0, R.anim.slide_out); 549 } 550 ft.hide(mDialpadFragment); 551 ft.commit(); 552 } 553 554 final AnimatorListener mHideListener = new AnimatorListenerAdapter() { 555 @Override 556 public void onAnimationEnd(Animator animation) { 557 mSearchAndRemoveViewContainer.setVisibility(View.GONE); 558 } 559 }; 560 561 private boolean getInSearchUi() { 562 return mInDialpadSearch || mInRegularSearch; 563 } 564 565 private void setNotInSearchUi() { 566 mInDialpadSearch = false; 567 mInRegularSearch = false; 568 } 569 570 private void hideDialpadAndSearchUi() { 571 mSearchView.setQuery("", false); 572 hideDialpadFragment(false, true); 573 } 574 575 /** 576 * Callback from child DialpadFragment when the dialpad is shown. 577 */ 578 public void onDialpadShown() { 579 mDialButton.setVisibility(View.VISIBLE); 580 mDialpadButton.setVisibility(View.GONE); 581 mMenuButton.setVisibility(View.VISIBLE); 582 if (mDialpadOverflowMenu == null) { 583 mDialpadOverflowMenu = mDialpadFragment.buildOptionsMenu(mMenuButton); 584 mMenuButton.setOnTouchListener(mDialpadOverflowMenu.getDragToOpenListener()); 585 } 586 587 SearchFragment fragment = null; 588 if (mInDialpadSearch) { 589 fragment = mSmartDialSearchFragment; 590 } else if (mInRegularSearch) { 591 fragment = mRegularSearchFragment; 592 } 593 if (fragment != null && fragment.isVisible()) { 594 fragment.getListView().animate().translationY(-getActionBar().getHeight()) 595 .setInterpolator(hideActionBarInterpolator).setDuration(ANIMATION_DURATION); 596 } 597 598 if (mListsFragment != null && mListsFragment.isVisible()) { 599 // If the favorites fragment is showing, fade to blank. 600 mFragmentsFrame.animate().alpha(0.0f); 601 } 602 getActionBar().hide(); 603 } 604 605 /** 606 * Callback from child DialpadFragment when the dialpad is hidden. 607 */ 608 public void onDialpadHidden() { 609 mDialButton.setVisibility(View.GONE); 610 mDialpadButton.setVisibility(View.VISIBLE); 611 mMenuButton.setVisibility(View.GONE); 612 613 SearchFragment fragment = null; 614 if (mInDialpadSearch) { 615 fragment = mSmartDialSearchFragment; 616 } else if (mInRegularSearch) { 617 fragment = mRegularSearchFragment; 618 } 619 if (fragment != null && fragment.isVisible()) { 620 fragment.getListView().animate().translationY(0) 621 .setInterpolator(showActionBarInterpolator).setDuration(ANIMATION_DURATION); 622 } 623 624 if (mListsFragment != null && mListsFragment.isVisible()) { 625 mFragmentsFrame.animate().alpha(1.0f); 626 } 627 getActionBar().show(); 628 } 629 630 private void hideInputMethod(View view) { 631 final InputMethodManager imm = (InputMethodManager) getSystemService( 632 Context.INPUT_METHOD_SERVICE); 633 if (imm != null && view != null) { 634 imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 635 } 636 } 637 638 @Override 639 public boolean onCreateOptionsMenu(Menu menu) { 640 if (DEBUG) { 641 Log.d(TAG, "onCreateOptionsMenu"); 642 } 643 MenuInflater inflater = getMenuInflater(); 644 inflater.inflate(R.menu.dialtacts_options, menu); 645 final MenuItem searchItem = menu.findItem(R.id.menu_search); 646 mSearchView = (SearchView) searchItem.getActionView(); 647 SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); 648 mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); 649 mSearchView.setOnQueryTextListener(mPhoneSearchQueryTextListener); 650 mSearchView.setIconifiedByDefault(false); 651 return super.onCreateOptionsMenu(menu); 652 } 653 654 /** 655 * Returns true if the intent is due to hitting the green send key (hardware call button: 656 * KEYCODE_CALL) while in a call. 657 * 658 * @param intent the intent that launched this activity 659 * @return true if the intent is due to hitting the green send key while in a call 660 */ 661 private boolean isSendKeyWhileInCall(Intent intent) { 662 // If there is a call in progress and the user launched the dialer by hitting the call 663 // button, go straight to the in-call screen. 664 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); 665 666 try { 667 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 668 if (callKey && phone != null && phone.showCallScreen()) { 669 return true; 670 } 671 } catch (RemoteException e) { 672 Log.e(TAG, "Failed to handle send while in call", e); 673 } 674 675 return false; 676 } 677 678 /** 679 * Sets the current tab based on the intent's request type 680 * 681 * @param intent Intent that contains information about which tab should be selected 682 */ 683 private void displayFragment(Intent intent) { 684 // If we got here by hitting send and we're in call forward along to the in-call activity 685 if (isSendKeyWhileInCall(intent)) { 686 finish(); 687 return; 688 } 689 690 if (mDialpadFragment != null) { 691 final boolean phoneIsInUse = phoneIsInUse(); 692 if (phoneIsInUse || isDialIntent(intent)) { 693 mDialpadFragment.setStartedFromNewIntent(true); 694 if (phoneIsInUse && !mDialpadFragment.isVisible()) { 695 mInCallDialpadUp = true; 696 } 697 showDialpadFragment(false); 698 } 699 } 700 } 701 702 @Override 703 public void onNewIntent(Intent newIntent) { 704 setIntent(newIntent); 705 displayFragment(newIntent); 706 707 invalidateOptionsMenu(); 708 } 709 710 /** Returns true if the given intent contains a phone number to populate the dialer with */ 711 private boolean isDialIntent(Intent intent) { 712 final String action = intent.getAction(); 713 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 714 return true; 715 } 716 if (Intent.ACTION_VIEW.equals(action)) { 717 final Uri data = intent.getData(); 718 if (data != null && CallUtil.SCHEME_TEL.equals(data.getScheme())) { 719 return true; 720 } 721 } 722 return false; 723 } 724 725 /** 726 * Returns an appropriate call origin for this Activity. May return null when no call origin 727 * should be used (e.g. when some 3rd party application launched the screen. Call origin is 728 * for remembering the tab in which the user made a phone call, so the external app's DIAL 729 * request should not be counted.) 730 */ 731 public String getCallOrigin() { 732 return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null; 733 } 734 735 /** 736 * Shows the search fragment 737 */ 738 private void enterSearchUi(boolean smartDialSearch, String query) { 739 if (getFragmentManager().isDestroyed()) { 740 // Weird race condition where fragment is doing work after the activity is destroyed 741 // due to talkback being on (b/10209937). Just return since we can't do any 742 // constructive here. 743 return; 744 } 745 746 if (DEBUG) { 747 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 748 } 749 750 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 751 752 SearchFragment fragment; 753 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 754 transaction.remove(mSmartDialSearchFragment); 755 } else if (mInRegularSearch && mRegularSearchFragment != null) { 756 transaction.remove(mRegularSearchFragment); 757 } 758 759 final String tag; 760 if (smartDialSearch) { 761 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 762 } else { 763 tag = TAG_REGULAR_SEARCH_FRAGMENT; 764 } 765 mInDialpadSearch = smartDialSearch; 766 mInRegularSearch = !smartDialSearch; 767 768 fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 769 if (fragment == null) { 770 if (smartDialSearch) { 771 fragment = new SmartDialSearchFragment(); 772 } else { 773 fragment = new RegularSearchFragment(); 774 } 775 } 776 // DialtactsActivity will provide the options menu 777 fragment.setHasOptionsMenu(false); 778 transaction.replace(R.id.dialtacts_frame, fragment, tag); 779 transaction.addToBackStack(null); 780 fragment.setQueryString(query, false); 781 transaction.commit(); 782 } 783 784 /** 785 * Hides the search fragment 786 */ 787 private void exitSearchUi() { 788 // See related bug in enterSearchUI(); 789 if (getFragmentManager().isDestroyed()) { 790 return; 791 } 792 // Go all the way back to the favorites fragment, regardless of how many times we 793 // transitioned between search fragments 794 getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE); 795 setNotInSearchUi(); 796 797 if (isDialpadShowing()) { 798 mFragmentsFrame.setAlpha(0); 799 } 800 } 801 802 /** Returns an Intent to launch Call Settings screen */ 803 public static Intent getCallSettingsIntent() { 804 final Intent intent = new Intent(Intent.ACTION_MAIN); 805 intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME); 806 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 807 return intent; 808 } 809 810 @Override 811 public void onBackPressed() { 812 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 813 hideDialpadFragment(true, false); 814 } else if (getInSearchUi()) { 815 mSearchView.setQuery(null, false); 816 mDialpadFragment.clearDialpad(); 817 } else { 818 super.onBackPressed(); 819 } 820 } 821 822 @Override 823 public void onDialpadQueryChanged(String query) { 824 if (mSmartDialSearchFragment != null) { 825 mSmartDialSearchFragment.setAddToContactNumber(query); 826 } 827 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 828 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 829 if (!TextUtils.equals(mSearchView.getQuery(), normalizedQuery)) { 830 if (DEBUG) { 831 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 832 } 833 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 834 // This callback can happen if the dialpad fragment is recreated because of 835 // activity destruction. In that case, don't update the search view because 836 // that would bring the user back to the search fragment regardless of the 837 // previous state of the application. Instead, just return here and let the 838 // fragment manager correctly figure out whatever fragment was last displayed. 839 return; 840 } 841 if (mDialpadFragment != null) { 842 mDialpadOverflowMenu = mDialpadFragment.buildOptionsMenu(mMenuButton); 843 } 844 mSearchView.setQuery(normalizedQuery, false); 845 } 846 } 847 848 @Override 849 public void onListFragmentScrollStateChange(int scrollState) { 850 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 851 hideDialpadFragment(true, false); 852 hideInputMethod(getCurrentFocus()); 853 } 854 } 855 856 @Override 857 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 858 int totalItemCount) { 859 // TODO: No-op for now. This should eventually show/hide the actionBar based on 860 // interactions with the ListsFragments. 861 } 862 863 @Override 864 public void setDialButtonEnabled(boolean enabled) { 865 if (mDialButton != null) { 866 mDialButton.setEnabled(enabled); 867 } 868 } 869 870 @Override 871 public void setDialButtonContainerVisible(boolean visible) { 872 mFakeActionBar.setVisibility(visible ? View.VISIBLE : View.GONE); 873 } 874 875 private boolean phoneIsInUse() { 876 final TelephonyManager tm = (TelephonyManager) getSystemService( 877 Context.TELEPHONY_SERVICE); 878 return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE; 879 } 880 881 @Override 882 public void onShowAllContacts() { 883 final Intent intent = new Intent(this, AllContactsActivity.class); 884 startActivity(intent); 885 } 886 887 public static Intent getAddNumberToContactIntent(CharSequence text) { 888 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 889 intent.putExtra(Intents.Insert.PHONE, text); 890 intent.setType(Contacts.CONTENT_ITEM_TYPE); 891 return intent; 892 } 893 894 private boolean canIntentBeHandled(Intent intent) { 895 final PackageManager packageManager = getPackageManager(); 896 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 897 PackageManager.MATCH_DEFAULT_ONLY); 898 return resolveInfo != null && resolveInfo.size() > 0; 899 } 900 901 /** 902 * Called when the user has long-pressed a contact tile to start a drag operation. 903 */ 904 @Override 905 public void onDragStarted(int itemIndex, int x, int y, PhoneFavoriteTileView view) { 906 getActionBar().hide(); 907 mSearchAndRemoveViewContainer.setVisibility(View.VISIBLE); 908 } 909 910 @Override 911 public void onDragHovered(int itemIndex, int x, int y) {} 912 913 /** 914 * Called when the user has released a contact tile after long-pressing it. 915 */ 916 @Override 917 public void onDragFinished(int x, int y) { 918 getActionBar().show(); 919 mSearchAndRemoveViewContainer.setVisibility(View.GONE); 920 } 921 922 @Override 923 public void onDroppedOnRemove() {} 924 925 /** 926 * Allows the PhoneFavoriteFragment to attach the drag controller to mRemoveViewContainer 927 * once it has been attached to the activity. 928 */ 929 @Override 930 public void setDragDropController(DragDropController dragController) { 931 mRemoveViewContainer.setDragDropController(dragController); 932 } 933 934 @Override 935 public void onPickPhoneNumberAction(Uri dataUri) { 936 mPhoneNumberPickerActionListener.onPickPhoneNumberAction(dataUri); 937 } 938 939 @Override 940 public void onCallNumberDirectly(String phoneNumber) { 941 mPhoneNumberPickerActionListener.onCallNumberDirectly(phoneNumber); 942 } 943 944 @Override 945 public void onShortcutIntentCreated(Intent intent) { 946 mPhoneNumberPickerActionListener.onShortcutIntentCreated(intent); 947 } 948 949 @Override 950 public void onHomeInActionBarSelected() { 951 mPhoneNumberPickerActionListener.onHomeInActionBarSelected(); 952 } 953} 954