DialtactsActivity.java revision 49aa5bf33be8b96d6409168cddbb819bc7539fab
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.app.Fragment; 20import android.app.FragmentTransaction; 21import android.content.ActivityNotFoundException; 22import android.content.Context; 23import android.content.Intent; 24import android.content.pm.PackageManager; 25import android.content.pm.ResolveInfo; 26import android.content.res.Configuration; 27import android.content.res.Resources; 28import android.net.Uri; 29import android.os.Bundle; 30import android.os.Trace; 31import android.provider.CallLog.Calls; 32import android.speech.RecognizerIntent; 33import android.support.v4.view.ViewPager; 34import android.support.v7.app.ActionBar; 35import android.telecom.PhoneAccount; 36import android.telecom.TelecomManager; 37import android.text.Editable; 38import android.text.TextUtils; 39import android.text.TextWatcher; 40import android.util.Log; 41import android.view.DragEvent; 42import android.view.Gravity; 43import android.view.KeyEvent; 44import android.view.Menu; 45import android.view.MenuItem; 46import android.view.MotionEvent; 47import android.view.View; 48import android.view.View.OnDragListener; 49import android.view.View.OnTouchListener; 50import android.view.ViewTreeObserver; 51import android.view.animation.Animation; 52import android.view.animation.AnimationUtils; 53import android.widget.AbsListView.OnScrollListener; 54import android.widget.EditText; 55import android.widget.FrameLayout; 56import android.widget.ImageButton; 57import android.widget.PopupMenu; 58import android.widget.TextView; 59import android.widget.Toast; 60 61import com.android.contacts.common.dialog.ClearFrequentsDialog; 62import com.android.contacts.common.interactions.ImportExportDialogFragment; 63import com.android.contacts.common.interactions.TouchPointManager; 64import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 65import com.android.contacts.common.util.PermissionsUtil; 66import com.android.contacts.common.widget.FloatingActionButtonController; 67import com.android.contacts.commonbind.analytics.AnalyticsUtil; 68import com.android.dialer.calllog.CallLogActivity; 69import com.android.dialer.calllog.CallLogFragment; 70import com.android.dialer.database.DialerDatabaseHelper; 71import com.android.dialer.dialpad.DialpadFragment; 72import com.android.dialer.dialpad.SmartDialNameMatcher; 73import com.android.dialer.dialpad.SmartDialPrefix; 74import com.android.dialer.interactions.PhoneNumberInteraction; 75import com.android.dialer.list.DragDropController; 76import com.android.dialer.list.ListsFragment; 77import com.android.dialer.list.OnDragDropListener; 78import com.android.dialer.list.OnListFragmentScrolledListener; 79import com.android.dialer.list.PhoneFavoriteSquareTileView; 80import com.android.dialer.list.RegularSearchFragment; 81import com.android.dialer.list.SearchFragment; 82import com.android.dialer.list.SmartDialSearchFragment; 83import com.android.dialer.list.SpeedDialFragment; 84import com.android.dialer.settings.DialerSettingsActivity; 85import com.android.dialer.util.IntentUtil; 86import com.android.dialer.util.DialerUtils; 87import com.android.dialer.widget.ActionBarController; 88import com.android.dialer.widget.SearchEditTextLayout; 89import com.android.dialer.widget.SearchEditTextLayout.Callback; 90import com.android.dialerbind.DatabaseHelperManager; 91import com.android.phone.common.animation.AnimUtils; 92import com.android.phone.common.animation.AnimationListenerAdapter; 93 94import junit.framework.Assert; 95 96import java.util.ArrayList; 97import java.util.List; 98 99/** 100 * The dialer tab's title is 'phone', a more common name (see strings.xml). 101 */ 102public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, 103 DialpadFragment.OnDialpadQueryChangedListener, 104 OnListFragmentScrolledListener, 105 CallLogFragment.HostInterface, 106 DialpadFragment.HostInterface, 107 ListsFragment.HostInterface, 108 SpeedDialFragment.HostInterface, 109 SearchFragment.HostInterface, 110 OnDragDropListener, 111 OnPhoneNumberPickerActionListener, 112 PopupMenu.OnMenuItemClickListener, 113 ViewPager.OnPageChangeListener, 114 ActionBarController.ActivityUi { 115 private static final String TAG = "DialtactsActivity"; 116 117 public static final boolean DEBUG = false; 118 119 public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; 120 121 /** @see #getCallOrigin() */ 122 private static final String CALL_ORIGIN_DIALTACTS = 123 "com.android.dialer.DialtactsActivity"; 124 125 private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; 126 private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; 127 private static final String KEY_SEARCH_QUERY = "search_query"; 128 private static final String KEY_FIRST_LAUNCH = "first_launch"; 129 private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown"; 130 131 private static final String TAG_DIALPAD_FRAGMENT = "dialpad"; 132 private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; 133 private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; 134 private static final String TAG_FAVORITES_FRAGMENT = "favorites"; 135 136 /** 137 * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. 138 */ 139 private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; 140 public static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB"; 141 142 private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; 143 144 private static final int FAB_SCALE_IN_DELAY_MS = 300; 145 146 private FrameLayout mParentLayout; 147 148 /** 149 * Fragment containing the dialpad that slides into view 150 */ 151 protected DialpadFragment mDialpadFragment; 152 153 /** 154 * Fragment for searching phone numbers using the alphanumeric keyboard. 155 */ 156 private RegularSearchFragment mRegularSearchFragment; 157 158 /** 159 * Fragment for searching phone numbers using the dialpad. 160 */ 161 private SmartDialSearchFragment mSmartDialSearchFragment; 162 163 /** 164 * Animation that slides in. 165 */ 166 private Animation mSlideIn; 167 168 /** 169 * Animation that slides out. 170 */ 171 private Animation mSlideOut; 172 173 AnimationListenerAdapter mSlideInListener = new AnimationListenerAdapter() { 174 @Override 175 public void onAnimationEnd(Animation animation) { 176 maybeEnterSearchUi(); 177 } 178 }; 179 180 /** 181 * Listener for after slide out animation completes on dialer fragment. 182 */ 183 AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() { 184 @Override 185 public void onAnimationEnd(Animation animation) { 186 commitDialpadFragmentHide(); 187 } 188 }; 189 190 /** 191 * Fragment containing the speed dial list, call history list, and all contacts list. 192 */ 193 private ListsFragment mListsFragment; 194 195 /** 196 * Tracks whether onSaveInstanceState has been called. If true, no fragment transactions can 197 * be commited. 198 */ 199 private boolean mStateSaved; 200 private boolean mIsRestarting; 201 private boolean mInDialpadSearch; 202 private boolean mInRegularSearch; 203 private boolean mClearSearchOnPause; 204 private boolean mIsDialpadShown; 205 private boolean mShowDialpadOnResume; 206 207 /** 208 * Whether or not the device is in landscape orientation. 209 */ 210 private boolean mIsLandscape; 211 212 /** 213 * True if the dialpad is only temporarily showing due to being in call 214 */ 215 private boolean mInCallDialpadUp; 216 217 /** 218 * True when this activity has been launched for the first time. 219 */ 220 private boolean mFirstLaunch; 221 222 /** 223 * Search query to be applied to the SearchView in the ActionBar once 224 * onCreateOptionsMenu has been called. 225 */ 226 private String mPendingSearchViewQuery; 227 228 private PopupMenu mOverflowMenu; 229 private EditText mSearchView; 230 private View mVoiceSearchButton; 231 232 private String mSearchQuery; 233 234 private DialerDatabaseHelper mDialerDatabaseHelper; 235 private DragDropController mDragDropController; 236 private ActionBarController mActionBarController; 237 238 private FloatingActionButtonController mFloatingActionButtonController; 239 240 private int mActionBarHeight; 241 242 /** 243 * The text returned from a voice search query. Set in {@link #onActivityResult} and used in 244 * {@link #onResume()} to populate the search box. 245 */ 246 private String mVoiceSearchQuery; 247 248 protected class OptionsPopupMenu extends PopupMenu { 249 public OptionsPopupMenu(Context context, View anchor) { 250 super(context, anchor, Gravity.END); 251 } 252 253 @Override 254 public void show() { 255 final boolean hasContactsPermission = 256 PermissionsUtil.hasContactsPermissions(DialtactsActivity.this); 257 final Menu menu = getMenu(); 258 final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); 259 clearFrequents.setVisible(mListsFragment != null && 260 mListsFragment.getSpeedDialFragment() != null && 261 mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission); 262 263 menu.findItem(R.id.menu_import_export).setVisible(hasContactsPermission); 264 menu.findItem(R.id.menu_add_contact).setVisible(hasContactsPermission); 265 266 menu.findItem(R.id.menu_history).setVisible( 267 PermissionsUtil.hasPhonePermissions(DialtactsActivity.this)); 268 super.show(); 269 } 270 } 271 272 /** 273 * Listener that listens to drag events and sends their x and y coordinates to a 274 * {@link DragDropController}. 275 */ 276 private class LayoutOnDragListener implements OnDragListener { 277 @Override 278 public boolean onDrag(View v, DragEvent event) { 279 if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { 280 mDragDropController.handleDragHovered(v, (int) event.getX(), (int) event.getY()); 281 } 282 return true; 283 } 284 } 285 286 /** 287 * Listener used to send search queries to the phone search fragment. 288 */ 289 private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { 290 @Override 291 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 292 } 293 294 @Override 295 public void onTextChanged(CharSequence s, int start, int before, int count) { 296 final String newText = s.toString(); 297 if (newText.equals(mSearchQuery)) { 298 // If the query hasn't changed (perhaps due to activity being destroyed 299 // and restored, or user launching the same DIAL intent twice), then there is 300 // no need to do anything here. 301 return; 302 } 303 if (DEBUG) { 304 Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText); 305 Log.d(TAG, "Previous Query: " + mSearchQuery); 306 } 307 mSearchQuery = newText; 308 309 // Show search fragment only when the query string is changed to non-empty text. 310 if (!TextUtils.isEmpty(newText)) { 311 // Call enterSearchUi only if we are switching search modes, or showing a search 312 // fragment for the first time. 313 final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) || 314 (!mIsDialpadShown && mInRegularSearch); 315 if (!sameSearchMode) { 316 enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */); 317 } 318 } 319 320 if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { 321 mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */); 322 } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { 323 mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */); 324 } 325 } 326 327 @Override 328 public void afterTextChanged(Editable s) { 329 } 330 }; 331 332 333 /** 334 * Open the search UI when the user clicks on the search box. 335 */ 336 private final View.OnClickListener mSearchViewOnClickListener = new View.OnClickListener() { 337 @Override 338 public void onClick(View v) { 339 if (!isInSearchUi()) { 340 mActionBarController.onSearchBoxTapped(); 341 enterSearchUi(false /* smartDialSearch */, mSearchView.getText().toString(), 342 true /* animate */); 343 } 344 } 345 }; 346 347 /** 348 * Handles the user closing the soft keyboard. 349 */ 350 private final View.OnKeyListener mSearchEditTextLayoutListener = new View.OnKeyListener() { 351 @Override 352 public boolean onKey(View v, int keyCode, KeyEvent event) { 353 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { 354 if (TextUtils.isEmpty(mSearchView.getText().toString())) { 355 // If the search term is empty, close the search UI. 356 maybeExitSearchUi(); 357 } else { 358 // If the search term is not empty, show the dialpad fab. 359 showFabInSearchUi(); 360 } 361 } 362 return false; 363 } 364 }; 365 366 @Override 367 public boolean dispatchTouchEvent(MotionEvent ev) { 368 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 369 TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY()); 370 } 371 return super.dispatchTouchEvent(ev); 372 373 } 374 375 @Override 376 protected void onCreate(Bundle savedInstanceState) { 377 Trace.beginSection(TAG + " onCreate"); 378 super.onCreate(savedInstanceState); 379 380 mFirstLaunch = true; 381 382 final Resources resources = getResources(); 383 mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height_large); 384 385 Trace.beginSection(TAG + " setContentView"); 386 setContentView(R.layout.dialtacts_activity); 387 Trace.endSection(); 388 getWindow().setBackgroundDrawable(null); 389 390 Trace.beginSection(TAG + " setup Views"); 391 final ActionBar actionBar = getSupportActionBar(); 392 actionBar.setCustomView(R.layout.search_edittext); 393 actionBar.setDisplayShowCustomEnabled(true); 394 actionBar.setBackgroundDrawable(null); 395 396 SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar 397 .getCustomView().findViewById(R.id.search_view_container); 398 searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener); 399 400 mActionBarController = new ActionBarController(this, searchEditTextLayout); 401 402 mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view); 403 mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); 404 mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button); 405 searchEditTextLayout.findViewById(R.id.search_magnifying_glass) 406 .setOnClickListener(mSearchViewOnClickListener); 407 searchEditTextLayout.findViewById(R.id.search_box_start_search) 408 .setOnClickListener(mSearchViewOnClickListener); 409 searchEditTextLayout.setOnClickListener(mSearchViewOnClickListener); 410 searchEditTextLayout.setCallback(new SearchEditTextLayout.Callback() { 411 @Override 412 public void onBackButtonClicked() { 413 onBackPressed(); 414 } 415 416 @Override 417 public void onSearchViewClicked() { 418 // Hide FAB, as the keyboard is shown. 419 mFloatingActionButtonController.scaleOut(); 420 } 421 }); 422 423 mIsLandscape = getResources().getConfiguration().orientation 424 == Configuration.ORIENTATION_LANDSCAPE; 425 426 final View floatingActionButtonContainer = findViewById( 427 R.id.floating_action_button_container); 428 ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button); 429 floatingActionButton.setOnClickListener(this); 430 mFloatingActionButtonController = new FloatingActionButtonController(this, 431 floatingActionButtonContainer, floatingActionButton); 432 433 ImageButton optionsMenuButton = 434 (ImageButton) searchEditTextLayout.findViewById(R.id.dialtacts_options_menu_button); 435 optionsMenuButton.setOnClickListener(this); 436 mOverflowMenu = buildOptionsMenu(searchEditTextLayout); 437 optionsMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener()); 438 439 // Add the favorites fragment but only if savedInstanceState is null. Otherwise the 440 // fragment manager is responsible for recreating it. 441 if (savedInstanceState == null) { 442 getFragmentManager().beginTransaction() 443 .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT) 444 .commit(); 445 } else { 446 mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); 447 mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); 448 mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); 449 mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); 450 mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN); 451 mActionBarController.restoreInstanceState(savedInstanceState); 452 } 453 454 final boolean isLayoutRtl = DialerUtils.isRtl(); 455 if (mIsLandscape) { 456 mSlideIn = AnimationUtils.loadAnimation(this, 457 isLayoutRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right); 458 mSlideOut = AnimationUtils.loadAnimation(this, 459 isLayoutRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right); 460 } else { 461 mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom); 462 mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom); 463 } 464 465 mSlideIn.setInterpolator(AnimUtils.EASE_IN); 466 mSlideOut.setInterpolator(AnimUtils.EASE_OUT); 467 468 mSlideIn.setAnimationListener(mSlideInListener); 469 mSlideOut.setAnimationListener(mSlideOutListener); 470 471 mParentLayout = (FrameLayout) findViewById(R.id.dialtacts_mainlayout); 472 mParentLayout.setOnDragListener(new LayoutOnDragListener()); 473 floatingActionButtonContainer.getViewTreeObserver().addOnGlobalLayoutListener( 474 new ViewTreeObserver.OnGlobalLayoutListener() { 475 @Override 476 public void onGlobalLayout() { 477 final ViewTreeObserver observer = 478 floatingActionButtonContainer.getViewTreeObserver(); 479 if (!observer.isAlive()) { 480 return; 481 } 482 observer.removeOnGlobalLayoutListener(this); 483 int screenWidth = mParentLayout.getWidth(); 484 mFloatingActionButtonController.setScreenWidth(screenWidth); 485 mFloatingActionButtonController.align( 486 getFabAlignment(), false /* animate */); 487 } 488 }); 489 490 Trace.endSection(); 491 492 Trace.beginSection(TAG + " initialize smart dialing"); 493 mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); 494 SmartDialPrefix.initializeNanpSettings(this); 495 Trace.endSection(); 496 Trace.endSection(); 497 } 498 499 @Override 500 protected void onResume() { 501 Trace.beginSection(TAG + " onResume"); 502 super.onResume(); 503 504 mStateSaved = false; 505 if (mFirstLaunch) { 506 displayFragment(getIntent()); 507 } else if (!phoneIsInUse() && mInCallDialpadUp) { 508 hideDialpadFragment(false, true); 509 mInCallDialpadUp = false; 510 } else if (mShowDialpadOnResume) { 511 showDialpadFragment(false); 512 mShowDialpadOnResume = false; 513 } 514 515 // If there was a voice query result returned in the {@link #onActivityResult} callback, it 516 // will have been stashed in mVoiceSearchQuery since the search results fragment cannot be 517 // shown until onResume has completed. Active the search UI and set the search term now. 518 if (!TextUtils.isEmpty(mVoiceSearchQuery)) { 519 mActionBarController.onSearchBoxTapped(); 520 mSearchView.setText(mVoiceSearchQuery); 521 mVoiceSearchQuery = null; 522 } 523 524 mFirstLaunch = false; 525 526 if (mIsRestarting) { 527 // This is only called when the activity goes from resumed -> paused -> resumed, so it 528 // will not cause an extra view to be sent out on rotation 529 if (mIsDialpadShown) { 530 AnalyticsUtil.sendScreenView(mDialpadFragment, this); 531 } 532 mIsRestarting = false; 533 } 534 535 prepareVoiceSearchButton(); 536 mDialerDatabaseHelper.startSmartDialUpdateThread(); 537 mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); 538 539 if (Calls.CONTENT_TYPE.equals(getIntent().getType())) { 540 // Externally specified extras take precedence to EXTRA_SHOW_TAB, which is only 541 // used internally. 542 final Bundle extras = getIntent().getExtras(); 543 if (extras != null 544 && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) { 545 mListsFragment.showTab(ListsFragment.TAB_INDEX_VOICEMAIL); 546 } else { 547 mListsFragment.showTab(ListsFragment.TAB_INDEX_HISTORY); 548 } 549 } else if (getIntent().hasExtra(EXTRA_SHOW_TAB)) { 550 int index = getIntent().getIntExtra(EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_SPEED_DIAL); 551 if (index < mListsFragment.getTabCount()) { 552 mListsFragment.showTab(index); 553 } 554 } 555 556 setSearchBoxHint(); 557 558 Trace.endSection(); 559 } 560 561 @Override 562 protected void onRestart() { 563 super.onRestart(); 564 mIsRestarting = true; 565 } 566 567 @Override 568 protected void onPause() { 569 if (mClearSearchOnPause) { 570 hideDialpadAndSearchUi(); 571 mClearSearchOnPause = false; 572 } 573 if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) { 574 commitDialpadFragmentHide(); 575 } 576 super.onPause(); 577 } 578 579 @Override 580 protected void onSaveInstanceState(Bundle outState) { 581 super.onSaveInstanceState(outState); 582 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 583 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 584 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 585 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 586 outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown); 587 mActionBarController.saveInstanceState(outState); 588 mStateSaved = true; 589 } 590 591 @Override 592 public void onAttachFragment(Fragment fragment) { 593 if (fragment instanceof DialpadFragment) { 594 mDialpadFragment = (DialpadFragment) fragment; 595 if (!mIsDialpadShown && !mShowDialpadOnResume) { 596 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 597 transaction.hide(mDialpadFragment); 598 transaction.commit(); 599 } 600 } else if (fragment instanceof SmartDialSearchFragment) { 601 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 602 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); 603 } else if (fragment instanceof SearchFragment) { 604 mRegularSearchFragment = (RegularSearchFragment) fragment; 605 mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this); 606 } else if (fragment instanceof ListsFragment) { 607 mListsFragment = (ListsFragment) fragment; 608 mListsFragment.addOnPageChangeListener(this); 609 } 610 } 611 612 protected void handleMenuSettings() { 613 final Intent intent = new Intent(this, DialerSettingsActivity.class); 614 startActivity(intent); 615 } 616 617 @Override 618 public void onClick(View view) { 619 switch (view.getId()) { 620 case R.id.floating_action_button: 621 if (mListsFragment.getCurrentTabIndex() 622 == ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch) { 623 DialerUtils.startActivityWithErrorToast( 624 this, 625 IntentUtil.getNewContactIntent(), 626 R.string.add_contact_not_available); 627 } else if (!mIsDialpadShown) { 628 mInCallDialpadUp = false; 629 showDialpadFragment(true); 630 } 631 break; 632 case R.id.voice_search_button: 633 try { 634 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 635 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 636 } catch (ActivityNotFoundException e) { 637 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 638 Toast.LENGTH_SHORT).show(); 639 } 640 break; 641 case R.id.dialtacts_options_menu_button: 642 mOverflowMenu.show(); 643 break; 644 default: { 645 Log.wtf(TAG, "Unexpected onClick event from " + view); 646 break; 647 } 648 } 649 } 650 651 @Override 652 public boolean onMenuItemClick(MenuItem item) { 653 switch (item.getItemId()) { 654 case R.id.menu_history: 655 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 656 // CONTENT_TYPE, so that we always open our call log from our dialer 657 final Intent intent = new Intent(this, CallLogActivity.class); 658 startActivity(intent); 659 break; 660 case R.id.menu_add_contact: 661 DialerUtils.startActivityWithErrorToast( 662 this, 663 IntentUtil.getNewContactIntent(), 664 R.string.add_contact_not_available); 665 break; 666 case R.id.menu_import_export: 667 // We hard-code the "contactsAreAvailable" argument because doing it properly would 668 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 669 // now in Dialtacts for (potential) performance reasons. Compare with how it is 670 // done in {@link PeopleActivity}. 671 ImportExportDialogFragment.show(getFragmentManager(), true, 672 DialtactsActivity.class); 673 return true; 674 case R.id.menu_clear_frequents: 675 ClearFrequentsDialog.show(getFragmentManager()); 676 return true; 677 case R.id.menu_call_settings: 678 handleMenuSettings(); 679 return true; 680 } 681 return false; 682 } 683 684 @Override 685 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 686 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 687 if (resultCode == RESULT_OK) { 688 final ArrayList<String> matches = data.getStringArrayListExtra( 689 RecognizerIntent.EXTRA_RESULTS); 690 if (matches.size() > 0) { 691 final String match = matches.get(0); 692 mVoiceSearchQuery = match; 693 } else { 694 Log.e(TAG, "Voice search - nothing heard"); 695 } 696 } else { 697 Log.e(TAG, "Voice search failed"); 698 } 699 } 700 super.onActivityResult(requestCode, resultCode, data); 701 } 702 703 /** 704 * Update the number of unread voicemails (potentially other tabs) displayed next to the tab 705 * icon. 706 */ 707 public void updateTabUnreadCounts() { 708 mListsFragment.updateTabUnreadCounts(); 709 } 710 711 /** 712 * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual 713 * updates are handled by a callback which is invoked after the dialpad fragment is shown. 714 * @see #onDialpadShown 715 */ 716 private void showDialpadFragment(boolean animate) { 717 if (mIsDialpadShown || mStateSaved) { 718 return; 719 } 720 mIsDialpadShown = true; 721 722 mListsFragment.setUserVisibleHint(false); 723 724 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 725 if (mDialpadFragment == null) { 726 mDialpadFragment = new DialpadFragment(); 727 ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT); 728 } else { 729 ft.show(mDialpadFragment); 730 } 731 732 mDialpadFragment.setAnimate(animate); 733 AnalyticsUtil.sendScreenView(mDialpadFragment); 734 ft.commit(); 735 736 if (animate) { 737 mFloatingActionButtonController.scaleOut(); 738 } else { 739 mFloatingActionButtonController.setVisible(false); 740 maybeEnterSearchUi(); 741 } 742 mActionBarController.onDialpadUp(); 743 744 mListsFragment.getView().animate().alpha(0).withLayer(); 745 } 746 747 /** 748 * Callback from child DialpadFragment when the dialpad is shown. 749 */ 750 public void onDialpadShown() { 751 Assert.assertNotNull(mDialpadFragment); 752 if (mDialpadFragment.getAnimate()) { 753 mDialpadFragment.getView().startAnimation(mSlideIn); 754 } else { 755 mDialpadFragment.setYFraction(0); 756 } 757 758 updateSearchFragmentPosition(); 759 } 760 761 /** 762 * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in 763 * a callback after the hide animation ends. 764 * @see #commitDialpadFragmentHide 765 */ 766 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 767 if (mDialpadFragment == null || mDialpadFragment.getView() == null) { 768 return; 769 } 770 if (clearDialpad) { 771 mDialpadFragment.clearDialpad(); 772 } 773 if (!mIsDialpadShown) { 774 return; 775 } 776 mIsDialpadShown = false; 777 mDialpadFragment.setAnimate(animate); 778 mListsFragment.setUserVisibleHint(true); 779 mListsFragment.sendScreenViewForCurrentPosition(); 780 781 updateSearchFragmentPosition(); 782 783 mFloatingActionButtonController.align(getFabAlignment(), animate); 784 if (animate) { 785 mDialpadFragment.getView().startAnimation(mSlideOut); 786 } else { 787 commitDialpadFragmentHide(); 788 } 789 790 mActionBarController.onDialpadDown(); 791 792 if (isInSearchUi()) { 793 if (TextUtils.isEmpty(mSearchQuery)) { 794 exitSearchUi(); 795 } 796 } 797 } 798 799 /** 800 * Finishes hiding the dialpad fragment after any animations are completed. 801 */ 802 private void commitDialpadFragmentHide() { 803 if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) { 804 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 805 ft.hide(mDialpadFragment); 806 ft.commit(); 807 } 808 mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY); 809 } 810 811 private void updateSearchFragmentPosition() { 812 SearchFragment fragment = null; 813 if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { 814 fragment = mSmartDialSearchFragment; 815 } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { 816 fragment = mRegularSearchFragment; 817 } 818 if (fragment != null && fragment.isVisible()) { 819 fragment.updatePosition(true /* animate */); 820 } 821 } 822 823 @Override 824 public boolean isInSearchUi() { 825 return mInDialpadSearch || mInRegularSearch; 826 } 827 828 @Override 829 public boolean hasSearchQuery() { 830 return !TextUtils.isEmpty(mSearchQuery); 831 } 832 833 @Override 834 public boolean shouldShowActionBar() { 835 return mListsFragment.shouldShowActionBar(); 836 } 837 838 private void setNotInSearchUi() { 839 mInDialpadSearch = false; 840 mInRegularSearch = false; 841 } 842 843 private void hideDialpadAndSearchUi() { 844 if (mIsDialpadShown) { 845 hideDialpadFragment(false, true); 846 } else { 847 exitSearchUi(); 848 } 849 } 850 851 private void prepareVoiceSearchButton() { 852 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 853 if (canIntentBeHandled(voiceIntent)) { 854 mVoiceSearchButton.setVisibility(View.VISIBLE); 855 mVoiceSearchButton.setOnClickListener(this); 856 } else { 857 mVoiceSearchButton.setVisibility(View.GONE); 858 } 859 } 860 861 protected int getSearchBoxHint () { 862 return R.string.dialer_hint_find_contact; 863 } 864 865 /** 866 * Sets the hint text for the contacts search box 867 */ 868 private void setSearchBoxHint() { 869 SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) getSupportActionBar() 870 .getCustomView().findViewById(R.id.search_view_container); 871 ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search)) 872 .setHint(getSearchBoxHint()); 873 } 874 875 protected OptionsPopupMenu buildOptionsMenu(View invoker) { 876 final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker); 877 popupMenu.inflate(R.menu.dialtacts_options); 878 popupMenu.setOnMenuItemClickListener(this); 879 return popupMenu; 880 } 881 882 @Override 883 public boolean onCreateOptionsMenu(Menu menu) { 884 if (mPendingSearchViewQuery != null) { 885 mSearchView.setText(mPendingSearchViewQuery); 886 mPendingSearchViewQuery = null; 887 } 888 if (mActionBarController != null) { 889 mActionBarController.restoreActionBarOffset(); 890 } 891 return false; 892 } 893 894 /** 895 * Returns true if the intent is due to hitting the green send key (hardware call button: 896 * KEYCODE_CALL) while in a call. 897 * 898 * @param intent the intent that launched this activity 899 * @return true if the intent is due to hitting the green send key while in a call 900 */ 901 private boolean isSendKeyWhileInCall(Intent intent) { 902 // If there is a call in progress and the user launched the dialer by hitting the call 903 // button, go straight to the in-call screen. 904 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); 905 906 if (callKey) { 907 getTelecomManager().showInCallScreen(false); 908 return true; 909 } 910 911 return false; 912 } 913 914 /** 915 * Sets the current tab based on the intent's request type 916 * 917 * @param intent Intent that contains information about which tab should be selected 918 */ 919 private void displayFragment(Intent intent) { 920 // If we got here by hitting send and we're in call forward along to the in-call activity 921 if (isSendKeyWhileInCall(intent)) { 922 finish(); 923 return; 924 } 925 926 final boolean showDialpadChooser = phoneIsInUse() && !DialpadFragment.isAddCallMode(intent); 927 if (showDialpadChooser || (intent.getData() != null && isDialIntent(intent))) { 928 showDialpadFragment(false); 929 mDialpadFragment.setStartedFromNewIntent(true); 930 if (showDialpadChooser && !mDialpadFragment.isVisible()) { 931 mInCallDialpadUp = true; 932 } 933 } 934 } 935 936 @Override 937 public void onNewIntent(Intent newIntent) { 938 setIntent(newIntent); 939 940 mStateSaved = false; 941 displayFragment(newIntent); 942 943 invalidateOptionsMenu(); 944 } 945 946 /** Returns true if the given intent contains a phone number to populate the dialer with */ 947 private boolean isDialIntent(Intent intent) { 948 final String action = intent.getAction(); 949 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 950 return true; 951 } 952 if (Intent.ACTION_VIEW.equals(action)) { 953 final Uri data = intent.getData(); 954 if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) { 955 return true; 956 } 957 } 958 return false; 959 } 960 961 /** 962 * Returns an appropriate call origin for this Activity. May return null when no call origin 963 * should be used (e.g. when some 3rd party application launched the screen. Call origin is 964 * for remembering the tab in which the user made a phone call, so the external app's DIAL 965 * request should not be counted.) 966 */ 967 public String getCallOrigin() { 968 return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null; 969 } 970 971 /** 972 * Shows the search fragment 973 */ 974 private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) { 975 if (mStateSaved || getFragmentManager().isDestroyed()) { 976 // Weird race condition where fragment is doing work after the activity is destroyed 977 // due to talkback being on (b/10209937). Just return since we can't do any 978 // constructive here. 979 return; 980 } 981 982 if (DEBUG) { 983 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 984 } 985 986 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 987 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 988 transaction.remove(mSmartDialSearchFragment); 989 } else if (mInRegularSearch && mRegularSearchFragment != null) { 990 transaction.remove(mRegularSearchFragment); 991 } 992 993 final String tag; 994 if (smartDialSearch) { 995 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 996 } else { 997 tag = TAG_REGULAR_SEARCH_FRAGMENT; 998 } 999 mInDialpadSearch = smartDialSearch; 1000 mInRegularSearch = !smartDialSearch; 1001 1002 mFloatingActionButtonController.scaleOut(); 1003 1004 SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 1005 if (animate) { 1006 transaction.setCustomAnimations(android.R.animator.fade_in, 0); 1007 } else { 1008 transaction.setTransition(FragmentTransaction.TRANSIT_NONE); 1009 } 1010 if (fragment == null) { 1011 if (smartDialSearch) { 1012 fragment = new SmartDialSearchFragment(); 1013 } else { 1014 fragment = new RegularSearchFragment(); 1015 fragment.setOnTouchListener(new View.OnTouchListener() { 1016 @Override 1017 public boolean onTouch(View v, MotionEvent event) { 1018 // Show the FAB when the user touches the lists fragment and the soft 1019 // keyboard is hidden. 1020 showFabInSearchUi(); 1021 return false; 1022 } 1023 }); 1024 } 1025 transaction.add(R.id.dialtacts_frame, fragment, tag); 1026 } else { 1027 transaction.show(fragment); 1028 } 1029 // DialtactsActivity will provide the options menu 1030 fragment.setHasOptionsMenu(false); 1031 fragment.setShowEmptyListForNullQuery(true); 1032 if (!smartDialSearch) { 1033 fragment.setQueryString(query, false /* delaySelection */); 1034 } 1035 transaction.commit(); 1036 1037 if (animate) { 1038 mListsFragment.getView().animate().alpha(0).withLayer(); 1039 } 1040 mListsFragment.setUserVisibleHint(false); 1041 } 1042 1043 /** 1044 * Hides the search fragment 1045 */ 1046 private void exitSearchUi() { 1047 // See related bug in enterSearchUI(); 1048 if (getFragmentManager().isDestroyed() || mStateSaved) { 1049 return; 1050 } 1051 1052 mSearchView.setText(null); 1053 1054 if (mDialpadFragment != null) { 1055 mDialpadFragment.clearDialpad(); 1056 } 1057 1058 setNotInSearchUi(); 1059 1060 // Restore the FAB for the lists fragment. 1061 if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) { 1062 mFloatingActionButtonController.setVisible(false); 1063 } 1064 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); 1065 onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */); 1066 onPageSelected(mListsFragment.getCurrentTabIndex()); 1067 1068 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1069 if (mSmartDialSearchFragment != null) { 1070 transaction.remove(mSmartDialSearchFragment); 1071 } 1072 if (mRegularSearchFragment != null) { 1073 transaction.remove(mRegularSearchFragment); 1074 } 1075 transaction.commit(); 1076 1077 mListsFragment.getView().animate().alpha(1).withLayer(); 1078 1079 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 1080 // If the dialpad fragment wasn't previously visible, then send a screen view because 1081 // we are exiting regular search. Otherwise, the screen view will be sent by 1082 // {@link #hideDialpadFragment}. 1083 mListsFragment.sendScreenViewForCurrentPosition(); 1084 mListsFragment.setUserVisibleHint(true); 1085 } 1086 1087 mActionBarController.onSearchUiExited(); 1088 } 1089 1090 @Override 1091 public void onBackPressed() { 1092 if (mStateSaved) { 1093 return; 1094 } 1095 if (mIsDialpadShown) { 1096 if (TextUtils.isEmpty(mSearchQuery) || 1097 (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible() 1098 && mSmartDialSearchFragment.getAdapter().getCount() == 0)) { 1099 exitSearchUi(); 1100 } 1101 hideDialpadFragment(true, false); 1102 } else if (isInSearchUi()) { 1103 exitSearchUi(); 1104 DialerUtils.hideInputMethod(mParentLayout); 1105 } else { 1106 super.onBackPressed(); 1107 } 1108 } 1109 1110 private void maybeEnterSearchUi() { 1111 if (!isInSearchUi()) { 1112 enterSearchUi(true /* isSmartDial */, mSearchQuery, false); 1113 } 1114 } 1115 1116 /** 1117 * @return True if the search UI was exited, false otherwise 1118 */ 1119 private boolean maybeExitSearchUi() { 1120 if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) { 1121 exitSearchUi(); 1122 DialerUtils.hideInputMethod(mParentLayout); 1123 return true; 1124 } 1125 return false; 1126 } 1127 1128 private void showFabInSearchUi() { 1129 mFloatingActionButtonController.changeIcon( 1130 getResources().getDrawable(R.drawable.fab_ic_dial), 1131 getResources().getString(R.string.action_menu_dialpad_button)); 1132 mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); 1133 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); 1134 } 1135 1136 @Override 1137 public void onDialpadQueryChanged(String query) { 1138 if (mSmartDialSearchFragment != null) { 1139 mSmartDialSearchFragment.setAddToContactNumber(query); 1140 } 1141 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 1142 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 1143 1144 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 1145 if (DEBUG) { 1146 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 1147 } 1148 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 1149 // This callback can happen if the dialpad fragment is recreated because of 1150 // activity destruction. In that case, don't update the search view because 1151 // that would bring the user back to the search fragment regardless of the 1152 // previous state of the application. Instead, just return here and let the 1153 // fragment manager correctly figure out whatever fragment was last displayed. 1154 if (!TextUtils.isEmpty(normalizedQuery)) { 1155 mPendingSearchViewQuery = normalizedQuery; 1156 } 1157 return; 1158 } 1159 mSearchView.setText(normalizedQuery); 1160 } 1161 1162 try { 1163 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 1164 mDialpadFragment.process_quote_emergency_unquote(normalizedQuery); 1165 } 1166 } catch (Exception ignored) { 1167 // Skip any exceptions for this piece of code 1168 } 1169 } 1170 1171 @Override 1172 public boolean onDialpadSpacerTouchWithEmptyQuery() { 1173 if (mInDialpadSearch && mSmartDialSearchFragment != null 1174 && !mSmartDialSearchFragment.isShowingPermissionRequest()) { 1175 hideDialpadFragment(true /* animate */, true /* clearDialpad */); 1176 return true; 1177 } 1178 return false; 1179 } 1180 1181 @Override 1182 public void onListFragmentScrollStateChange(int scrollState) { 1183 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 1184 hideDialpadFragment(true, false); 1185 DialerUtils.hideInputMethod(mParentLayout); 1186 } 1187 } 1188 1189 @Override 1190 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 1191 int totalItemCount) { 1192 // TODO: No-op for now. This should eventually show/hide the actionBar based on 1193 // interactions with the ListsFragments. 1194 } 1195 1196 private boolean phoneIsInUse() { 1197 return getTelecomManager().isInCall(); 1198 } 1199 1200 private boolean canIntentBeHandled(Intent intent) { 1201 final PackageManager packageManager = getPackageManager(); 1202 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 1203 PackageManager.MATCH_DEFAULT_ONLY); 1204 return resolveInfo != null && resolveInfo.size() > 0; 1205 } 1206 1207 /** 1208 * Called when the user has long-pressed a contact tile to start a drag operation. 1209 */ 1210 @Override 1211 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 1212 mListsFragment.showRemoveView(true); 1213 } 1214 1215 @Override 1216 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 1217 } 1218 1219 /** 1220 * Called when the user has released a contact tile after long-pressing it. 1221 */ 1222 @Override 1223 public void onDragFinished(int x, int y) { 1224 mListsFragment.showRemoveView(false); 1225 } 1226 1227 @Override 1228 public void onDroppedOnRemove() {} 1229 1230 /** 1231 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 1232 * once it has been attached to the activity. 1233 */ 1234 @Override 1235 public void setDragDropController(DragDropController dragController) { 1236 mDragDropController = dragController; 1237 mListsFragment.getRemoveView().setDragDropController(dragController); 1238 } 1239 1240 /** 1241 * Implemented to satisfy {@link SpeedDialFragment.HostInterface} 1242 */ 1243 @Override 1244 public void showAllContactsTab() { 1245 if (mListsFragment != null) { 1246 mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS); 1247 } 1248 } 1249 1250 /** 1251 * Implemented to satisfy {@link CallLogFragment.HostInterface} 1252 */ 1253 @Override 1254 public void showDialpad() { 1255 showDialpadFragment(true); 1256 } 1257 1258 @Override 1259 public void onPickPhoneNumberAction(Uri dataUri) { 1260 // Specify call-origin so that users will see the previous tab instead of 1261 // CallLog screen (search UI will be automatically exited). 1262 PhoneNumberInteraction.startInteractionForPhoneCall( 1263 DialtactsActivity.this, dataUri, getCallOrigin()); 1264 mClearSearchOnPause = true; 1265 } 1266 1267 @Override 1268 public void onCallNumberDirectly(String phoneNumber) { 1269 onCallNumberDirectly(phoneNumber, false /* isVideoCall */); 1270 } 1271 1272 @Override 1273 public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall) { 1274 if (phoneNumber == null) { 1275 // Invalid phone number, but let the call go through so that InCallUI can show 1276 // an error message. 1277 phoneNumber = ""; 1278 } 1279 Intent intent = isVideoCall ? 1280 IntentUtil.getVideoCallIntent(phoneNumber, getCallOrigin()) : 1281 IntentUtil.getCallIntent(phoneNumber, getCallOrigin()); 1282 DialerUtils.startActivityWithErrorToast(this, intent); 1283 mClearSearchOnPause = true; 1284 } 1285 1286 @Override 1287 public void onShortcutIntentCreated(Intent intent) { 1288 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 1289 } 1290 1291 @Override 1292 public void onHomeInActionBarSelected() { 1293 exitSearchUi(); 1294 } 1295 1296 @Override 1297 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 1298 int tabIndex = mListsFragment.getCurrentTabIndex(); 1299 1300 // Scroll the button from center to end when moving from the Speed Dial to Call History tab. 1301 // In RTL, scroll when the current tab is Call History instead, since the order of the tabs 1302 // is reversed and the ViewPager returns the left tab position during scroll. 1303 boolean isRtl = DialerUtils.isRtl(); 1304 if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) { 1305 mFloatingActionButtonController.onPageScrolled(positionOffset); 1306 } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_HISTORY && !mIsLandscape) { 1307 mFloatingActionButtonController.onPageScrolled(1 - positionOffset); 1308 } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) { 1309 mFloatingActionButtonController.onPageScrolled(1); 1310 } 1311 } 1312 1313 @Override 1314 public void onPageSelected(int position) { 1315 int tabIndex = mListsFragment.getCurrentTabIndex(); 1316 if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) { 1317 mFloatingActionButtonController.changeIcon( 1318 getResources().getDrawable(R.drawable.ic_person_add_24dp), 1319 getResources().getString(R.string.search_shortcut_create_new_contact)); 1320 } else { 1321 mFloatingActionButtonController.changeIcon( 1322 getResources().getDrawable(R.drawable.fab_ic_dial), 1323 getResources().getString(R.string.action_menu_dialpad_button)); 1324 } 1325 } 1326 1327 @Override 1328 public void onPageScrollStateChanged(int state) { 1329 } 1330 1331 private TelecomManager getTelecomManager() { 1332 return (TelecomManager) getSystemService(Context.TELECOM_SERVICE); 1333 } 1334 1335 @Override 1336 public boolean isActionBarShowing() { 1337 return mActionBarController.isActionBarShowing(); 1338 } 1339 1340 @Override 1341 public ActionBarController getActionBarController() { 1342 return mActionBarController; 1343 } 1344 1345 @Override 1346 public boolean isDialpadShown() { 1347 return mIsDialpadShown; 1348 } 1349 1350 @Override 1351 public int getDialpadHeight() { 1352 if (mDialpadFragment != null) { 1353 return mDialpadFragment.getDialpadHeight(); 1354 } 1355 return 0; 1356 } 1357 1358 @Override 1359 public int getActionBarHideOffset() { 1360 return getSupportActionBar().getHideOffset(); 1361 } 1362 1363 @Override 1364 public void setActionBarHideOffset(int offset) { 1365 getSupportActionBar().setHideOffset(offset); 1366 } 1367 1368 @Override 1369 public int getActionBarHeight() { 1370 return mActionBarHeight; 1371 } 1372 1373 private int getFabAlignment() { 1374 if (!mIsLandscape && !isInSearchUi() && 1375 mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1376 return FloatingActionButtonController.ALIGN_MIDDLE; 1377 } 1378 return FloatingActionButtonController.ALIGN_END; 1379 } 1380} 1381