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