DialtactsActivity.java revision 752956e3add8b9fff107e1637120a74a61a5fead
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.AttributeSet; 44import android.util.Log; 45import android.view.DragEvent; 46import android.view.Menu; 47import android.view.MenuInflater; 48import android.view.MenuItem; 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.dialerbind.DatabaseHelperManager; 88import com.android.internal.telephony.ITelephony; 89 90import java.util.ArrayList; 91import java.util.List; 92 93/** 94 * The dialer tab's title is 'phone', a more common name (see strings.xml). 95 */ 96public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, 97 DialpadFragment.OnDialpadQueryChangedListener, 98 OnListFragmentScrolledListener, 99 DialpadFragment.HostInterface, 100 ListsFragment.HostInterface, 101 SpeedDialFragment.HostInterface, 102 OnDragDropListener, 103 OnPhoneNumberPickerActionListener, 104 ViewPager.OnPageChangeListener { 105 private static final String TAG = "DialtactsActivity"; 106 107 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 108 109 public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; 110 111 /** Used to open Call Setting */ 112 private static final String PHONE_PACKAGE = "com.android.phone"; 113 private static final String CALL_SETTINGS_CLASS_NAME = 114 "com.android.phone.CallFeaturesSetting"; 115 /** @see #getCallOrigin() */ 116 private static final String CALL_ORIGIN_DIALTACTS = 117 "com.android.dialer.DialtactsActivity"; 118 119 private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; 120 private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; 121 private static final String KEY_SEARCH_QUERY = "search_query"; 122 private static final String KEY_FIRST_LAUNCH = "first_launch"; 123 124 private static final String TAG_DIALPAD_FRAGMENT = "dialpad"; 125 private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; 126 private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; 127 private static final String TAG_FAVORITES_FRAGMENT = "favorites"; 128 129 /** 130 * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. 131 */ 132 private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; 133 134 private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; 135 136 private static final int ANIMATION_DURATION = 250; 137 138 private RelativeLayout parentLayout; 139 140 /** 141 * Fragment containing the dialpad that slides into view 142 */ 143 private DialpadFragment mDialpadFragment; 144 145 /** 146 * Fragment for searching phone numbers using the alphanumeric keyboard. 147 */ 148 private RegularSearchFragment mRegularSearchFragment; 149 150 /** 151 * Fragment for searching phone numbers using the dialpad. 152 */ 153 private SmartDialSearchFragment mSmartDialSearchFragment; 154 155 /** 156 * Fragment containing the speed dial list, recents list, and all contacts list. 157 */ 158 private ListsFragment mListsFragment; 159 160 private View mFloatingActionButtonContainer; 161 private ImageButton mFloatingActionButton; 162 163 private View mFragmentsFrame; 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 * View that contains the "Remove" dialog that shows up when the user long presses a contact. 197 * If the user releases a contact when hovering on top of this, the contact is unfavorited and 198 * removed from the speed dial list. 199 */ 200 private View mRemoveViewContainer; 201 202 final Interpolator hideActionBarInterpolator = new AccelerateInterpolator(1.5f); 203 final Interpolator showActionBarInterpolator = new DecelerateInterpolator(1.5f); 204 private String mSearchQuery; 205 206 private DialerDatabaseHelper mDialerDatabaseHelper; 207 private DragDropController mDragDropController; 208 209 private int mDialerBackgroundColor; 210 private int mContactListBackgroundColor; 211 212 private class OverflowPopupMenu extends PopupMenu { 213 public OverflowPopupMenu(Context context, View anchor) { 214 super(context, anchor); 215 } 216 217 @Override 218 public void show() { 219 final Menu menu = getMenu(); 220 final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); 221 // TODO: Check mSpeedDialFragment.hasFrequents() 222 clearFrequents.setVisible(true); 223 super.show(); 224 } 225 } 226 227 /** 228 * Listener that listens to drag events and sends their x and y coordinates to a 229 * {@link DragDropController}. 230 */ 231 private class LayoutOnDragListener implements OnDragListener { 232 @Override 233 public boolean onDrag(View v, DragEvent event) { 234 if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { 235 mDragDropController.handleDragHovered(v, (int) event.getX(), 236 (int) event.getY()); 237 } 238 return true; 239 } 240 } 241 242 /** 243 * Listener used to send search queries to the phone search fragment. 244 */ 245 private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { 246 @Override 247 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 248 } 249 250 @Override 251 public void onTextChanged(CharSequence s, int start, int before, int count) { 252 final String newText = s.toString(); 253 if (newText.equals(mSearchQuery)) { 254 // If the query hasn't changed (perhaps due to activity being destroyed 255 // and restored, or user launching the same DIAL intent twice), then there is 256 // no need to do anything here. 257 return; 258 } 259 mSearchQuery = newText; 260 if (DEBUG) { 261 Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText); 262 } 263 final boolean dialpadSearch = mIsDialpadShown; 264 265 // Show search result with non-empty text. Show a bare list otherwise. 266 if (TextUtils.isEmpty(newText) && getInSearchUi()) { 267 exitSearchUi(); 268 mSearchViewCloseButton.setVisibility(View.GONE); 269 mVoiceSearchButton.setVisibility(View.VISIBLE); 270 return; 271 } else if (!TextUtils.isEmpty(newText)) { 272 final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) || 273 (!dialpadSearch && mInRegularSearch); 274 if (!sameSearchMode) { 275 // call enterSearchUi only if we are switching search modes, or entering 276 // search ui for the first time 277 enterSearchUi(dialpadSearch, newText); 278 } 279 280 if (dialpadSearch && mSmartDialSearchFragment != null) { 281 mSmartDialSearchFragment.setQueryString(newText, false); 282 } else if (mRegularSearchFragment != null) { 283 mRegularSearchFragment.setQueryString(newText, false); 284 } 285 mSearchViewCloseButton.setVisibility(View.VISIBLE); 286 mVoiceSearchButton.setVisibility(View.GONE); 287 return; 288 } 289 return; 290 } 291 292 @Override 293 public void afterTextChanged(Editable s) { 294 } 295 }; 296 297 @Override 298 protected void onCreate(Bundle savedInstanceState) { 299 super.onCreate(savedInstanceState); 300 mFirstLaunch = true; 301 302 setContentView(R.layout.dialtacts_activity); 303 getWindow().setBackgroundDrawable(null); 304 305 final ActionBar actionBar = getActionBar(); 306 actionBar.setCustomView(R.layout.search_edittext); 307 actionBar.setDisplayShowCustomEnabled(true); 308 309 final View customView = actionBar.getCustomView(); 310 311 mSearchView = (EditText) customView.findViewById(R.id.search_view); 312 mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); 313 314 mSearchViewCloseButton = customView.findViewById(R.id.search_close_button); 315 mSearchViewCloseButton.setOnClickListener(this); 316 317 final TypedArray styledAttributes = getTheme().obtainStyledAttributes( 318 new int[] { android.R.attr.actionBarSize }); 319 mActionBarHeight = (int) styledAttributes.getDimension(0, 0); 320 styledAttributes.recycle(); 321 322 // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState 323 // is null. Otherwise the fragment manager takes care of recreating these fragments. 324 if (savedInstanceState == null) { 325 getFragmentManager().beginTransaction() 326 .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT) 327 .add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT) 328 .commit(); 329 } else { 330 mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); 331 mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); 332 mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); 333 mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); 334 } 335 336 parentLayout = (RelativeLayout) findViewById(R.id.dialtacts_mainlayout); 337 parentLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); 338 parentLayout.setOnDragListener(new LayoutOnDragListener()); 339 340 mDialerBackgroundColor = getResources().getColor(R.color.background_dialer_light); 341 mContactListBackgroundColor = 342 getResources().getColor(R.color.contact_list_background_color); 343 344 mFragmentsFrame = findViewById(R.id.dialtacts_frame); 345 346 mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container); 347 ViewUtil.setupFloatingActionButton(mFloatingActionButtonContainer, getResources()); 348 349 mFloatingActionButton = (ImageButton) findViewById(R.id.floating_action_button); 350 mFloatingActionButton.setOnClickListener(this); 351 352 mRemoveViewContainer = findViewById(R.id.remove_view_container); 353 354 mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); 355 SmartDialPrefix.initializeNanpSettings(this); 356 } 357 358 @Override 359 protected void onResume() { 360 super.onResume(); 361 if (mFirstLaunch) { 362 displayFragment(getIntent()); 363 } else if (!phoneIsInUse() && mInCallDialpadUp) { 364 hideDialpadFragment(false, true); 365 mInCallDialpadUp = false; 366 } 367 mFirstLaunch = false; 368 prepareVoiceSearchButton(); 369 mDialerDatabaseHelper.startSmartDialUpdateThread(); 370 } 371 372 @Override 373 protected void onPause() { 374 if (mClearSearchOnPause) { 375 hideDialpadAndSearchUi(); 376 mClearSearchOnPause = false; 377 } 378 super.onPause(); 379 } 380 381 @Override 382 protected void onSaveInstanceState(Bundle outState) { 383 super.onSaveInstanceState(outState); 384 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 385 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 386 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 387 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 388 } 389 390 @Override 391 public void onAttachFragment(Fragment fragment) { 392 if (fragment instanceof DialpadFragment) { 393 mDialpadFragment = (DialpadFragment) fragment; 394 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 395 transaction.hide(mDialpadFragment); 396 transaction.commit(); 397 } else if (fragment instanceof SmartDialSearchFragment) { 398 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 399 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); 400 if (mFragmentsFrame != null) { 401 mFragmentsFrame.setAlpha(1.0f); 402 } 403 } else if (fragment instanceof SearchFragment) { 404 mRegularSearchFragment = (RegularSearchFragment) fragment; 405 mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this); 406 if (mFragmentsFrame != null) { 407 mFragmentsFrame.setAlpha(1.0f); 408 } 409 } else if (fragment instanceof ListsFragment) { 410 mListsFragment = (ListsFragment) fragment; 411 mListsFragment.addOnPageChangeListener(this); 412 } 413 } 414 415 protected void handleMenuSettings() { 416 openTelephonySetting(this); 417 } 418 419 public static void openTelephonySetting(Activity activity) { 420 final Intent settingsIntent = getCallSettingsIntent(); 421 activity.startActivity(settingsIntent); 422 } 423 424 @Override 425 public void onClick(View view) { 426 switch (view.getId()) { 427 case R.id.floating_action_button: 428 if (!mIsDialpadShown) { 429 mInCallDialpadUp = false; 430 showDialpadFragment(true); 431 } else { 432 // Dial button was pressed; tell the Dialpad fragment 433 mDialpadFragment.dialButtonPressed(); 434 } 435 break; 436 case R.id.search_close_button: 437 // Clear the search field 438 if (!TextUtils.isEmpty(mSearchView.getText())) { 439 mDialpadFragment.clearDialpad(); 440 mSearchView.setText(null); 441 } 442 break; 443 case R.id.voice_search_button: 444 try { 445 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 446 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 447 } catch (ActivityNotFoundException e) { 448 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 449 Toast.LENGTH_SHORT).show(); 450 } 451 break; 452 default: { 453 Log.wtf(TAG, "Unexpected onClick event from " + view); 454 break; 455 } 456 } 457 } 458 459 @Override 460 public boolean onOptionsItemSelected(MenuItem item) { 461 switch (item.getItemId()) { 462 case R.id.menu_history: 463 showCallHistory(); 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 // SpeedDialFragment.hasFrequents 486 ClearFrequentsDialog.show(getFragmentManager()); 487 return true; 488 case R.id.menu_call_settings: 489 handleMenuSettings(); 490 return true; 491 } 492 return false; 493 } 494 495 @Override 496 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 497 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 498 if (resultCode == RESULT_OK) { 499 final ArrayList<String> matches = data.getStringArrayListExtra( 500 RecognizerIntent.EXTRA_RESULTS); 501 if (matches.size() > 0) { 502 final String match = matches.get(0); 503 mSearchView.setText(match); 504 } else { 505 Log.e(TAG, "Voice search - nothing heard"); 506 } 507 } else { 508 Log.e(TAG, "Voice search failed"); 509 } 510 } 511 super.onActivityResult(requestCode, resultCode, data); 512 } 513 514 /** 515 * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual 516 * updates are handled by a callback which is invoked after the dialpad fragment is shown. 517 * @see #onDialpadShown 518 */ 519 private void showDialpadFragment(boolean animate) { 520 if (mIsDialpadShown) { 521 return; 522 } 523 mIsDialpadShown = true; 524 mDialpadFragment.setAnimate(animate); 525 526 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 527 ft.show(mDialpadFragment); 528 ft.commit(); 529 } 530 531 /** 532 * Callback from child DialpadFragment when the dialpad is shown. 533 */ 534 public void onDialpadShown() { 535 updateFloatingActionButton(); 536 if (mDialpadFragment.getAnimate()) { 537 Animation slideIn = AnimationUtils.loadAnimation(this, R.anim.slide_in); 538 mDialpadFragment.getView().startAnimation(slideIn); 539 } else { 540 mDialpadFragment.setYFraction(0); 541 } 542 543 if (mListsFragment != null && mListsFragment.isResumed() && mListsFragment.isVisible()) { 544 // If the favorites fragment is showing, fade to blank. 545 mFragmentsFrame.animate().alpha(0.0f); 546 parentLayout.setBackgroundColor(mContactListBackgroundColor); 547 } 548 549 updateSearchFragmentPosition(); 550 getActionBar().hide(); 551 } 552 553 /** 554 * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in 555 * a callback after the hide animation ends. 556 * @see #commitDialpadFragmentHide 557 */ 558 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 559 if (mDialpadFragment == null) { 560 return; 561 } 562 if (clearDialpad) { 563 mDialpadFragment.clearDialpad(); 564 } 565 if (!mIsDialpadShown) { 566 return; 567 } 568 mIsDialpadShown = false; 569 mDialpadFragment.setAnimate(animate); 570 571 updateFloatingActionButton(); 572 if (animate) { 573 Animation slideOut = AnimationUtils.loadAnimation(this, R.anim.slide_out); 574 slideOut.setAnimationListener(new ActivityAnimationListener() { 575 @Override 576 public void onAnimationEnd(Animation animation) { 577 commitDialpadFragmentHide(); 578 } 579 }); 580 mDialpadFragment.getView().startAnimation(slideOut); 581 } else { 582 commitDialpadFragmentHide(); 583 } 584 585 if (mListsFragment != null && mListsFragment.isVisible()) { 586 mFragmentsFrame.animate().alpha(1.0f); 587 parentLayout.setBackgroundColor(mDialerBackgroundColor); 588 } 589 590 updateSearchFragmentPosition(); 591 getActionBar().show(); 592 } 593 594 /** 595 * Finishes hiding the dialpad fragment after any animations are completed. 596 */ 597 private void commitDialpadFragmentHide() { 598 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 599 ft.hide(mDialpadFragment); 600 ft.commit(); 601 } 602 603 private void updateSearchFragmentPosition() { 604 int translationValue = mIsDialpadShown ? -mActionBarHeight : 0; 605 SearchFragment fragment = null; 606 if (mInDialpadSearch) { 607 fragment = mSmartDialSearchFragment; 608 } else if (mInRegularSearch) { 609 fragment = mRegularSearchFragment; 610 } 611 if (fragment != null && fragment.isVisible()) { 612 fragment.getView().animate().translationY(translationValue) 613 .setInterpolator(hideActionBarInterpolator).setDuration(ANIMATION_DURATION); 614 } 615 } 616 617 private boolean getInSearchUi() { 618 return mInDialpadSearch || mInRegularSearch; 619 } 620 621 private void setNotInSearchUi() { 622 mInDialpadSearch = false; 623 mInRegularSearch = false; 624 } 625 626 private void hideDialpadAndSearchUi() { 627 mSearchView.setText(null); 628 hideDialpadFragment(false, true); 629 } 630 631 private void hideInputMethod(View view) { 632 final InputMethodManager imm = (InputMethodManager) getSystemService( 633 Context.INPUT_METHOD_SERVICE); 634 if (imm != null && view != null) { 635 imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 636 } 637 } 638 639 private void prepareVoiceSearchButton() { 640 mVoiceSearchButton = getActionBar().getCustomView().findViewById(R.id.voice_search_button); 641 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 642 if (canIntentBeHandled(voiceIntent)) { 643 mVoiceSearchButton.setVisibility(View.VISIBLE); 644 mVoiceSearchButton.setOnClickListener(this); 645 } else { 646 mVoiceSearchButton.setVisibility(View.GONE); 647 } 648 } 649 650 @Override 651 public boolean onCreateOptionsMenu(Menu menu) { 652 if (DEBUG) { 653 Log.d(TAG, "onCreateOptionsMenu"); 654 } 655 MenuInflater inflater = getMenuInflater(); 656 inflater.inflate(R.menu.dialtacts_options, menu); 657 658 if (mPendingSearchViewQuery != null) { 659 mSearchView.setText(mPendingSearchViewQuery); 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 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 763 transaction.remove(mSmartDialSearchFragment); 764 } else if (mInRegularSearch && mRegularSearchFragment != null) { 765 transaction.remove(mRegularSearchFragment); 766 } 767 768 final String tag; 769 if (smartDialSearch) { 770 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 771 } else { 772 tag = TAG_REGULAR_SEARCH_FRAGMENT; 773 } 774 mInDialpadSearch = smartDialSearch; 775 mInRegularSearch = !smartDialSearch; 776 777 SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 778 if (fragment == null) { 779 if (smartDialSearch) { 780 fragment = new SmartDialSearchFragment(); 781 } else { 782 fragment = new RegularSearchFragment(); 783 } 784 } 785 // DialtactsActivity will provide the options menu 786 fragment.setHasOptionsMenu(false); 787 transaction.replace(R.id.dialtacts_frame, fragment, tag); 788 transaction.addToBackStack(null); 789 fragment.setQueryString(query, false); 790 transaction.commit(); 791 } 792 793 /** 794 * Hides the search fragment 795 */ 796 private void exitSearchUi() { 797 // See related bug in enterSearchUI(); 798 if (getFragmentManager().isDestroyed()) { 799 return; 800 } 801 // Go all the way back to the favorites fragment, regardless of how many times we 802 // transitioned between search fragments 803 getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE); 804 setNotInSearchUi(); 805 806 if (mIsDialpadShown) { 807 mFragmentsFrame.setAlpha(0); 808 } 809 } 810 811 /** Returns an Intent to launch Call Settings screen */ 812 public static Intent getCallSettingsIntent() { 813 final Intent intent = new Intent(Intent.ACTION_MAIN); 814 intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME); 815 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 816 return intent; 817 } 818 819 @Override 820 public void onBackPressed() { 821 if (mIsDialpadShown) { 822 hideDialpadFragment(true, false); 823 } else if (getInSearchUi()) { 824 mSearchView.setText(null); 825 mDialpadFragment.clearDialpad(); 826 } else { 827 super.onBackPressed(); 828 } 829 } 830 831 @Override 832 public void onDialpadQueryChanged(String query) { 833 if (mSmartDialSearchFragment != null) { 834 mSmartDialSearchFragment.setAddToContactNumber(query); 835 } 836 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 837 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 838 839 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 840 if (DEBUG) { 841 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 842 } 843 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 844 // This callback can happen if the dialpad fragment is recreated because of 845 // activity destruction. In that case, don't update the search view because 846 // that would bring the user back to the search fragment regardless of the 847 // previous state of the application. Instead, just return here and let the 848 // fragment manager correctly figure out whatever fragment was last displayed. 849 if (!TextUtils.isEmpty(normalizedQuery)) { 850 mPendingSearchViewQuery = normalizedQuery; 851 } 852 return; 853 } 854 mSearchView.setText(normalizedQuery); 855 } 856 } 857 858 @Override 859 public void onListFragmentScrollStateChange(int scrollState) { 860 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 861 hideDialpadFragment(true, false); 862 hideInputMethod(getCurrentFocus()); 863 } 864 } 865 866 @Override 867 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 868 int totalItemCount) { 869 // TODO: No-op for now. This should eventually show/hide the actionBar based on 870 // interactions with the ListsFragments. 871 } 872 873 @Override 874 public void setFloatingActionButtonVisible(boolean visible) { 875 mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE); 876 } 877 878 private boolean phoneIsInUse() { 879 final TelephonyManager tm = (TelephonyManager) getSystemService( 880 Context.TELEPHONY_SERVICE); 881 return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE; 882 } 883 884 public static Intent getAddNumberToContactIntent(CharSequence text) { 885 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 886 intent.putExtra(Intents.Insert.PHONE, text); 887 intent.setType(Contacts.CONTENT_ITEM_TYPE); 888 return intent; 889 } 890 891 private boolean canIntentBeHandled(Intent intent) { 892 final PackageManager packageManager = getPackageManager(); 893 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 894 PackageManager.MATCH_DEFAULT_ONLY); 895 return resolveInfo != null && resolveInfo.size() > 0; 896 } 897 898 @Override 899 public void showCallHistory() { 900 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 901 // CONTENT_TYPE, so that we always open our call log from our dialer 902 final Intent intent = new Intent(this, CallLogActivity.class); 903 startActivity(intent); 904 } 905 906 /** 907 * Called when the user has long-pressed a contact tile to start a drag operation. 908 */ 909 @Override 910 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 911 getActionBar().hide(); 912 mRemoveViewContainer.setVisibility(View.VISIBLE); 913 } 914 915 @Override 916 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 917 } 918 919 /** 920 * Called when the user has released a contact tile after long-pressing it. 921 */ 922 @Override 923 public void onDragFinished(int x, int y) { 924 getActionBar().show(); 925 mRemoveViewContainer.setVisibility(View.GONE); 926 } 927 928 @Override 929 public void onDroppedOnRemove() {} 930 931 /** 932 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 933 * once it has been attached to the activity. 934 */ 935 @Override 936 public void setDragDropController(DragDropController dragController) { 937 mDragDropController = dragController; 938 ((RemoveView) findViewById(R.id.remove_view)) 939 .setDragDropController(dragController); 940 } 941 942 @Override 943 public void onPickPhoneNumberAction(Uri dataUri) { 944 // Specify call-origin so that users will see the previous tab instead of 945 // CallLog screen (search UI will be automatically exited). 946 PhoneNumberInteraction.startInteractionForPhoneCall( 947 DialtactsActivity.this, dataUri, getCallOrigin()); 948 mClearSearchOnPause = true; 949 } 950 951 @Override 952 public void onCallNumberDirectly(String phoneNumber) { 953 Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin()); 954 startActivity(intent); 955 mClearSearchOnPause = true; 956 } 957 958 @Override 959 public void onShortcutIntentCreated(Intent intent) { 960 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 961 } 962 963 @Override 964 public void onHomeInActionBarSelected() { 965 exitSearchUi(); 966 } 967 968 public int getActionBarHeight() { 969 return mActionBarHeight; 970 } 971 972 @Override 973 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 974 975 } 976 977 @Override 978 public void onPageSelected(int position) { 979 mCurrentTabPosition = position; 980 // If the dialpad is showing, the floating action button should always be middle aligned. 981 if (!mIsDialpadShown) { 982 alignFloatingActionButtonByTab(mCurrentTabPosition); 983 } 984 } 985 986 @Override 987 public void onPageScrollStateChanged(int state) { 988 } 989 990 private void updateFloatingActionButton() { 991 if (mIsDialpadShown) { 992 mFloatingActionButton.setImageResource(R.drawable.fab_ic_call); 993 mFloatingActionButton.setContentDescription( 994 getResources().getString(R.string.description_dial_button)); 995 alignFloatingActionButtonByTab(mCurrentTabPosition); 996 } else { 997 mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial); 998 mFloatingActionButton.setContentDescription( 999 getResources().getString(R.string.action_menu_dialpad_button)); 1000 alignFloatingActionButtonMiddle(); 1001 } 1002 } 1003 1004 private void alignFloatingActionButtonByTab(int position) { 1005 if (position == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1006 alignFloatingActionButtonMiddle(); 1007 } else { 1008 alignFloatingActionButtonRight(); 1009 } 1010 } 1011 1012 private void alignFloatingActionButtonRight() { 1013 final RelativeLayout.LayoutParams params = 1014 (RelativeLayout.LayoutParams) mFloatingActionButtonContainer.getLayoutParams(); 1015 params.removeRule(RelativeLayout.CENTER_HORIZONTAL); 1016 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1017 mFloatingActionButtonContainer.setLayoutParams(params); 1018 } 1019 1020 private void alignFloatingActionButtonMiddle() { 1021 final RelativeLayout.LayoutParams params = 1022 (RelativeLayout.LayoutParams) mFloatingActionButtonContainer.getLayoutParams(); 1023 params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1024 params.addRule(RelativeLayout.CENTER_HORIZONTAL); 1025 mFloatingActionButtonContainer.setLayoutParams(params); 1026 } 1027 1028 /** 1029 * Convenience class which implements AnimationListener interface as null-op methods. 1030 */ 1031 private class ActivityAnimationListener implements AnimationListener { 1032 @Override 1033 public void onAnimationStart(Animation animation) { 1034 } 1035 1036 @Override 1037 public void onAnimationEnd(Animation animation) { 1038 } 1039 1040 @Override 1041 public void onAnimationRepeat(Animation animation) { 1042 } 1043 } 1044} 1045