DialtactsActivity.java revision f0a95146bb220db05dd2104e76cd3e1cbab8002a
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 264 // Show search result with non-empty text. Show a bare list otherwise. 265 final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) || 266 (!mIsDialpadShown && mInRegularSearch); 267 if (!sameSearchMode) { 268 // call enterSearchUi only if we are switching search modes, or entering 269 // search ui for the first time 270 enterSearchUi(mIsDialpadShown, newText); 271 } 272 273 if (mIsDialpadShown && mSmartDialSearchFragment != null) { 274 mSmartDialSearchFragment.setQueryString(newText, false /* delaySelection */); 275 } else if (mRegularSearchFragment != null) { 276 mRegularSearchFragment.setQueryString(newText, false /* delaySelection */); 277 } 278 279 if (TextUtils.isEmpty(newText)) { 280 mSearchViewCloseButton.setVisibility(View.GONE); 281 mVoiceSearchButton.setVisibility(View.VISIBLE); 282 } else { 283 mSearchViewCloseButton.setVisibility(View.VISIBLE); 284 mVoiceSearchButton.setVisibility(View.GONE); 285 } 286 } 287 288 @Override 289 public void afterTextChanged(Editable s) { 290 } 291 }; 292 293 @Override 294 protected void onCreate(Bundle savedInstanceState) { 295 super.onCreate(savedInstanceState); 296 mFirstLaunch = true; 297 298 setContentView(R.layout.dialtacts_activity); 299 getWindow().setBackgroundDrawable(null); 300 301 final ActionBar actionBar = getActionBar(); 302 actionBar.setCustomView(R.layout.search_edittext); 303 actionBar.setDisplayShowCustomEnabled(true); 304 305 final View customView = actionBar.getCustomView(); 306 307 mSearchView = (EditText) customView.findViewById(R.id.search_view); 308 mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); 309 310 mSearchViewCloseButton = customView.findViewById(R.id.search_close_button); 311 mSearchViewCloseButton.setOnClickListener(this); 312 313 final TypedArray styledAttributes = getTheme().obtainStyledAttributes( 314 new int[] { android.R.attr.actionBarSize }); 315 mActionBarHeight = (int) styledAttributes.getDimension(0, 0); 316 styledAttributes.recycle(); 317 318 // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState 319 // is null. Otherwise the fragment manager takes care of recreating these fragments. 320 if (savedInstanceState == null) { 321 getFragmentManager().beginTransaction() 322 .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT) 323 .add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT) 324 .commit(); 325 } else { 326 mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); 327 mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); 328 mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); 329 mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); 330 } 331 332 parentLayout = (RelativeLayout) findViewById(R.id.dialtacts_mainlayout); 333 parentLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); 334 parentLayout.setOnDragListener(new LayoutOnDragListener()); 335 336 mDialerBackgroundColor = getResources().getColor(R.color.background_dialer_light); 337 mContactListBackgroundColor = 338 getResources().getColor(R.color.contact_list_background_color); 339 340 mFragmentsFrame = findViewById(R.id.dialtacts_frame); 341 342 mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container); 343 ViewUtil.setupFloatingActionButton(mFloatingActionButtonContainer, getResources()); 344 345 mFloatingActionButton = (ImageButton) findViewById(R.id.floating_action_button); 346 mFloatingActionButton.setOnClickListener(this); 347 348 mRemoveViewContainer = findViewById(R.id.remove_view_container); 349 350 mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); 351 SmartDialPrefix.initializeNanpSettings(this); 352 } 353 354 @Override 355 protected void onResume() { 356 super.onResume(); 357 if (mFirstLaunch) { 358 displayFragment(getIntent()); 359 } else if (!phoneIsInUse() && mInCallDialpadUp) { 360 hideDialpadFragment(false, true); 361 mInCallDialpadUp = false; 362 } 363 mFirstLaunch = false; 364 prepareVoiceSearchButton(); 365 mDialerDatabaseHelper.startSmartDialUpdateThread(); 366 } 367 368 @Override 369 protected void onPause() { 370 if (mClearSearchOnPause) { 371 hideDialpadAndSearchUi(); 372 mClearSearchOnPause = false; 373 } 374 super.onPause(); 375 } 376 377 @Override 378 protected void onSaveInstanceState(Bundle outState) { 379 super.onSaveInstanceState(outState); 380 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 381 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 382 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 383 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 384 } 385 386 @Override 387 public void onAttachFragment(Fragment fragment) { 388 if (fragment instanceof DialpadFragment) { 389 mDialpadFragment = (DialpadFragment) fragment; 390 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 391 transaction.hide(mDialpadFragment); 392 transaction.commit(); 393 } else if (fragment instanceof SmartDialSearchFragment) { 394 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 395 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); 396 mSmartDialSearchFragment.setShowEmptyListForNullQuery(true); 397 if (mFragmentsFrame != null) { 398 mFragmentsFrame.setAlpha(1.0f); 399 } 400 } else if (fragment instanceof SearchFragment) { 401 mRegularSearchFragment = (RegularSearchFragment) fragment; 402 mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this); 403 mRegularSearchFragment.setShowEmptyListForNullQuery(true); 404 if (mFragmentsFrame != null) { 405 mFragmentsFrame.setAlpha(1.0f); 406 } 407 } else if (fragment instanceof ListsFragment) { 408 mListsFragment = (ListsFragment) fragment; 409 mListsFragment.addOnPageChangeListener(this); 410 } 411 } 412 413 protected void handleMenuSettings() { 414 openTelephonySetting(this); 415 } 416 417 public static void openTelephonySetting(Activity activity) { 418 final Intent settingsIntent = getCallSettingsIntent(); 419 activity.startActivity(settingsIntent); 420 } 421 422 @Override 423 public void onClick(View view) { 424 switch (view.getId()) { 425 case R.id.floating_action_button: 426 if (!mIsDialpadShown) { 427 mInCallDialpadUp = false; 428 showDialpadFragment(true); 429 } else { 430 // Dial button was pressed; tell the Dialpad fragment 431 mDialpadFragment.dialButtonPressed(); 432 } 433 break; 434 case R.id.search_close_button: 435 // Clear the search field 436 if (!TextUtils.isEmpty(mSearchView.getText())) { 437 mDialpadFragment.clearDialpad(); 438 mSearchView.setText(null); 439 } 440 break; 441 case R.id.voice_search_button: 442 try { 443 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 444 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 445 } catch (ActivityNotFoundException e) { 446 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 447 Toast.LENGTH_SHORT).show(); 448 } 449 break; 450 default: { 451 Log.wtf(TAG, "Unexpected onClick event from " + view); 452 break; 453 } 454 } 455 } 456 457 @Override 458 public boolean onOptionsItemSelected(MenuItem item) { 459 switch (item.getItemId()) { 460 case R.id.menu_history: 461 showCallHistory(); 462 break; 463 case R.id.menu_add_contact: 464 try { 465 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 466 } catch (ActivityNotFoundException e) { 467 Toast toast = Toast.makeText(this, 468 R.string.add_contact_not_available, 469 Toast.LENGTH_SHORT); 470 toast.show(); 471 } 472 break; 473 case R.id.menu_import_export: 474 // We hard-code the "contactsAreAvailable" argument because doing it properly would 475 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 476 // now in Dialtacts for (potential) performance reasons. Compare with how it is 477 // done in {@link PeopleActivity}. 478 ImportExportDialogFragment.show(getFragmentManager(), true, 479 DialtactsActivity.class); 480 return true; 481 case R.id.menu_clear_frequents: 482 // TODO: This should be enabled/disabled based on 483 // SpeedDialFragment.hasFrequents 484 ClearFrequentsDialog.show(getFragmentManager()); 485 return true; 486 case R.id.menu_call_settings: 487 handleMenuSettings(); 488 return true; 489 } 490 return false; 491 } 492 493 @Override 494 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 495 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 496 if (resultCode == RESULT_OK) { 497 final ArrayList<String> matches = data.getStringArrayListExtra( 498 RecognizerIntent.EXTRA_RESULTS); 499 if (matches.size() > 0) { 500 final String match = matches.get(0); 501 mSearchView.setText(match); 502 } else { 503 Log.e(TAG, "Voice search - nothing heard"); 504 } 505 } else { 506 Log.e(TAG, "Voice search failed"); 507 } 508 } 509 super.onActivityResult(requestCode, resultCode, data); 510 } 511 512 /** 513 * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual 514 * updates are handled by a callback which is invoked after the dialpad fragment is shown. 515 * @see #onDialpadShown 516 */ 517 private void showDialpadFragment(boolean animate) { 518 if (mIsDialpadShown) { 519 return; 520 } 521 mIsDialpadShown = true; 522 mDialpadFragment.setAnimate(animate); 523 524 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 525 ft.show(mDialpadFragment); 526 ft.commit(); 527 } 528 529 /** 530 * Callback from child DialpadFragment when the dialpad is shown. 531 */ 532 public void onDialpadShown() { 533 updateFloatingActionButton(); 534 if (mDialpadFragment.getAnimate()) { 535 Animation slideIn = AnimationUtils.loadAnimation(this, R.anim.slide_in); 536 mDialpadFragment.getView().startAnimation(slideIn); 537 } else { 538 mDialpadFragment.setYFraction(0); 539 } 540 541 if (mListsFragment != null && mListsFragment.isResumed() && mListsFragment.isVisible()) { 542 // If the favorites fragment is showing, fade to blank. 543 mFragmentsFrame.animate().alpha(0.0f); 544 parentLayout.setBackgroundColor(mContactListBackgroundColor); 545 } 546 547 updateSearchFragmentPosition(); 548 getActionBar().hide(); 549 } 550 551 /** 552 * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in 553 * a callback after the hide animation ends. 554 * @see #commitDialpadFragmentHide 555 */ 556 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 557 if (mDialpadFragment == null) { 558 return; 559 } 560 if (clearDialpad) { 561 mDialpadFragment.clearDialpad(); 562 } 563 if (!mIsDialpadShown) { 564 return; 565 } 566 mIsDialpadShown = false; 567 mDialpadFragment.setAnimate(animate); 568 569 updateFloatingActionButton(); 570 if (animate) { 571 Animation slideOut = AnimationUtils.loadAnimation(this, R.anim.slide_out); 572 slideOut.setAnimationListener(new ActivityAnimationListener() { 573 @Override 574 public void onAnimationEnd(Animation animation) { 575 commitDialpadFragmentHide(); 576 } 577 }); 578 mDialpadFragment.getView().startAnimation(slideOut); 579 } else { 580 commitDialpadFragmentHide(); 581 } 582 583 if (mListsFragment != null && mListsFragment.isVisible()) { 584 mFragmentsFrame.animate().alpha(1.0f); 585 parentLayout.setBackgroundColor(mDialerBackgroundColor); 586 } 587 588 updateSearchFragmentPosition(); 589 getActionBar().show(); 590 } 591 592 /** 593 * Finishes hiding the dialpad fragment after any animations are completed. 594 */ 595 private void commitDialpadFragmentHide() { 596 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 597 ft.hide(mDialpadFragment); 598 ft.commit(); 599 } 600 601 private void updateSearchFragmentPosition() { 602 int translationValue = mIsDialpadShown ? -mActionBarHeight : 0; 603 SearchFragment fragment = null; 604 if (mInDialpadSearch) { 605 fragment = mSmartDialSearchFragment; 606 } else if (mInRegularSearch) { 607 fragment = mRegularSearchFragment; 608 } 609 if (fragment != null && fragment.isVisible()) { 610 fragment.getView().animate().translationY(translationValue) 611 .setInterpolator(hideActionBarInterpolator).setDuration(ANIMATION_DURATION); 612 } 613 } 614 615 private boolean getInSearchUi() { 616 return mInDialpadSearch || mInRegularSearch; 617 } 618 619 private void setNotInSearchUi() { 620 mInDialpadSearch = false; 621 mInRegularSearch = false; 622 } 623 624 private void hideDialpadAndSearchUi() { 625 mSearchView.setText(null); 626 hideDialpadFragment(false, true); 627 } 628 629 private void hideInputMethod(View view) { 630 final InputMethodManager imm = (InputMethodManager) getSystemService( 631 Context.INPUT_METHOD_SERVICE); 632 if (imm != null && view != null) { 633 imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 634 } 635 } 636 637 private void prepareVoiceSearchButton() { 638 mVoiceSearchButton = getActionBar().getCustomView().findViewById(R.id.voice_search_button); 639 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 640 if (canIntentBeHandled(voiceIntent)) { 641 mVoiceSearchButton.setVisibility(View.VISIBLE); 642 mVoiceSearchButton.setOnClickListener(this); 643 } else { 644 mVoiceSearchButton.setVisibility(View.GONE); 645 } 646 } 647 648 @Override 649 public boolean onCreateOptionsMenu(Menu menu) { 650 if (DEBUG) { 651 Log.d(TAG, "onCreateOptionsMenu"); 652 } 653 MenuInflater inflater = getMenuInflater(); 654 inflater.inflate(R.menu.dialtacts_options, menu); 655 656 if (mPendingSearchViewQuery != null) { 657 mSearchView.setText(mPendingSearchViewQuery); 658 mPendingSearchViewQuery = null; 659 } 660 return super.onCreateOptionsMenu(menu); 661 } 662 663 /** 664 * Returns true if the intent is due to hitting the green send key (hardware call button: 665 * KEYCODE_CALL) while in a call. 666 * 667 * @param intent the intent that launched this activity 668 * @return true if the intent is due to hitting the green send key while in a call 669 */ 670 private boolean isSendKeyWhileInCall(Intent intent) { 671 // If there is a call in progress and the user launched the dialer by hitting the call 672 // button, go straight to the in-call screen. 673 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); 674 675 try { 676 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 677 if (callKey && phone != null && phone.showCallScreen()) { 678 return true; 679 } 680 } catch (RemoteException e) { 681 Log.e(TAG, "Failed to handle send while in call", e); 682 } 683 684 return false; 685 } 686 687 /** 688 * Sets the current tab based on the intent's request type 689 * 690 * @param intent Intent that contains information about which tab should be selected 691 */ 692 private void displayFragment(Intent intent) { 693 // If we got here by hitting send and we're in call forward along to the in-call activity 694 if (isSendKeyWhileInCall(intent)) { 695 finish(); 696 return; 697 } 698 699 if (mDialpadFragment != null) { 700 final boolean phoneIsInUse = phoneIsInUse(); 701 if (phoneIsInUse || isDialIntent(intent)) { 702 mDialpadFragment.setStartedFromNewIntent(true); 703 if (phoneIsInUse && !mDialpadFragment.isVisible()) { 704 mInCallDialpadUp = true; 705 } 706 showDialpadFragment(false); 707 } 708 } 709 } 710 711 @Override 712 public void onNewIntent(Intent newIntent) { 713 setIntent(newIntent); 714 displayFragment(newIntent); 715 716 invalidateOptionsMenu(); 717 } 718 719 /** Returns true if the given intent contains a phone number to populate the dialer with */ 720 private boolean isDialIntent(Intent intent) { 721 final String action = intent.getAction(); 722 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 723 return true; 724 } 725 if (Intent.ACTION_VIEW.equals(action)) { 726 final Uri data = intent.getData(); 727 if (data != null && CallUtil.SCHEME_TEL.equals(data.getScheme())) { 728 return true; 729 } 730 } 731 return false; 732 } 733 734 /** 735 * Returns an appropriate call origin for this Activity. May return null when no call origin 736 * should be used (e.g. when some 3rd party application launched the screen. Call origin is 737 * for remembering the tab in which the user made a phone call, so the external app's DIAL 738 * request should not be counted.) 739 */ 740 public String getCallOrigin() { 741 return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null; 742 } 743 744 /** 745 * Shows the search fragment 746 */ 747 private void enterSearchUi(boolean smartDialSearch, String query) { 748 if (getFragmentManager().isDestroyed()) { 749 // Weird race condition where fragment is doing work after the activity is destroyed 750 // due to talkback being on (b/10209937). Just return since we can't do any 751 // constructive here. 752 return; 753 } 754 755 if (DEBUG) { 756 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 757 } 758 759 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 760 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 761 transaction.remove(mSmartDialSearchFragment); 762 } else if (mInRegularSearch && mRegularSearchFragment != null) { 763 transaction.remove(mRegularSearchFragment); 764 } 765 766 final String tag; 767 if (smartDialSearch) { 768 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 769 } else { 770 tag = TAG_REGULAR_SEARCH_FRAGMENT; 771 } 772 mInDialpadSearch = smartDialSearch; 773 mInRegularSearch = !smartDialSearch; 774 775 SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 776 if (fragment == null) { 777 if (smartDialSearch) { 778 fragment = new SmartDialSearchFragment(); 779 } else { 780 fragment = new RegularSearchFragment(); 781 } 782 } 783 // DialtactsActivity will provide the options menu 784 fragment.setHasOptionsMenu(false); 785 transaction.replace(R.id.dialtacts_frame, fragment, tag); 786 transaction.addToBackStack(null); 787 fragment.setQueryString(query, false /* delaySelection */); 788 transaction.commit(); 789 } 790 791 /** 792 * Hides the search fragment 793 */ 794 private void exitSearchUi() { 795 // See related bug in enterSearchUI(); 796 if (getFragmentManager().isDestroyed()) { 797 return; 798 } 799 // Go all the way back to the favorites fragment, regardless of how many times we 800 // transitioned between search fragments 801 getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE); 802 setNotInSearchUi(); 803 804 if (mIsDialpadShown) { 805 mFragmentsFrame.setAlpha(0); 806 } 807 } 808 809 /** Returns an Intent to launch Call Settings screen */ 810 public static Intent getCallSettingsIntent() { 811 final Intent intent = new Intent(Intent.ACTION_MAIN); 812 intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME); 813 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 814 return intent; 815 } 816 817 @Override 818 public void onBackPressed() { 819 if (mIsDialpadShown) { 820 hideDialpadFragment(true, false); 821 } else if (getInSearchUi()) { 822 mSearchView.setText(null); 823 mDialpadFragment.clearDialpad(); 824 exitSearchUi(); 825 } else { 826 super.onBackPressed(); 827 } 828 } 829 830 @Override 831 public void onDialpadQueryChanged(String query) { 832 if (mSmartDialSearchFragment != null) { 833 mSmartDialSearchFragment.setAddToContactNumber(query); 834 } 835 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 836 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 837 838 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 839 if (DEBUG) { 840 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 841 } 842 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 843 // This callback can happen if the dialpad fragment is recreated because of 844 // activity destruction. In that case, don't update the search view because 845 // that would bring the user back to the search fragment regardless of the 846 // previous state of the application. Instead, just return here and let the 847 // fragment manager correctly figure out whatever fragment was last displayed. 848 if (!TextUtils.isEmpty(normalizedQuery)) { 849 mPendingSearchViewQuery = normalizedQuery; 850 } 851 return; 852 } 853 mSearchView.setText(normalizedQuery); 854 } 855 } 856 857 @Override 858 public void onListFragmentScrollStateChange(int scrollState) { 859 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 860 hideDialpadFragment(true, false); 861 hideInputMethod(getCurrentFocus()); 862 } 863 } 864 865 @Override 866 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 867 int totalItemCount) { 868 // TODO: No-op for now. This should eventually show/hide the actionBar based on 869 // interactions with the ListsFragments. 870 } 871 872 @Override 873 public void setFloatingActionButtonVisible(boolean visible) { 874 mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE); 875 } 876 877 private boolean phoneIsInUse() { 878 final TelephonyManager tm = (TelephonyManager) getSystemService( 879 Context.TELEPHONY_SERVICE); 880 return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE; 881 } 882 883 public static Intent getAddNumberToContactIntent(CharSequence text) { 884 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 885 intent.putExtra(Intents.Insert.PHONE, text); 886 intent.setType(Contacts.CONTENT_ITEM_TYPE); 887 return intent; 888 } 889 890 private boolean canIntentBeHandled(Intent intent) { 891 final PackageManager packageManager = getPackageManager(); 892 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 893 PackageManager.MATCH_DEFAULT_ONLY); 894 return resolveInfo != null && resolveInfo.size() > 0; 895 } 896 897 @Override 898 public void showCallHistory() { 899 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 900 // CONTENT_TYPE, so that we always open our call log from our dialer 901 final Intent intent = new Intent(this, CallLogActivity.class); 902 startActivity(intent); 903 } 904 905 /** 906 * Called when the user has long-pressed a contact tile to start a drag operation. 907 */ 908 @Override 909 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 910 getActionBar().hide(); 911 mRemoveViewContainer.setVisibility(View.VISIBLE); 912 } 913 914 @Override 915 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 916 } 917 918 /** 919 * Called when the user has released a contact tile after long-pressing it. 920 */ 921 @Override 922 public void onDragFinished(int x, int y) { 923 getActionBar().show(); 924 mRemoveViewContainer.setVisibility(View.GONE); 925 } 926 927 @Override 928 public void onDroppedOnRemove() {} 929 930 /** 931 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 932 * once it has been attached to the activity. 933 */ 934 @Override 935 public void setDragDropController(DragDropController dragController) { 936 mDragDropController = dragController; 937 ((RemoveView) findViewById(R.id.remove_view)) 938 .setDragDropController(dragController); 939 } 940 941 @Override 942 public void onPickPhoneNumberAction(Uri dataUri) { 943 // Specify call-origin so that users will see the previous tab instead of 944 // CallLog screen (search UI will be automatically exited). 945 PhoneNumberInteraction.startInteractionForPhoneCall( 946 DialtactsActivity.this, dataUri, getCallOrigin()); 947 mClearSearchOnPause = true; 948 } 949 950 @Override 951 public void onCallNumberDirectly(String phoneNumber) { 952 Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin()); 953 startActivity(intent); 954 mClearSearchOnPause = true; 955 } 956 957 @Override 958 public void onShortcutIntentCreated(Intent intent) { 959 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 960 } 961 962 @Override 963 public void onHomeInActionBarSelected() { 964 exitSearchUi(); 965 } 966 967 public int getActionBarHeight() { 968 return mActionBarHeight; 969 } 970 971 @Override 972 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 973 974 } 975 976 @Override 977 public void onPageSelected(int position) { 978 mCurrentTabPosition = position; 979 // If the dialpad is showing, the floating action button should always be middle aligned. 980 if (!mIsDialpadShown) { 981 alignFloatingActionButtonByTab(mCurrentTabPosition); 982 } 983 } 984 985 @Override 986 public void onPageScrollStateChanged(int state) { 987 } 988 989 private void updateFloatingActionButton() { 990 if (mIsDialpadShown) { 991 mFloatingActionButton.setImageResource(R.drawable.fab_ic_call); 992 mFloatingActionButton.setContentDescription( 993 getResources().getString(R.string.description_dial_button)); 994 alignFloatingActionButtonMiddle(); 995 } else { 996 mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial); 997 mFloatingActionButton.setContentDescription( 998 getResources().getString(R.string.action_menu_dialpad_button)); 999 alignFloatingActionButtonByTab(mCurrentTabPosition); 1000 } 1001 } 1002 1003 private void alignFloatingActionButtonByTab(int position) { 1004 if (position == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1005 alignFloatingActionButtonMiddle(); 1006 } else { 1007 alignFloatingActionButtonRight(); 1008 } 1009 } 1010 1011 private void alignFloatingActionButtonRight() { 1012 final RelativeLayout.LayoutParams params = 1013 (RelativeLayout.LayoutParams) mFloatingActionButtonContainer.getLayoutParams(); 1014 params.removeRule(RelativeLayout.CENTER_HORIZONTAL); 1015 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1016 mFloatingActionButtonContainer.setLayoutParams(params); 1017 } 1018 1019 private void alignFloatingActionButtonMiddle() { 1020 final RelativeLayout.LayoutParams params = 1021 (RelativeLayout.LayoutParams) mFloatingActionButtonContainer.getLayoutParams(); 1022 params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1023 params.addRule(RelativeLayout.CENTER_HORIZONTAL); 1024 mFloatingActionButtonContainer.setLayoutParams(params); 1025 } 1026 1027 /** 1028 * Convenience class which implements AnimationListener interface as null-op methods. 1029 */ 1030 private class ActivityAnimationListener implements AnimationListener { 1031 @Override 1032 public void onAnimationStart(Animation animation) { 1033 } 1034 1035 @Override 1036 public void onAnimationEnd(Animation animation) { 1037 } 1038 1039 @Override 1040 public void onAnimationRepeat(Animation animation) { 1041 } 1042 } 1043} 1044