DialtactsActivity.java revision 4e28c1d175a1a9e565b974c85a54709f41ee925b
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.DecelerateInterpolator; 53import android.view.animation.Interpolator; 54import android.view.inputmethod.InputMethodManager; 55import android.widget.AbsListView.OnScrollListener; 56import android.widget.EditText; 57import android.widget.PopupMenu; 58import android.widget.RelativeLayout; 59import android.widget.Toast; 60 61import com.android.contacts.common.CallUtil; 62import com.android.contacts.common.activity.TransactionSafeActivity; 63import com.android.contacts.common.dialog.ClearFrequentsDialog; 64import com.android.contacts.common.interactions.ImportExportDialogFragment; 65import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 66import com.android.contacts.common.util.ViewUtil; 67import com.android.dialer.calllog.CallLogActivity; 68import com.android.dialer.database.DialerDatabaseHelper; 69import com.android.dialer.dialpad.DialpadFragment; 70import com.android.dialer.dialpad.SmartDialNameMatcher; 71import com.android.dialer.dialpad.SmartDialPrefix; 72import com.android.dialer.interactions.PhoneNumberInteraction; 73import com.android.dialer.list.DragDropController; 74import com.android.dialer.list.ListsFragment; 75import com.android.dialer.list.OnDragDropListener; 76import com.android.dialer.list.OnListFragmentScrolledListener; 77import com.android.dialer.list.SpeedDialFragment; 78import com.android.dialer.list.PhoneFavoriteSquareTileView; 79import com.android.dialer.list.RegularSearchFragment; 80import com.android.dialer.list.RemoveView; 81import com.android.dialer.list.SearchFragment; 82import com.android.dialer.list.SmartDialSearchFragment; 83import com.android.dialerbind.DatabaseHelperManager; 84import com.android.internal.telephony.ITelephony; 85 86import java.util.ArrayList; 87import java.util.List; 88 89/** 90 * The dialer tab's title is 'phone', a more common name (see strings.xml). 91 */ 92public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, 93 DialpadFragment.OnDialpadQueryChangedListener, 94 OnListFragmentScrolledListener, 95 DialpadFragment.HostInterface, 96 ListsFragment.HostInterface, 97 SpeedDialFragment.HostInterface, 98 OnDragDropListener, View.OnLongClickListener, 99 OnPhoneNumberPickerActionListener, 100 ViewPager.OnPageChangeListener { 101 private static final String TAG = "DialtactsActivity"; 102 103 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 104 105 public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; 106 107 /** Used to open Call Setting */ 108 private static final String PHONE_PACKAGE = "com.android.phone"; 109 private static final String CALL_SETTINGS_CLASS_NAME = 110 "com.android.phone.CallFeaturesSetting"; 111 /** @see #getCallOrigin() */ 112 private static final String CALL_ORIGIN_DIALTACTS = 113 "com.android.dialer.DialtactsActivity"; 114 115 private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; 116 private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; 117 private static final String KEY_SEARCH_QUERY = "search_query"; 118 private static final String KEY_FIRST_LAUNCH = "first_launch"; 119 120 private static final String TAG_DIALPAD_FRAGMENT = "dialpad"; 121 private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; 122 private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; 123 private static final String TAG_FAVORITES_FRAGMENT = "favorites"; 124 125 /** 126 * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. 127 */ 128 private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; 129 130 private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; 131 132 private static final int ANIMATION_DURATION = 250; 133 134 private RelativeLayout parentLayout; 135 136 /** 137 * Fragment containing the dialpad that slides into view 138 */ 139 private DialpadFragment mDialpadFragment; 140 141 /** 142 * Fragment for searching phone numbers using the alphanumeric keyboard. 143 */ 144 private RegularSearchFragment mRegularSearchFragment; 145 146 /** 147 * Fragment for searching phone numbers using the dialpad. 148 */ 149 private SmartDialSearchFragment mSmartDialSearchFragment; 150 151 /** 152 * Fragment containing the speed dial list, recents list, and all contacts list. 153 */ 154 private ListsFragment mListsFragment; 155 156 private View mFloatingActionButton; 157 private View mDialpadButton; 158 private View mDialButton; 159 160 private View mFragmentsFrame; 161 162 private int mActionBarHeight; 163 private boolean mInDialpadSearch; 164 private boolean mInRegularSearch; 165 private boolean mClearSearchOnPause; 166 167 /** 168 * The position of the currently selected tab in the attached {@link ListsFragment}. 169 */ 170 private int mCurrentTabPosition = 0; 171 172 /** 173 * True if the dialpad is only temporarily showing due to being in call 174 */ 175 private boolean mInCallDialpadUp; 176 177 /** 178 * True when this activity has been launched for the first time. 179 */ 180 private boolean mFirstLaunch; 181 182 /** 183 * Search query to be applied to the SearchView in the ActionBar once 184 * onCreateOptionsMenu has been called. 185 */ 186 private String mPendingSearchViewQuery; 187 188 private EditText mSearchView; 189 private View mSearchViewCloseButton; 190 private View mVoiceSearchButton; 191 /** 192 * View that contains the "Remove" dialog that shows up when the user long presses a contact. 193 * If the user releases a contact when hovering on top of this, the contact is unfavorited and 194 * removed from the speed dial list. 195 */ 196 private View mRemoveViewContainer; 197 198 final Interpolator hideActionBarInterpolator = new AccelerateInterpolator(1.5f); 199 final Interpolator showActionBarInterpolator = new DecelerateInterpolator(1.5f); 200 private String mSearchQuery; 201 202 private DialerDatabaseHelper mDialerDatabaseHelper; 203 private DragDropController mDragDropController; 204 205 private int mDialerBackgroundColor; 206 private int mContactListBackgroundColor; 207 208 private class OverflowPopupMenu extends PopupMenu { 209 public OverflowPopupMenu(Context context, View anchor) { 210 super(context, anchor); 211 } 212 213 @Override 214 public void show() { 215 final Menu menu = getMenu(); 216 final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); 217 // TODO: Check mSpeedDialFragment.hasFrequents() 218 clearFrequents.setVisible(true); 219 super.show(); 220 } 221 } 222 223 /** 224 * Listener that listens to drag events and sends their x and y coordinates to a 225 * {@link DragDropController}. 226 */ 227 private class LayoutOnDragListener implements OnDragListener { 228 @Override 229 public boolean onDrag(View v, DragEvent event) { 230 if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { 231 mDragDropController.handleDragHovered(v, (int) event.getX(), 232 (int) event.getY()); 233 } 234 return true; 235 } 236 } 237 238 /** 239 * Listener used to send search queries to the phone search fragment. 240 */ 241 private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { 242 @Override 243 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 244 } 245 246 @Override 247 public void onTextChanged(CharSequence s, int start, int before, int count) { 248 final String newText = s.toString(); 249 if (newText.equals(mSearchQuery)) { 250 // If the query hasn't changed (perhaps due to activity being destroyed 251 // and restored, or user launching the same DIAL intent twice), then there is 252 // no need to do anything here. 253 return; 254 } 255 mSearchQuery = newText; 256 if (DEBUG) { 257 Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText); 258 } 259 final boolean dialpadSearch = isDialpadShowing(); 260 261 // Show search result with non-empty text. Show a bare list otherwise. 262 if (TextUtils.isEmpty(newText) && getInSearchUi()) { 263 exitSearchUi(); 264 mSearchViewCloseButton.setVisibility(View.GONE); 265 mVoiceSearchButton.setVisibility(View.VISIBLE); 266 return; 267 } else if (!TextUtils.isEmpty(newText)) { 268 final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) || 269 (!dialpadSearch && mInRegularSearch); 270 if (!sameSearchMode) { 271 // call enterSearchUi only if we are switching search modes, or entering 272 // search ui for the first time 273 enterSearchUi(dialpadSearch, newText); 274 } 275 276 if (dialpadSearch && mSmartDialSearchFragment != null) { 277 mSmartDialSearchFragment.setQueryString(newText, false); 278 } else if (mRegularSearchFragment != null) { 279 mRegularSearchFragment.setQueryString(newText, false); 280 } 281 mSearchViewCloseButton.setVisibility(View.VISIBLE); 282 mVoiceSearchButton.setVisibility(View.GONE); 283 return; 284 } 285 return; 286 } 287 288 @Override 289 public void afterTextChanged(Editable s) { 290 } 291 }; 292 293 private boolean isDialpadShowing() { 294 return mDialpadFragment != null && mDialpadFragment.isVisible(); 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 mFloatingActionButton = findViewById(R.id.floating_action_button); 347 ViewUtil.setupFloatingActionButton(mFloatingActionButton, getResources()); 348 349 mDialButton = findViewById(R.id.dial_button); 350 mDialButton.setOnClickListener(this); 351 mDialpadButton = findViewById(R.id.dialpad_button); 352 mDialpadButton.setOnClickListener(this); 353 354 mRemoveViewContainer = findViewById(R.id.remove_view_container); 355 356 mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); 357 SmartDialPrefix.initializeNanpSettings(this); 358 } 359 360 @Override 361 protected void onResume() { 362 super.onResume(); 363 if (mFirstLaunch) { 364 displayFragment(getIntent()); 365 } else if (!phoneIsInUse() && mInCallDialpadUp) { 366 hideDialpadFragment(false, true); 367 mInCallDialpadUp = false; 368 } 369 mFirstLaunch = false; 370 prepareVoiceSearchButton(); 371 mDialerDatabaseHelper.startSmartDialUpdateThread(); 372 } 373 374 @Override 375 protected void onPause() { 376 if (mClearSearchOnPause) { 377 hideDialpadAndSearchUi(); 378 mClearSearchOnPause = false; 379 } 380 super.onPause(); 381 } 382 383 @Override 384 protected void onSaveInstanceState(Bundle outState) { 385 super.onSaveInstanceState(outState); 386 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 387 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 388 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 389 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 390 } 391 392 @Override 393 public void onAttachFragment(Fragment fragment) { 394 if (fragment instanceof DialpadFragment) { 395 mDialpadFragment = (DialpadFragment) fragment; 396 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 397 transaction.hide(mDialpadFragment); 398 transaction.commit(); 399 } else if (fragment instanceof SmartDialSearchFragment) { 400 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 401 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); 402 if (mFragmentsFrame != null) { 403 mFragmentsFrame.setAlpha(1.0f); 404 } 405 } else if (fragment instanceof SearchFragment) { 406 mRegularSearchFragment = (RegularSearchFragment) fragment; 407 mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this); 408 if (mFragmentsFrame != null) { 409 mFragmentsFrame.setAlpha(1.0f); 410 } 411 } else if (fragment instanceof ListsFragment) { 412 mListsFragment = (ListsFragment) fragment; 413 mListsFragment.addOnPageChangeListener(this); 414 } 415 } 416 417 protected void handleMenuSettings() { 418 openTelephonySetting(this); 419 } 420 421 public static void openTelephonySetting(Activity activity) { 422 final Intent settingsIntent = getCallSettingsIntent(); 423 activity.startActivity(settingsIntent); 424 } 425 426 @Override 427 public void onClick(View view) { 428 switch (view.getId()) { 429 case R.id.dialpad_button: 430 // Reset the boolean flag that tracks whether the dialpad was up because 431 // we were in call. Regardless of whether it was true before, we want to 432 // show the dialpad because the user has explicitly clicked the dialpad 433 // button. 434 mInCallDialpadUp = false; 435 showDialpadFragment(true); 436 break; 437 case R.id.dial_button: 438 // Dial button was pressed; tell the Dialpad fragment 439 mDialpadFragment.dialButtonPressed(); 440 break; 441 case R.id.search_close_button: 442 // Clear the search field 443 if (!TextUtils.isEmpty(mSearchView.getText())) { 444 mDialpadFragment.clearDialpad(); 445 mSearchView.setText(null); 446 } 447 break; 448 case R.id.voice_search_button: 449 try { 450 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 451 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 452 } catch (ActivityNotFoundException e) { 453 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 454 Toast.LENGTH_SHORT).show(); 455 } 456 break; 457 default: { 458 Log.wtf(TAG, "Unexpected onClick event from " + view); 459 break; 460 } 461 } 462 } 463 464 @Override 465 public boolean onOptionsItemSelected(MenuItem item) { 466 switch (item.getItemId()) { 467 case R.id.menu_history: 468 showCallHistory(); 469 break; 470 case R.id.menu_add_contact: 471 try { 472 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 473 } catch (ActivityNotFoundException e) { 474 Toast toast = Toast.makeText(this, 475 R.string.add_contact_not_available, 476 Toast.LENGTH_SHORT); 477 toast.show(); 478 } 479 break; 480 case R.id.menu_import_export: 481 // We hard-code the "contactsAreAvailable" argument because doing it properly would 482 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 483 // now in Dialtacts for (potential) performance reasons. Compare with how it is 484 // done in {@link PeopleActivity}. 485 ImportExportDialogFragment.show(getFragmentManager(), true, 486 DialtactsActivity.class); 487 return true; 488 case R.id.menu_clear_frequents: 489 // TODO: This should be enabled/disabled based on 490 // SpeedDialFragment.hasFrequents 491 ClearFrequentsDialog.show(getFragmentManager()); 492 return true; 493 case R.id.menu_call_settings: 494 handleMenuSettings(); 495 return true; 496 } 497 return false; 498 } 499 500 @Override 501 public boolean onLongClick(View view) { 502 switch (view.getId()) { 503 case R.id.dial_button: { 504 // Dial button was pressed; tell the Dialpad fragment 505 mDialpadFragment.dialButtonPressed(); 506 return true; // Consume the event 507 } 508 default: { 509 Log.wtf(TAG, "Unexpected onClick event from " + view); 510 break; 511 } 512 } 513 return false; 514 } 515 516 @Override 517 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 518 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 519 if (resultCode == RESULT_OK) { 520 final ArrayList<String> matches = data.getStringArrayListExtra( 521 RecognizerIntent.EXTRA_RESULTS); 522 if (matches.size() > 0) { 523 final String match = matches.get(0); 524 mSearchView.setText(match); 525 } else { 526 Log.e(TAG, "Voice search - nothing heard"); 527 } 528 } else { 529 Log.e(TAG, "Voice search failed"); 530 } 531 } 532 super.onActivityResult(requestCode, resultCode, data); 533 } 534 535 private void showDialpadFragment(boolean animate) { 536 mDialpadFragment.setAnimate(animate); 537 538 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 539 if (animate) { 540 ft.setCustomAnimations(R.anim.slide_in, 0); 541 } else { 542 mDialpadFragment.setYFraction(0); 543 } 544 ft.show(mDialpadFragment); 545 ft.commit(); 546 } 547 548 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 549 if (mDialpadFragment == null) return; 550 if (clearDialpad) { 551 mDialpadFragment.clearDialpad(); 552 } 553 if (!mDialpadFragment.isVisible()) return; 554 mDialpadFragment.setAnimate(animate); 555 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 556 if (animate) { 557 ft.setCustomAnimations(0, R.anim.slide_out); 558 } 559 ft.hide(mDialpadFragment); 560 ft.commit(); 561 } 562 563 private boolean getInSearchUi() { 564 return mInDialpadSearch || mInRegularSearch; 565 } 566 567 private void setNotInSearchUi() { 568 mInDialpadSearch = false; 569 mInRegularSearch = false; 570 } 571 572 private void hideDialpadAndSearchUi() { 573 mSearchView.setText(null); 574 hideDialpadFragment(false, true); 575 } 576 577 /** 578 * Callback from child DialpadFragment when the dialpad is shown. 579 */ 580 public void onDialpadShown() { 581 mDialButton.setVisibility(View.VISIBLE); 582 mDialpadButton.setVisibility(View.GONE); 583 584 SearchFragment fragment = null; 585 if (mInDialpadSearch) { 586 fragment = mSmartDialSearchFragment; 587 } else if (mInRegularSearch) { 588 fragment = mRegularSearchFragment; 589 } 590 if (fragment != null && fragment.isVisible()) { 591 fragment.getView().animate().translationY(-mActionBarHeight) 592 .setInterpolator(hideActionBarInterpolator).setDuration(ANIMATION_DURATION); 593 } 594 595 if (mListsFragment != null && mListsFragment.isResumed() && mListsFragment.isVisible()) { 596 // If the favorites fragment is showing, fade to blank. 597 mFragmentsFrame.animate().alpha(0.0f); 598 parentLayout.setBackgroundColor(mContactListBackgroundColor); 599 } 600 getActionBar().hide(); 601 alignFloatingActionButtonMiddle(); 602 } 603 604 /** 605 * Callback from child DialpadFragment when the dialpad is hidden. 606 */ 607 public void onDialpadHidden() { 608 mDialButton.setVisibility(View.GONE); 609 mDialpadButton.setVisibility(View.VISIBLE); 610 611 SearchFragment fragment = null; 612 if (mInDialpadSearch) { 613 fragment = mSmartDialSearchFragment; 614 } else if (mInRegularSearch) { 615 fragment = mRegularSearchFragment; 616 } 617 if (fragment != null && fragment.isVisible()) { 618 fragment.getView().animate().translationY(0) 619 .setInterpolator(showActionBarInterpolator).setDuration(ANIMATION_DURATION); 620 } 621 622 if (mListsFragment != null && mListsFragment.isVisible()) { 623 mFragmentsFrame.animate().alpha(1.0f); 624 parentLayout.setBackgroundColor(mDialerBackgroundColor); 625 626 } 627 getActionBar().show(); 628 alignFloatingActionButtonByTab(mCurrentTabPosition); 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 763 SearchFragment fragment; 764 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 765 transaction.remove(mSmartDialSearchFragment); 766 } else if (mInRegularSearch && mRegularSearchFragment != null) { 767 transaction.remove(mRegularSearchFragment); 768 } 769 770 final String tag; 771 if (smartDialSearch) { 772 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 773 } else { 774 tag = TAG_REGULAR_SEARCH_FRAGMENT; 775 } 776 mInDialpadSearch = smartDialSearch; 777 mInRegularSearch = !smartDialSearch; 778 779 fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 780 if (fragment == null) { 781 if (smartDialSearch) { 782 fragment = new SmartDialSearchFragment(); 783 } else { 784 fragment = new RegularSearchFragment(); 785 } 786 } 787 // DialtactsActivity will provide the options menu 788 fragment.setHasOptionsMenu(false); 789 transaction.replace(R.id.dialtacts_frame, fragment, tag); 790 transaction.addToBackStack(null); 791 fragment.setQueryString(query, false); 792 transaction.commit(); 793 } 794 795 /** 796 * Hides the search fragment 797 */ 798 private void exitSearchUi() { 799 // See related bug in enterSearchUI(); 800 if (getFragmentManager().isDestroyed()) { 801 return; 802 } 803 // Go all the way back to the favorites fragment, regardless of how many times we 804 // transitioned between search fragments 805 getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE); 806 setNotInSearchUi(); 807 808 if (isDialpadShowing()) { 809 mFragmentsFrame.setAlpha(0); 810 } 811 } 812 813 /** Returns an Intent to launch Call Settings screen */ 814 public static Intent getCallSettingsIntent() { 815 final Intent intent = new Intent(Intent.ACTION_MAIN); 816 intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME); 817 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 818 return intent; 819 } 820 821 @Override 822 public void onBackPressed() { 823 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 824 hideDialpadFragment(true, false); 825 } else if (getInSearchUi()) { 826 mSearchView.setText(null); 827 mDialpadFragment.clearDialpad(); 828 } else { 829 super.onBackPressed(); 830 } 831 } 832 833 @Override 834 public void onDialpadQueryChanged(String query) { 835 if (mSmartDialSearchFragment != null) { 836 mSmartDialSearchFragment.setAddToContactNumber(query); 837 } 838 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 839 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 840 841 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 842 if (DEBUG) { 843 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 844 } 845 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 846 // This callback can happen if the dialpad fragment is recreated because of 847 // activity destruction. In that case, don't update the search view because 848 // that would bring the user back to the search fragment regardless of the 849 // previous state of the application. Instead, just return here and let the 850 // fragment manager correctly figure out whatever fragment was last displayed. 851 if (!TextUtils.isEmpty(normalizedQuery)) { 852 mPendingSearchViewQuery = normalizedQuery; 853 } 854 return; 855 } 856 mSearchView.setText(normalizedQuery); 857 } 858 } 859 860 @Override 861 public void onListFragmentScrollStateChange(int scrollState) { 862 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 863 hideDialpadFragment(true, false); 864 hideInputMethod(getCurrentFocus()); 865 } 866 } 867 868 @Override 869 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 870 int totalItemCount) { 871 // TODO: No-op for now. This should eventually show/hide the actionBar based on 872 // interactions with the ListsFragments. 873 } 874 875 @Override 876 public void setDialButtonEnabled(boolean enabled) { 877 if (mDialButton != null) { 878 mDialButton.setEnabled(enabled); 879 } 880 } 881 882 @Override 883 public void setDialButtonContainerVisible(boolean visible) { 884 mFloatingActionButton.setVisibility(visible ? View.VISIBLE : View.GONE); 885 } 886 887 private boolean phoneIsInUse() { 888 final TelephonyManager tm = (TelephonyManager) getSystemService( 889 Context.TELEPHONY_SERVICE); 890 return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE; 891 } 892 893 public static Intent getAddNumberToContactIntent(CharSequence text) { 894 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 895 intent.putExtra(Intents.Insert.PHONE, text); 896 intent.setType(Contacts.CONTENT_ITEM_TYPE); 897 return intent; 898 } 899 900 private boolean canIntentBeHandled(Intent intent) { 901 final PackageManager packageManager = getPackageManager(); 902 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 903 PackageManager.MATCH_DEFAULT_ONLY); 904 return resolveInfo != null && resolveInfo.size() > 0; 905 } 906 907 @Override 908 public void showCallHistory() { 909 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 910 // CONTENT_TYPE, so that we always open our call log from our dialer 911 final Intent intent = new Intent(this, CallLogActivity.class); 912 startActivity(intent); 913 } 914 915 /** 916 * Called when the user has long-pressed a contact tile to start a drag operation. 917 */ 918 @Override 919 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 920 getActionBar().hide(); 921 mRemoveViewContainer.setVisibility(View.VISIBLE); 922 } 923 924 @Override 925 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 926 } 927 928 /** 929 * Called when the user has released a contact tile after long-pressing it. 930 */ 931 @Override 932 public void onDragFinished(int x, int y) { 933 getActionBar().show(); 934 mRemoveViewContainer.setVisibility(View.GONE); 935 } 936 937 @Override 938 public void onDroppedOnRemove() {} 939 940 /** 941 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 942 * once it has been attached to the activity. 943 */ 944 @Override 945 public void setDragDropController(DragDropController dragController) { 946 mDragDropController = dragController; 947 ((RemoveView) findViewById(R.id.remove_view)) 948 .setDragDropController(dragController); 949 } 950 951 @Override 952 public void onPickPhoneNumberAction(Uri dataUri) { 953 // Specify call-origin so that users will see the previous tab instead of 954 // CallLog screen (search UI will be automatically exited). 955 PhoneNumberInteraction.startInteractionForPhoneCall( 956 DialtactsActivity.this, dataUri, getCallOrigin()); 957 mClearSearchOnPause = true; 958 } 959 960 @Override 961 public void onCallNumberDirectly(String phoneNumber) { 962 Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin()); 963 startActivity(intent); 964 mClearSearchOnPause = true; 965 } 966 967 @Override 968 public void onShortcutIntentCreated(Intent intent) { 969 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 970 } 971 972 @Override 973 public void onHomeInActionBarSelected() { 974 exitSearchUi(); 975 } 976 977 public int getActionBarHeight() { 978 return mActionBarHeight; 979 } 980 981 @Override 982 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 983 984 } 985 986 @Override 987 public void onPageSelected(int position) { 988 mCurrentTabPosition = position; 989 // If the dialpad is showing, the floating action button should always be middle aligned. 990 if (!isDialpadShowing()) { 991 alignFloatingActionButtonByTab(mCurrentTabPosition); 992 } 993 } 994 995 @Override 996 public void onPageScrollStateChanged(int state) { 997 } 998 999 private void alignFloatingActionButtonByTab(int position) { 1000 if (position == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1001 alignFloatingActionButtonMiddle(); 1002 } else { 1003 alignFloatingActionButtonRight(); 1004 } 1005 } 1006 1007 private void alignFloatingActionButtonRight() { 1008 final RelativeLayout.LayoutParams params = 1009 (RelativeLayout.LayoutParams) mFloatingActionButton.getLayoutParams(); 1010 params.removeRule(RelativeLayout.CENTER_HORIZONTAL); 1011 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1012 mFloatingActionButton.setLayoutParams(params); 1013 } 1014 1015 private void alignFloatingActionButtonMiddle() { 1016 final RelativeLayout.LayoutParams params = 1017 (RelativeLayout.LayoutParams) mFloatingActionButton.getLayoutParams(); 1018 params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1019 params.addRule(RelativeLayout.CENTER_HORIZONTAL); 1020 mFloatingActionButton.setLayoutParams(params); 1021 } 1022} 1023