DialtactsActivity.java revision 90374a77ce339f30a04fbb5d3c5faa0c84901c71
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.LayoutTransition; 20import android.app.ActionBar; 21import android.app.Activity; 22import android.app.Fragment; 23import android.app.FragmentManager; 24import android.app.FragmentTransaction; 25import android.content.ActivityNotFoundException; 26import android.content.Context; 27import android.content.Intent; 28import android.content.pm.PackageManager; 29import android.content.pm.ResolveInfo; 30import android.content.res.TypedArray; 31import android.net.Uri; 32import android.os.Bundle; 33import android.os.RemoteException; 34import android.os.ServiceManager; 35import android.provider.ContactsContract.Contacts; 36import android.provider.ContactsContract.Intents; 37import android.speech.RecognizerIntent; 38import android.support.v4.view.ViewPager; 39import android.telephony.TelephonyManager; 40import android.text.Editable; 41import android.text.TextUtils; 42import android.text.TextWatcher; 43import android.util.Log; 44import android.view.DragEvent; 45import android.view.KeyEvent; 46import android.view.Menu; 47import android.view.MenuItem; 48import android.view.MotionEvent; 49import android.view.View; 50import android.view.View.OnDragListener; 51import android.view.animation.AccelerateInterpolator; 52import android.view.animation.Animation; 53import android.view.animation.Animation.AnimationListener; 54import android.view.animation.AnimationUtils; 55import android.view.animation.DecelerateInterpolator; 56import android.view.animation.Interpolator; 57import android.view.inputmethod.InputMethodManager; 58import android.widget.AbsListView.OnScrollListener; 59import android.widget.EditText; 60import android.widget.ImageButton; 61import android.widget.PopupMenu; 62import android.widget.RelativeLayout; 63import android.widget.Toast; 64 65import com.android.contacts.common.CallUtil; 66import com.android.contacts.common.activity.TransactionSafeActivity; 67import com.android.contacts.common.dialog.ClearFrequentsDialog; 68import com.android.contacts.common.interactions.ImportExportDialogFragment; 69import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 70import com.android.contacts.common.util.ViewUtil; 71import com.android.dialer.calllog.CallLogActivity; 72import com.android.dialer.database.DialerDatabaseHelper; 73import com.android.dialer.dialpad.DialpadFragment; 74import com.android.dialer.dialpad.SmartDialNameMatcher; 75import com.android.dialer.dialpad.SmartDialPrefix; 76import com.android.dialer.interactions.PhoneNumberInteraction; 77import com.android.dialer.list.DragDropController; 78import com.android.dialer.list.ListsFragment; 79import com.android.dialer.list.OnDragDropListener; 80import com.android.dialer.list.OnListFragmentScrolledListener; 81import com.android.dialer.list.SpeedDialFragment; 82import com.android.dialer.list.PhoneFavoriteSquareTileView; 83import com.android.dialer.list.RegularSearchFragment; 84import com.android.dialer.list.RemoveView; 85import com.android.dialer.list.SearchFragment; 86import com.android.dialer.list.SmartDialSearchFragment; 87import com.android.dialer.widget.SearchEditTextLayout; 88import com.android.dialerbind.DatabaseHelperManager; 89import com.android.internal.telephony.ITelephony; 90 91import java.util.ArrayList; 92import java.util.List; 93 94/** 95 * The dialer tab's title is 'phone', a more common name (see strings.xml). 96 */ 97public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, 98 DialpadFragment.OnDialpadQueryChangedListener, 99 OnListFragmentScrolledListener, 100 DialpadFragment.HostInterface, 101 ListsFragment.HostInterface, 102 SpeedDialFragment.HostInterface, 103 OnDragDropListener, 104 OnPhoneNumberPickerActionListener, 105 PopupMenu.OnMenuItemClickListener, 106 ViewPager.OnPageChangeListener { 107 private static final String TAG = "DialtactsActivity"; 108 109 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 110 111 public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; 112 113 /** Used to open Call Setting */ 114 private static final String PHONE_PACKAGE = "com.android.phone"; 115 private static final String CALL_SETTINGS_CLASS_NAME = 116 "com.android.phone.CallFeaturesSetting"; 117 /** @see #getCallOrigin() */ 118 private static final String CALL_ORIGIN_DIALTACTS = 119 "com.android.dialer.DialtactsActivity"; 120 121 private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; 122 private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; 123 private static final String KEY_SEARCH_QUERY = "search_query"; 124 private static final String KEY_FIRST_LAUNCH = "first_launch"; 125 126 private static final String TAG_DIALPAD_FRAGMENT = "dialpad"; 127 private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; 128 private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; 129 private static final String TAG_FAVORITES_FRAGMENT = "favorites"; 130 131 /** 132 * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. 133 */ 134 private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; 135 136 private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; 137 138 private static final int ANIMATION_DURATION = 250; 139 140 private RelativeLayout parentLayout; 141 142 /** 143 * Fragment containing the dialpad that slides into view 144 */ 145 private DialpadFragment mDialpadFragment; 146 147 /** 148 * Fragment for searching phone numbers using the alphanumeric keyboard. 149 */ 150 private RegularSearchFragment mRegularSearchFragment; 151 152 /** 153 * Fragment for searching phone numbers using the dialpad. 154 */ 155 private SmartDialSearchFragment mSmartDialSearchFragment; 156 157 /** 158 * Fragment containing the speed dial list, recents list, and all contacts list. 159 */ 160 private ListsFragment mListsFragment; 161 162 private View mFloatingActionButtonContainer; 163 private ImageButton mFloatingActionButton; 164 165 private int mActionBarHeight; 166 private boolean mInDialpadSearch; 167 private boolean mInRegularSearch; 168 private boolean mClearSearchOnPause; 169 private boolean mIsDialpadShown; 170 171 /** 172 * The position of the currently selected tab in the attached {@link ListsFragment}. 173 */ 174 private int mCurrentTabPosition = 0; 175 176 /** 177 * True if the dialpad is only temporarily showing due to being in call 178 */ 179 private boolean mInCallDialpadUp; 180 181 /** 182 * True when this activity has been launched for the first time. 183 */ 184 private boolean mFirstLaunch; 185 186 /** 187 * Search query to be applied to the SearchView in the ActionBar once 188 * onCreateOptionsMenu has been called. 189 */ 190 private String mPendingSearchViewQuery; 191 192 private EditText mSearchView; 193 private View mSearchViewCloseButton; 194 private View mVoiceSearchButton; 195 196 /** 197 * View that contains the "Remove" dialog that shows up when the user long presses a contact. 198 * If the user releases a contact when hovering on top of this, the contact is unfavorited and 199 * removed from the speed dial list. 200 */ 201 private View mRemoveViewContainer; 202 203 final Interpolator hideActionBarInterpolator = new AccelerateInterpolator(1.5f); 204 final Interpolator showActionBarInterpolator = new DecelerateInterpolator(1.5f); 205 private String mSearchQuery; 206 207 private DialerDatabaseHelper mDialerDatabaseHelper; 208 private DragDropController mDragDropController; 209 210 private class OptionsPopupMenu extends PopupMenu { 211 public OptionsPopupMenu(Context context, View anchor) { 212 super(context, anchor); 213 } 214 215 @Override 216 public void show() { 217 final Menu menu = getMenu(); 218 final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); 219 clearFrequents.setVisible(mListsFragment != null && 220 mListsFragment.getSpeedDialFragment() != null && 221 mListsFragment.getSpeedDialFragment().hasFrequents()); 222 super.show(); 223 } 224 } 225 226 /** 227 * Listener that listens to drag events and sends their x and y coordinates to a 228 * {@link DragDropController}. 229 */ 230 private class LayoutOnDragListener implements OnDragListener { 231 @Override 232 public boolean onDrag(View v, DragEvent event) { 233 if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { 234 mDragDropController.handleDragHovered(v, (int) event.getX(), 235 (int) event.getY()); 236 } 237 return true; 238 } 239 } 240 241 /** 242 * Listener used to send search queries to the phone search fragment. 243 */ 244 private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { 245 @Override 246 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 247 } 248 249 @Override 250 public void onTextChanged(CharSequence s, int start, int before, int count) { 251 final String newText = s.toString(); 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; 257 } 258 mSearchQuery = newText; 259 if (DEBUG) { 260 Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText); 261 } 262 263 // Show search fragment only when the query string is changed to non-empty text. 264 if (!TextUtils.isEmpty(newText)) { 265 // Call enterSearchUi only if we are switching search modes, or showing a search 266 // fragment for the first time. 267 final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) || 268 (!mIsDialpadShown && mInRegularSearch); 269 if (!sameSearchMode) { 270 enterSearchUi(mIsDialpadShown, mSearchQuery); 271 } 272 } 273 274 if (mIsDialpadShown && mSmartDialSearchFragment != null) { 275 mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */); 276 } else if (mRegularSearchFragment != null) { 277 mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */); 278 } 279 280 if (TextUtils.isEmpty(newText)) { 281 mSearchViewCloseButton.setVisibility(View.GONE); 282 mVoiceSearchButton.setVisibility(View.VISIBLE); 283 } else { 284 mSearchViewCloseButton.setVisibility(View.VISIBLE); 285 mVoiceSearchButton.setVisibility(View.GONE); 286 } 287 } 288 289 @Override 290 public void afterTextChanged(Editable s) { 291 } 292 }; 293 294 295 /** 296 * Open the search UI when the user touches the search text view. 297 */ 298 private final View.OnTouchListener mSearchViewOnTouchListener = new View.OnTouchListener() { 299 @Override 300 public boolean onTouch(View v, MotionEvent event) { 301 if (!isInSearchUi()) { 302 enterSearchUi(false /* smartDialSearch */, mSearchView.getText().toString()); 303 } 304 return false; 305 } 306 }; 307 308 /** 309 * If the search term is empty and the user closes the soft keyboard, close the search UI. 310 */ 311 private final View.OnKeyListener mSearchEditTextLayoutListener = new View.OnKeyListener() { 312 @Override 313 public boolean onKey(View v, int keyCode, KeyEvent event) { 314 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN && 315 TextUtils.isEmpty(mSearchView.getText().toString())) { 316 onBackPressed(); 317 } 318 return false; 319 } 320 }; 321 322 @Override 323 protected void onCreate(Bundle savedInstanceState) { 324 super.onCreate(savedInstanceState); 325 mFirstLaunch = true; 326 327 setContentView(R.layout.dialtacts_activity); 328 getWindow().setBackgroundDrawable(null); 329 330 final ActionBar actionBar = getActionBar(); 331 actionBar.setCustomView(R.layout.search_edittext); 332 actionBar.setDisplayShowCustomEnabled(true); 333 334 SearchEditTextLayout actionBarView = (SearchEditTextLayout) actionBar.getCustomView(); 335 actionBarView.setPreImeKeyListener(mSearchEditTextLayoutListener); 336 337 mSearchView = (EditText) actionBarView.findViewById(R.id.search_view); 338 mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); 339 mSearchView.setOnTouchListener(mSearchViewOnTouchListener); 340 341 mSearchViewCloseButton = actionBarView.findViewById(R.id.search_close_button); 342 mSearchViewCloseButton.setOnClickListener(this); 343 344 ImageButton optionsMenuButton = 345 (ImageButton) actionBarView.findViewById(R.id.dialtacts_options_menu_button); 346 optionsMenuButton.setOnClickListener(this); 347 final OptionsPopupMenu optionsMenu = buildOptionsMenu(optionsMenuButton); 348 optionsMenuButton.setOnTouchListener(optionsMenu.getDragToOpenListener()); 349 350 final TypedArray styledAttributes = getTheme().obtainStyledAttributes( 351 new int[] { android.R.attr.actionBarSize }); 352 mActionBarHeight = (int) styledAttributes.getDimension(0, 0); 353 styledAttributes.recycle(); 354 355 // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState 356 // is null. Otherwise the fragment manager takes care of recreating these fragments. 357 if (savedInstanceState == null) { 358 getFragmentManager().beginTransaction() 359 .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT) 360 .add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT) 361 .commit(); 362 } else { 363 mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); 364 mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); 365 mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); 366 mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); 367 } 368 369 parentLayout = (RelativeLayout) findViewById(R.id.dialtacts_mainlayout); 370 parentLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); 371 parentLayout.setOnDragListener(new LayoutOnDragListener()); 372 373 mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container); 374 ViewUtil.setupFloatingActionButton(mFloatingActionButtonContainer, getResources()); 375 376 mFloatingActionButton = (ImageButton) findViewById(R.id.floating_action_button); 377 mFloatingActionButton.setOnClickListener(this); 378 379 mRemoveViewContainer = findViewById(R.id.remove_view_container); 380 381 mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); 382 SmartDialPrefix.initializeNanpSettings(this); 383 } 384 385 @Override 386 protected void onResume() { 387 super.onResume(); 388 if (mFirstLaunch) { 389 displayFragment(getIntent()); 390 } else if (!phoneIsInUse() && mInCallDialpadUp) { 391 hideDialpadFragment(false, true); 392 mInCallDialpadUp = false; 393 } 394 mFirstLaunch = false; 395 prepareVoiceSearchButton(); 396 mDialerDatabaseHelper.startSmartDialUpdateThread(); 397 } 398 399 @Override 400 protected void onPause() { 401 if (mClearSearchOnPause) { 402 hideDialpadAndSearchUi(); 403 mClearSearchOnPause = false; 404 } 405 super.onPause(); 406 } 407 408 @Override 409 protected void onSaveInstanceState(Bundle outState) { 410 super.onSaveInstanceState(outState); 411 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 412 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 413 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 414 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 415 } 416 417 @Override 418 public void onAttachFragment(Fragment fragment) { 419 if (fragment instanceof DialpadFragment) { 420 mDialpadFragment = (DialpadFragment) fragment; 421 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 422 transaction.hide(mDialpadFragment); 423 transaction.commit(); 424 } else if (fragment instanceof SmartDialSearchFragment) { 425 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 426 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); 427 } else if (fragment instanceof SearchFragment) { 428 mRegularSearchFragment = (RegularSearchFragment) fragment; 429 mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this); 430 } else if (fragment instanceof ListsFragment) { 431 mListsFragment = (ListsFragment) fragment; 432 mListsFragment.addOnPageChangeListener(this); 433 } 434 } 435 436 protected void handleMenuSettings() { 437 openTelephonySetting(this); 438 } 439 440 public static void openTelephonySetting(Activity activity) { 441 final Intent settingsIntent = getCallSettingsIntent(); 442 activity.startActivity(settingsIntent); 443 } 444 445 @Override 446 public void onClick(View view) { 447 switch (view.getId()) { 448 case R.id.floating_action_button: 449 if (!mIsDialpadShown) { 450 mInCallDialpadUp = false; 451 showDialpadFragment(true); 452 } else { 453 // Dial button was pressed; tell the Dialpad fragment 454 mDialpadFragment.dialButtonPressed(); 455 } 456 break; 457 case R.id.search_close_button: 458 // Clear the search field 459 if (!TextUtils.isEmpty(mSearchView.getText())) { 460 mDialpadFragment.clearDialpad(); 461 mSearchView.setText(null); 462 } 463 break; 464 case R.id.voice_search_button: 465 try { 466 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 467 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 468 } catch (ActivityNotFoundException e) { 469 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 470 Toast.LENGTH_SHORT).show(); 471 } 472 break; 473 case R.id.dialtacts_options_menu_button: 474 buildOptionsMenu(view).show(); 475 break; 476 default: { 477 Log.wtf(TAG, "Unexpected onClick event from " + view); 478 break; 479 } 480 } 481 } 482 483 @Override 484 public boolean onMenuItemClick(MenuItem item) { 485 switch (item.getItemId()) { 486 case R.id.menu_history: 487 showCallHistory(); 488 break; 489 case R.id.menu_add_contact: 490 try { 491 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 492 } catch (ActivityNotFoundException e) { 493 Toast toast = Toast.makeText(this, 494 R.string.add_contact_not_available, 495 Toast.LENGTH_SHORT); 496 toast.show(); 497 } 498 break; 499 case R.id.menu_import_export: 500 // We hard-code the "contactsAreAvailable" argument because doing it properly would 501 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 502 // now in Dialtacts for (potential) performance reasons. Compare with how it is 503 // done in {@link PeopleActivity}. 504 ImportExportDialogFragment.show(getFragmentManager(), true, 505 DialtactsActivity.class); 506 return true; 507 case R.id.menu_clear_frequents: 508 ClearFrequentsDialog.show(getFragmentManager()); 509 return true; 510 case R.id.menu_call_settings: 511 handleMenuSettings(); 512 return true; 513 } 514 return false; 515 } 516 517 @Override 518 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 519 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 520 if (resultCode == RESULT_OK) { 521 final ArrayList<String> matches = data.getStringArrayListExtra( 522 RecognizerIntent.EXTRA_RESULTS); 523 if (matches.size() > 0) { 524 final String match = matches.get(0); 525 mSearchView.setText(match); 526 } else { 527 Log.e(TAG, "Voice search - nothing heard"); 528 } 529 } else { 530 Log.e(TAG, "Voice search failed"); 531 } 532 } 533 super.onActivityResult(requestCode, resultCode, data); 534 } 535 536 /** 537 * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual 538 * updates are handled by a callback which is invoked after the dialpad fragment is shown. 539 * @see #onDialpadShown 540 */ 541 private void showDialpadFragment(boolean animate) { 542 if (mIsDialpadShown) { 543 return; 544 } 545 mIsDialpadShown = true; 546 mDialpadFragment.setAnimate(animate); 547 548 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 549 ft.show(mDialpadFragment); 550 ft.commit(); 551 552 if (!isInSearchUi()) { 553 enterSearchUi(true /* isSmartDial */, mSearchQuery); 554 } 555 } 556 557 /** 558 * Callback from child DialpadFragment when the dialpad is shown. 559 */ 560 public void onDialpadShown() { 561 updateFloatingActionButton(); 562 if (mDialpadFragment.getAnimate()) { 563 Animation slideIn = AnimationUtils.loadAnimation(this, R.anim.slide_in); 564 mDialpadFragment.getView().startAnimation(slideIn); 565 } else { 566 mDialpadFragment.setYFraction(0); 567 } 568 569 updateSearchFragmentPosition(); 570 getActionBar().hide(); 571 } 572 573 /** 574 * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in 575 * a callback after the hide animation ends. 576 * @see #commitDialpadFragmentHide 577 */ 578 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 579 if (mDialpadFragment == null) { 580 return; 581 } 582 if (clearDialpad) { 583 mDialpadFragment.clearDialpad(); 584 } 585 if (!mIsDialpadShown) { 586 return; 587 } 588 mIsDialpadShown = false; 589 mDialpadFragment.setAnimate(animate); 590 591 updateFloatingActionButton(); 592 if (animate) { 593 Animation slideOut = AnimationUtils.loadAnimation(this, R.anim.slide_out); 594 slideOut.setAnimationListener(new ActivityAnimationListener() { 595 @Override 596 public void onAnimationEnd(Animation animation) { 597 commitDialpadFragmentHide(); 598 } 599 }); 600 mDialpadFragment.getView().startAnimation(slideOut); 601 } else { 602 commitDialpadFragmentHide(); 603 } 604 605 updateSearchFragmentPosition(); 606 mListsFragment.maybeShowActionBar(); 607 } 608 609 /** 610 * Finishes hiding the dialpad fragment after any animations are completed. 611 */ 612 private void commitDialpadFragmentHide() { 613 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 614 ft.hide(mDialpadFragment); 615 ft.commit(); 616 } 617 618 private void updateSearchFragmentPosition() { 619 int translationValue = mIsDialpadShown ? -mActionBarHeight : 0; 620 SearchFragment fragment = null; 621 if (mInDialpadSearch) { 622 fragment = mSmartDialSearchFragment; 623 } else if (mInRegularSearch) { 624 fragment = mRegularSearchFragment; 625 } 626 if (fragment != null && fragment.isVisible()) { 627 fragment.getView().animate().translationY(translationValue) 628 .setInterpolator(hideActionBarInterpolator).setDuration(ANIMATION_DURATION); 629 } 630 } 631 632 private boolean isInSearchUi() { 633 return mInDialpadSearch || mInRegularSearch; 634 } 635 636 private void setNotInSearchUi() { 637 mInDialpadSearch = false; 638 mInRegularSearch = false; 639 } 640 641 private void hideDialpadAndSearchUi() { 642 mSearchView.setText(null); 643 hideDialpadFragment(false, true); 644 } 645 646 private void hideInputMethod(View view) { 647 final InputMethodManager imm = (InputMethodManager) getSystemService( 648 Context.INPUT_METHOD_SERVICE); 649 if (imm != null && view != null) { 650 imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 651 } 652 } 653 654 private void prepareVoiceSearchButton() { 655 mVoiceSearchButton = getActionBar().getCustomView().findViewById(R.id.voice_search_button); 656 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 657 if (canIntentBeHandled(voiceIntent)) { 658 mVoiceSearchButton.setVisibility(View.VISIBLE); 659 mVoiceSearchButton.setOnClickListener(this); 660 } else { 661 mVoiceSearchButton.setVisibility(View.GONE); 662 } 663 } 664 665 private OptionsPopupMenu buildOptionsMenu(View invoker) { 666 final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker); 667 popupMenu.inflate(R.menu.dialtacts_options); 668 popupMenu.setOnMenuItemClickListener(this); 669 return popupMenu; 670 } 671 672 @Override 673 public boolean onCreateOptionsMenu(Menu menu) { 674 if (mPendingSearchViewQuery != null) { 675 mSearchView.setText(mPendingSearchViewQuery); 676 mPendingSearchViewQuery = null; 677 } 678 return false; 679 } 680 681 /** 682 * Returns true if the intent is due to hitting the green send key (hardware call button: 683 * KEYCODE_CALL) while in a call. 684 * 685 * @param intent the intent that launched this activity 686 * @return true if the intent is due to hitting the green send key while in a call 687 */ 688 private boolean isSendKeyWhileInCall(Intent intent) { 689 // If there is a call in progress and the user launched the dialer by hitting the call 690 // button, go straight to the in-call screen. 691 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); 692 693 try { 694 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 695 if (callKey && phone != null && phone.showCallScreen()) { 696 return true; 697 } 698 } catch (RemoteException e) { 699 Log.e(TAG, "Failed to handle send while in call", e); 700 } 701 702 return false; 703 } 704 705 /** 706 * Sets the current tab based on the intent's request type 707 * 708 * @param intent Intent that contains information about which tab should be selected 709 */ 710 private void displayFragment(Intent intent) { 711 // If we got here by hitting send and we're in call forward along to the in-call activity 712 if (isSendKeyWhileInCall(intent)) { 713 finish(); 714 return; 715 } 716 717 if (mDialpadFragment != null) { 718 final boolean phoneIsInUse = phoneIsInUse(); 719 if (phoneIsInUse || isDialIntent(intent)) { 720 mDialpadFragment.setStartedFromNewIntent(true); 721 if (phoneIsInUse && !mDialpadFragment.isVisible()) { 722 mInCallDialpadUp = true; 723 } 724 showDialpadFragment(false); 725 } 726 } 727 } 728 729 @Override 730 public void onNewIntent(Intent newIntent) { 731 setIntent(newIntent); 732 displayFragment(newIntent); 733 734 invalidateOptionsMenu(); 735 } 736 737 /** Returns true if the given intent contains a phone number to populate the dialer with */ 738 private boolean isDialIntent(Intent intent) { 739 final String action = intent.getAction(); 740 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 741 return true; 742 } 743 if (Intent.ACTION_VIEW.equals(action)) { 744 final Uri data = intent.getData(); 745 if (data != null && CallUtil.SCHEME_TEL.equals(data.getScheme())) { 746 return true; 747 } 748 } 749 return false; 750 } 751 752 /** 753 * Returns an appropriate call origin for this Activity. May return null when no call origin 754 * should be used (e.g. when some 3rd party application launched the screen. Call origin is 755 * for remembering the tab in which the user made a phone call, so the external app's DIAL 756 * request should not be counted.) 757 */ 758 public String getCallOrigin() { 759 return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null; 760 } 761 762 /** 763 * Shows the search fragment 764 */ 765 private void enterSearchUi(boolean smartDialSearch, String query) { 766 if (getFragmentManager().isDestroyed()) { 767 // Weird race condition where fragment is doing work after the activity is destroyed 768 // due to talkback being on (b/10209937). Just return since we can't do any 769 // constructive here. 770 return; 771 } 772 773 if (DEBUG) { 774 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 775 } 776 777 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 778 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 779 transaction.remove(mSmartDialSearchFragment); 780 } else if (mInRegularSearch && mRegularSearchFragment != null) { 781 transaction.remove(mRegularSearchFragment); 782 } 783 784 final String tag; 785 if (smartDialSearch) { 786 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 787 } else { 788 tag = TAG_REGULAR_SEARCH_FRAGMENT; 789 } 790 mInDialpadSearch = smartDialSearch; 791 mInRegularSearch = !smartDialSearch; 792 793 SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 794 transaction.setCustomAnimations(android.R.animator.fade_in, 0); 795 if (fragment == null) { 796 if (smartDialSearch) { 797 fragment = new SmartDialSearchFragment(); 798 } else { 799 fragment = new RegularSearchFragment(); 800 } 801 transaction.add(R.id.dialtacts_frame, fragment, tag); 802 } else { 803 transaction.show(fragment); 804 } 805 806 // DialtactsActivity will provide the options menu 807 fragment.setHasOptionsMenu(false); 808 fragment.setShowEmptyListForNullQuery(true); 809 fragment.setQueryString(query, false /* delaySelection */); 810 transaction.commit(); 811 812 mListsFragment.getView().animate().alpha(0).withLayer(); 813 } 814 815 /** 816 * Hides the search fragment 817 */ 818 private void exitSearchUi() { 819 // See related bug in enterSearchUI(); 820 if (getFragmentManager().isDestroyed()) { 821 return; 822 } 823 824 mSearchView.setText(null); 825 mDialpadFragment.clearDialpad(); 826 setNotInSearchUi(); 827 828 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 829 transaction.setCustomAnimations(0, android.R.animator.fade_out); 830 831 if (mSmartDialSearchFragment != null) { 832 transaction.remove(mSmartDialSearchFragment); 833 } 834 if (mRegularSearchFragment != null) { 835 transaction.remove(mRegularSearchFragment); 836 } 837 transaction.commit(); 838 839 mListsFragment.getView().animate().alpha(1).withLayer(); 840 } 841 842 /** Returns an Intent to launch Call Settings screen */ 843 public static Intent getCallSettingsIntent() { 844 final Intent intent = new Intent(Intent.ACTION_MAIN); 845 intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME); 846 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 847 return intent; 848 } 849 850 @Override 851 public void onBackPressed() { 852 if (mIsDialpadShown) { 853 if (TextUtils.isEmpty(mSearchQuery)) { 854 exitSearchUi(); 855 } 856 hideDialpadFragment(true, false); 857 } else if (isInSearchUi()) { 858 exitSearchUi(); 859 } else { 860 super.onBackPressed(); 861 } 862 } 863 864 @Override 865 public void onDialpadQueryChanged(String query) { 866 if (mSmartDialSearchFragment != null) { 867 mSmartDialSearchFragment.setAddToContactNumber(query); 868 } 869 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 870 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 871 872 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 873 if (DEBUG) { 874 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 875 } 876 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 877 // This callback can happen if the dialpad fragment is recreated because of 878 // activity destruction. In that case, don't update the search view because 879 // that would bring the user back to the search fragment regardless of the 880 // previous state of the application. Instead, just return here and let the 881 // fragment manager correctly figure out whatever fragment was last displayed. 882 if (!TextUtils.isEmpty(normalizedQuery)) { 883 mPendingSearchViewQuery = normalizedQuery; 884 } 885 return; 886 } 887 mSearchView.setText(normalizedQuery); 888 } 889 } 890 891 @Override 892 public void onListFragmentScrollStateChange(int scrollState) { 893 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 894 hideDialpadFragment(true, false); 895 hideInputMethod(getCurrentFocus()); 896 } 897 } 898 899 @Override 900 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 901 int totalItemCount) { 902 // TODO: No-op for now. This should eventually show/hide the actionBar based on 903 // interactions with the ListsFragments. 904 } 905 906 @Override 907 public void setFloatingActionButtonVisible(boolean visible) { 908 mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE); 909 } 910 911 private boolean phoneIsInUse() { 912 final TelephonyManager tm = (TelephonyManager) getSystemService( 913 Context.TELEPHONY_SERVICE); 914 return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE; 915 } 916 917 public static Intent getAddNumberToContactIntent(CharSequence text) { 918 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 919 intent.putExtra(Intents.Insert.PHONE, text); 920 intent.setType(Contacts.CONTENT_ITEM_TYPE); 921 return intent; 922 } 923 924 private boolean canIntentBeHandled(Intent intent) { 925 final PackageManager packageManager = getPackageManager(); 926 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 927 PackageManager.MATCH_DEFAULT_ONLY); 928 return resolveInfo != null && resolveInfo.size() > 0; 929 } 930 931 @Override 932 public void showCallHistory() { 933 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 934 // CONTENT_TYPE, so that we always open our call log from our dialer 935 final Intent intent = new Intent(this, CallLogActivity.class); 936 startActivity(intent); 937 } 938 939 /** 940 * Called when the user has long-pressed a contact tile to start a drag operation. 941 */ 942 @Override 943 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 944 getActionBar().hide(); 945 mRemoveViewContainer.setVisibility(View.VISIBLE); 946 } 947 948 @Override 949 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 950 } 951 952 /** 953 * Called when the user has released a contact tile after long-pressing it. 954 */ 955 @Override 956 public void onDragFinished(int x, int y) { 957 getActionBar().show(); 958 mRemoveViewContainer.setVisibility(View.GONE); 959 } 960 961 @Override 962 public void onDroppedOnRemove() {} 963 964 /** 965 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 966 * once it has been attached to the activity. 967 */ 968 @Override 969 public void setDragDropController(DragDropController dragController) { 970 mDragDropController = dragController; 971 ((RemoveView) findViewById(R.id.remove_view)) 972 .setDragDropController(dragController); 973 } 974 975 @Override 976 public void onPickPhoneNumberAction(Uri dataUri) { 977 // Specify call-origin so that users will see the previous tab instead of 978 // CallLog screen (search UI will be automatically exited). 979 PhoneNumberInteraction.startInteractionForPhoneCall( 980 DialtactsActivity.this, dataUri, getCallOrigin()); 981 mClearSearchOnPause = true; 982 } 983 984 @Override 985 public void onCallNumberDirectly(String phoneNumber) { 986 Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin()); 987 startActivity(intent); 988 mClearSearchOnPause = true; 989 } 990 991 @Override 992 public void onShortcutIntentCreated(Intent intent) { 993 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 994 } 995 996 @Override 997 public void onHomeInActionBarSelected() { 998 exitSearchUi(); 999 } 1000 1001 public int getActionBarHeight() { 1002 return mActionBarHeight; 1003 } 1004 1005 @Override 1006 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 1007 1008 } 1009 1010 @Override 1011 public void onPageSelected(int position) { 1012 mCurrentTabPosition = position; 1013 // If the dialpad is showing, the floating action button should always be middle aligned. 1014 if (!mIsDialpadShown) { 1015 alignFloatingActionButtonByTab(mCurrentTabPosition); 1016 } 1017 } 1018 1019 @Override 1020 public void onPageScrollStateChanged(int state) { 1021 } 1022 1023 private void updateFloatingActionButton() { 1024 if (mIsDialpadShown) { 1025 mFloatingActionButton.setImageResource(R.drawable.fab_ic_call); 1026 mFloatingActionButton.setContentDescription( 1027 getResources().getString(R.string.description_dial_button)); 1028 alignFloatingActionButtonMiddle(); 1029 } else { 1030 mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial); 1031 mFloatingActionButton.setContentDescription( 1032 getResources().getString(R.string.action_menu_dialpad_button)); 1033 alignFloatingActionButtonByTab(mCurrentTabPosition); 1034 } 1035 } 1036 1037 private void alignFloatingActionButtonByTab(int position) { 1038 if (position == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1039 alignFloatingActionButtonMiddle(); 1040 } else { 1041 alignFloatingActionButtonRight(); 1042 } 1043 } 1044 1045 private void alignFloatingActionButtonRight() { 1046 final RelativeLayout.LayoutParams params = 1047 (RelativeLayout.LayoutParams) mFloatingActionButtonContainer.getLayoutParams(); 1048 params.removeRule(RelativeLayout.CENTER_HORIZONTAL); 1049 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1050 mFloatingActionButtonContainer.setLayoutParams(params); 1051 } 1052 1053 private void alignFloatingActionButtonMiddle() { 1054 final RelativeLayout.LayoutParams params = 1055 (RelativeLayout.LayoutParams) mFloatingActionButtonContainer.getLayoutParams(); 1056 params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1057 params.addRule(RelativeLayout.CENTER_HORIZONTAL); 1058 mFloatingActionButtonContainer.setLayoutParams(params); 1059 } 1060 1061 /** 1062 * Convenience class which implements AnimationListener interface as null-op methods. 1063 */ 1064 private class ActivityAnimationListener implements AnimationListener { 1065 @Override 1066 public void onAnimationStart(Animation animation) { 1067 } 1068 1069 @Override 1070 public void onAnimationEnd(Animation animation) { 1071 } 1072 1073 @Override 1074 public void onAnimationRepeat(Animation animation) { 1075 } 1076 } 1077} 1078