DialtactsActivity.java revision 4e5df6b3a264cceba8774f6704697312b35f329d
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 (getIntent().hasExtra(EXTRA_SHOW_TAB)) { 540 int index = getIntent().getIntExtra(EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_SPEED_DIAL); 541 if (index < mListsFragment.getTabCount()) { 542 mListsFragment.showTab(index); 543 } 544 } else if (Calls.CONTENT_TYPE.equals(getIntent().getType())) { 545 mListsFragment.showTab(ListsFragment.TAB_INDEX_HISTORY); 546 } 547 548 setSearchBoxHint(); 549 550 Trace.endSection(); 551 } 552 553 @Override 554 protected void onRestart() { 555 super.onRestart(); 556 mIsRestarting = true; 557 } 558 559 @Override 560 protected void onPause() { 561 if (mClearSearchOnPause) { 562 hideDialpadAndSearchUi(); 563 mClearSearchOnPause = false; 564 } 565 if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) { 566 commitDialpadFragmentHide(); 567 } 568 super.onPause(); 569 } 570 571 @Override 572 protected void onSaveInstanceState(Bundle outState) { 573 super.onSaveInstanceState(outState); 574 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 575 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 576 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 577 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 578 outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown); 579 mActionBarController.saveInstanceState(outState); 580 mStateSaved = true; 581 } 582 583 @Override 584 public void onAttachFragment(Fragment fragment) { 585 if (fragment instanceof DialpadFragment) { 586 mDialpadFragment = (DialpadFragment) fragment; 587 if (!mIsDialpadShown && !mShowDialpadOnResume) { 588 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 589 transaction.hide(mDialpadFragment); 590 transaction.commit(); 591 } 592 } else if (fragment instanceof SmartDialSearchFragment) { 593 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 594 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); 595 } else if (fragment instanceof SearchFragment) { 596 mRegularSearchFragment = (RegularSearchFragment) fragment; 597 mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this); 598 } else if (fragment instanceof ListsFragment) { 599 mListsFragment = (ListsFragment) fragment; 600 mListsFragment.addOnPageChangeListener(this); 601 } 602 } 603 604 protected void handleMenuSettings() { 605 final Intent intent = new Intent(this, DialerSettingsActivity.class); 606 startActivity(intent); 607 } 608 609 @Override 610 public void onClick(View view) { 611 switch (view.getId()) { 612 case R.id.floating_action_button: 613 if (mListsFragment.getCurrentTabIndex() 614 == ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch) { 615 DialerUtils.startActivityWithErrorToast( 616 this, 617 IntentUtil.getNewContactIntent(), 618 R.string.add_contact_not_available); 619 } else if (!mIsDialpadShown) { 620 mInCallDialpadUp = false; 621 showDialpadFragment(true); 622 } 623 break; 624 case R.id.voice_search_button: 625 try { 626 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 627 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 628 } catch (ActivityNotFoundException e) { 629 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 630 Toast.LENGTH_SHORT).show(); 631 } 632 break; 633 case R.id.dialtacts_options_menu_button: 634 mOverflowMenu.show(); 635 break; 636 default: { 637 Log.wtf(TAG, "Unexpected onClick event from " + view); 638 break; 639 } 640 } 641 } 642 643 @Override 644 public boolean onMenuItemClick(MenuItem item) { 645 switch (item.getItemId()) { 646 case R.id.menu_history: 647 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 648 // CONTENT_TYPE, so that we always open our call log from our dialer 649 final Intent intent = new Intent(this, CallLogActivity.class); 650 startActivity(intent); 651 break; 652 case R.id.menu_add_contact: 653 DialerUtils.startActivityWithErrorToast( 654 this, 655 IntentUtil.getNewContactIntent(), 656 R.string.add_contact_not_available); 657 break; 658 case R.id.menu_import_export: 659 // We hard-code the "contactsAreAvailable" argument because doing it properly would 660 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 661 // now in Dialtacts for (potential) performance reasons. Compare with how it is 662 // done in {@link PeopleActivity}. 663 ImportExportDialogFragment.show(getFragmentManager(), true, 664 DialtactsActivity.class); 665 return true; 666 case R.id.menu_clear_frequents: 667 ClearFrequentsDialog.show(getFragmentManager()); 668 return true; 669 case R.id.menu_call_settings: 670 handleMenuSettings(); 671 return true; 672 } 673 return false; 674 } 675 676 @Override 677 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 678 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 679 if (resultCode == RESULT_OK) { 680 final ArrayList<String> matches = data.getStringArrayListExtra( 681 RecognizerIntent.EXTRA_RESULTS); 682 if (matches.size() > 0) { 683 final String match = matches.get(0); 684 mVoiceSearchQuery = match; 685 } else { 686 Log.e(TAG, "Voice search - nothing heard"); 687 } 688 } else { 689 Log.e(TAG, "Voice search failed"); 690 } 691 } 692 super.onActivityResult(requestCode, resultCode, data); 693 } 694 695 /** 696 * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual 697 * updates are handled by a callback which is invoked after the dialpad fragment is shown. 698 * @see #onDialpadShown 699 */ 700 private void showDialpadFragment(boolean animate) { 701 if (mIsDialpadShown || mStateSaved) { 702 return; 703 } 704 mIsDialpadShown = true; 705 706 mListsFragment.setUserVisibleHint(false); 707 708 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 709 if (mDialpadFragment == null) { 710 mDialpadFragment = new DialpadFragment(); 711 ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT); 712 } else { 713 ft.show(mDialpadFragment); 714 } 715 716 mDialpadFragment.setAnimate(animate); 717 AnalyticsUtil.sendScreenView(mDialpadFragment); 718 ft.commit(); 719 720 if (animate) { 721 mFloatingActionButtonController.scaleOut(); 722 } else { 723 mFloatingActionButtonController.setVisible(false); 724 maybeEnterSearchUi(); 725 } 726 mActionBarController.onDialpadUp(); 727 728 mListsFragment.getView().animate().alpha(0).withLayer(); 729 } 730 731 /** 732 * Callback from child DialpadFragment when the dialpad is shown. 733 */ 734 public void onDialpadShown() { 735 Assert.assertNotNull(mDialpadFragment); 736 if (mDialpadFragment.getAnimate()) { 737 mDialpadFragment.getView().startAnimation(mSlideIn); 738 } else { 739 mDialpadFragment.setYFraction(0); 740 } 741 742 updateSearchFragmentPosition(); 743 } 744 745 /** 746 * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in 747 * a callback after the hide animation ends. 748 * @see #commitDialpadFragmentHide 749 */ 750 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 751 if (mDialpadFragment == null || mDialpadFragment.getView() == null) { 752 return; 753 } 754 if (clearDialpad) { 755 mDialpadFragment.clearDialpad(); 756 } 757 if (!mIsDialpadShown) { 758 return; 759 } 760 mIsDialpadShown = false; 761 mDialpadFragment.setAnimate(animate); 762 mListsFragment.setUserVisibleHint(true); 763 mListsFragment.sendScreenViewForCurrentPosition(); 764 765 updateSearchFragmentPosition(); 766 767 mFloatingActionButtonController.align(getFabAlignment(), animate); 768 if (animate) { 769 mDialpadFragment.getView().startAnimation(mSlideOut); 770 } else { 771 commitDialpadFragmentHide(); 772 } 773 774 mActionBarController.onDialpadDown(); 775 776 if (isInSearchUi()) { 777 if (TextUtils.isEmpty(mSearchQuery)) { 778 exitSearchUi(); 779 } 780 } 781 } 782 783 /** 784 * Finishes hiding the dialpad fragment after any animations are completed. 785 */ 786 private void commitDialpadFragmentHide() { 787 if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) { 788 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 789 ft.hide(mDialpadFragment); 790 ft.commit(); 791 } 792 mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY); 793 } 794 795 private void updateSearchFragmentPosition() { 796 SearchFragment fragment = null; 797 if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { 798 fragment = mSmartDialSearchFragment; 799 } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { 800 fragment = mRegularSearchFragment; 801 } 802 if (fragment != null && fragment.isVisible()) { 803 fragment.updatePosition(true /* animate */); 804 } 805 } 806 807 @Override 808 public boolean isInSearchUi() { 809 return mInDialpadSearch || mInRegularSearch; 810 } 811 812 @Override 813 public boolean hasSearchQuery() { 814 return !TextUtils.isEmpty(mSearchQuery); 815 } 816 817 @Override 818 public boolean shouldShowActionBar() { 819 return mListsFragment.shouldShowActionBar(); 820 } 821 822 private void setNotInSearchUi() { 823 mInDialpadSearch = false; 824 mInRegularSearch = false; 825 } 826 827 private void hideDialpadAndSearchUi() { 828 if (mIsDialpadShown) { 829 hideDialpadFragment(false, true); 830 } else { 831 exitSearchUi(); 832 } 833 } 834 835 private void prepareVoiceSearchButton() { 836 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 837 if (canIntentBeHandled(voiceIntent)) { 838 mVoiceSearchButton.setVisibility(View.VISIBLE); 839 mVoiceSearchButton.setOnClickListener(this); 840 } else { 841 mVoiceSearchButton.setVisibility(View.GONE); 842 } 843 } 844 845 protected int getSearchBoxHint () { 846 return R.string.dialer_hint_find_contact; 847 } 848 849 /** 850 * Sets the hint text for the contacts search box 851 */ 852 private void setSearchBoxHint() { 853 SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) getSupportActionBar() 854 .getCustomView().findViewById(R.id.search_view_container); 855 ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search)) 856 .setHint(getSearchBoxHint()); 857 } 858 859 protected OptionsPopupMenu buildOptionsMenu(View invoker) { 860 final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker); 861 popupMenu.inflate(R.menu.dialtacts_options); 862 popupMenu.setOnMenuItemClickListener(this); 863 return popupMenu; 864 } 865 866 @Override 867 public boolean onCreateOptionsMenu(Menu menu) { 868 if (mPendingSearchViewQuery != null) { 869 mSearchView.setText(mPendingSearchViewQuery); 870 mPendingSearchViewQuery = null; 871 } 872 if (mActionBarController != null) { 873 mActionBarController.restoreActionBarOffset(); 874 } 875 return false; 876 } 877 878 /** 879 * Returns true if the intent is due to hitting the green send key (hardware call button: 880 * KEYCODE_CALL) while in a call. 881 * 882 * @param intent the intent that launched this activity 883 * @return true if the intent is due to hitting the green send key while in a call 884 */ 885 private boolean isSendKeyWhileInCall(Intent intent) { 886 // If there is a call in progress and the user launched the dialer by hitting the call 887 // button, go straight to the in-call screen. 888 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); 889 890 if (callKey) { 891 getTelecomManager().showInCallScreen(false); 892 return true; 893 } 894 895 return false; 896 } 897 898 /** 899 * Sets the current tab based on the intent's request type 900 * 901 * @param intent Intent that contains information about which tab should be selected 902 */ 903 private void displayFragment(Intent intent) { 904 // If we got here by hitting send and we're in call forward along to the in-call activity 905 if (isSendKeyWhileInCall(intent)) { 906 finish(); 907 return; 908 } 909 910 final boolean phoneIsInUse = phoneIsInUse(); 911 if (phoneIsInUse || (intent.getData() != null && isDialIntent(intent))) { 912 showDialpadFragment(false); 913 mDialpadFragment.setStartedFromNewIntent(true); 914 if (phoneIsInUse && !mDialpadFragment.isVisible()) { 915 mInCallDialpadUp = true; 916 } 917 } 918 } 919 920 @Override 921 public void onNewIntent(Intent newIntent) { 922 setIntent(newIntent); 923 924 mStateSaved = false; 925 displayFragment(newIntent); 926 927 invalidateOptionsMenu(); 928 } 929 930 /** Returns true if the given intent contains a phone number to populate the dialer with */ 931 private boolean isDialIntent(Intent intent) { 932 final String action = intent.getAction(); 933 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 934 return true; 935 } 936 if (Intent.ACTION_VIEW.equals(action)) { 937 final Uri data = intent.getData(); 938 if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) { 939 return true; 940 } 941 } 942 return false; 943 } 944 945 /** 946 * Returns an appropriate call origin for this Activity. May return null when no call origin 947 * should be used (e.g. when some 3rd party application launched the screen. Call origin is 948 * for remembering the tab in which the user made a phone call, so the external app's DIAL 949 * request should not be counted.) 950 */ 951 public String getCallOrigin() { 952 return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null; 953 } 954 955 /** 956 * Shows the search fragment 957 */ 958 private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) { 959 if (mStateSaved || getFragmentManager().isDestroyed()) { 960 // Weird race condition where fragment is doing work after the activity is destroyed 961 // due to talkback being on (b/10209937). Just return since we can't do any 962 // constructive here. 963 return; 964 } 965 966 if (DEBUG) { 967 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 968 } 969 970 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 971 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 972 transaction.remove(mSmartDialSearchFragment); 973 } else if (mInRegularSearch && mRegularSearchFragment != null) { 974 transaction.remove(mRegularSearchFragment); 975 } 976 977 final String tag; 978 if (smartDialSearch) { 979 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 980 } else { 981 tag = TAG_REGULAR_SEARCH_FRAGMENT; 982 } 983 mInDialpadSearch = smartDialSearch; 984 mInRegularSearch = !smartDialSearch; 985 986 mFloatingActionButtonController.scaleOut(); 987 988 SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 989 if (animate) { 990 transaction.setCustomAnimations(android.R.animator.fade_in, 0); 991 } else { 992 transaction.setTransition(FragmentTransaction.TRANSIT_NONE); 993 } 994 if (fragment == null) { 995 if (smartDialSearch) { 996 fragment = new SmartDialSearchFragment(); 997 } else { 998 fragment = new RegularSearchFragment(); 999 fragment.setOnTouchListener(new View.OnTouchListener() { 1000 @Override 1001 public boolean onTouch(View v, MotionEvent event) { 1002 // Show the FAB when the user touches the lists fragment and the soft 1003 // keyboard is hidden. 1004 showFabInSearchUi(); 1005 return false; 1006 } 1007 }); 1008 } 1009 transaction.add(R.id.dialtacts_frame, fragment, tag); 1010 } else { 1011 transaction.show(fragment); 1012 } 1013 // DialtactsActivity will provide the options menu 1014 fragment.setHasOptionsMenu(false); 1015 fragment.setShowEmptyListForNullQuery(true); 1016 if (!smartDialSearch) { 1017 fragment.setQueryString(query, false /* delaySelection */); 1018 } 1019 transaction.commit(); 1020 1021 if (animate) { 1022 mListsFragment.getView().animate().alpha(0).withLayer(); 1023 } 1024 mListsFragment.setUserVisibleHint(false); 1025 } 1026 1027 /** 1028 * Hides the search fragment 1029 */ 1030 private void exitSearchUi() { 1031 // See related bug in enterSearchUI(); 1032 if (getFragmentManager().isDestroyed() || mStateSaved) { 1033 return; 1034 } 1035 1036 mSearchView.setText(null); 1037 1038 if (mDialpadFragment != null) { 1039 mDialpadFragment.clearDialpad(); 1040 } 1041 1042 setNotInSearchUi(); 1043 1044 // Restore the FAB for the lists fragment. 1045 if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) { 1046 mFloatingActionButtonController.setVisible(false); 1047 } 1048 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); 1049 onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */); 1050 onPageSelected(mListsFragment.getCurrentTabIndex()); 1051 1052 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1053 if (mSmartDialSearchFragment != null) { 1054 transaction.remove(mSmartDialSearchFragment); 1055 } 1056 if (mRegularSearchFragment != null) { 1057 transaction.remove(mRegularSearchFragment); 1058 } 1059 transaction.commit(); 1060 1061 mListsFragment.getView().animate().alpha(1).withLayer(); 1062 1063 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 1064 // If the dialpad fragment wasn't previously visible, then send a screen view because 1065 // we are exiting regular search. Otherwise, the screen view will be sent by 1066 // {@link #hideDialpadFragment}. 1067 mListsFragment.sendScreenViewForCurrentPosition(); 1068 mListsFragment.setUserVisibleHint(true); 1069 } 1070 1071 mActionBarController.onSearchUiExited(); 1072 } 1073 1074 @Override 1075 public void onBackPressed() { 1076 if (mStateSaved) { 1077 return; 1078 } 1079 if (mIsDialpadShown) { 1080 if (TextUtils.isEmpty(mSearchQuery) || 1081 (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible() 1082 && mSmartDialSearchFragment.getAdapter().getCount() == 0)) { 1083 exitSearchUi(); 1084 } 1085 hideDialpadFragment(true, false); 1086 } else if (isInSearchUi()) { 1087 exitSearchUi(); 1088 DialerUtils.hideInputMethod(mParentLayout); 1089 } else { 1090 super.onBackPressed(); 1091 } 1092 } 1093 1094 private void maybeEnterSearchUi() { 1095 if (!isInSearchUi()) { 1096 enterSearchUi(true /* isSmartDial */, mSearchQuery, false); 1097 } 1098 } 1099 1100 /** 1101 * @return True if the search UI was exited, false otherwise 1102 */ 1103 private boolean maybeExitSearchUi() { 1104 if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) { 1105 exitSearchUi(); 1106 DialerUtils.hideInputMethod(mParentLayout); 1107 return true; 1108 } 1109 return false; 1110 } 1111 1112 private void showFabInSearchUi() { 1113 mFloatingActionButtonController.changeIcon( 1114 getResources().getDrawable(R.drawable.fab_ic_dial), 1115 getResources().getString(R.string.action_menu_dialpad_button)); 1116 mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); 1117 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); 1118 } 1119 1120 @Override 1121 public void onDialpadQueryChanged(String query) { 1122 if (mSmartDialSearchFragment != null) { 1123 mSmartDialSearchFragment.setAddToContactNumber(query); 1124 } 1125 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 1126 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 1127 1128 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 1129 if (DEBUG) { 1130 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 1131 } 1132 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 1133 // This callback can happen if the dialpad fragment is recreated because of 1134 // activity destruction. In that case, don't update the search view because 1135 // that would bring the user back to the search fragment regardless of the 1136 // previous state of the application. Instead, just return here and let the 1137 // fragment manager correctly figure out whatever fragment was last displayed. 1138 if (!TextUtils.isEmpty(normalizedQuery)) { 1139 mPendingSearchViewQuery = normalizedQuery; 1140 } 1141 return; 1142 } 1143 mSearchView.setText(normalizedQuery); 1144 } 1145 1146 try { 1147 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 1148 mDialpadFragment.process_quote_emergency_unquote(normalizedQuery); 1149 } 1150 } catch (Exception ignored) { 1151 // Skip any exceptions for this piece of code 1152 } 1153 } 1154 1155 @Override 1156 public boolean onDialpadSpacerTouchWithEmptyQuery() { 1157 if (mInDialpadSearch && mSmartDialSearchFragment != null 1158 && !mSmartDialSearchFragment.isShowingPermissionRequest()) { 1159 hideDialpadFragment(true /* animate */, true /* clearDialpad */); 1160 return true; 1161 } 1162 return false; 1163 } 1164 1165 @Override 1166 public void onListFragmentScrollStateChange(int scrollState) { 1167 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 1168 hideDialpadFragment(true, false); 1169 DialerUtils.hideInputMethod(mParentLayout); 1170 } 1171 } 1172 1173 @Override 1174 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 1175 int totalItemCount) { 1176 // TODO: No-op for now. This should eventually show/hide the actionBar based on 1177 // interactions with the ListsFragments. 1178 } 1179 1180 private boolean phoneIsInUse() { 1181 return getTelecomManager().isInCall(); 1182 } 1183 1184 private boolean canIntentBeHandled(Intent intent) { 1185 final PackageManager packageManager = getPackageManager(); 1186 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 1187 PackageManager.MATCH_DEFAULT_ONLY); 1188 return resolveInfo != null && resolveInfo.size() > 0; 1189 } 1190 1191 /** 1192 * Called when the user has long-pressed a contact tile to start a drag operation. 1193 */ 1194 @Override 1195 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 1196 mListsFragment.showRemoveView(true); 1197 } 1198 1199 @Override 1200 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 1201 } 1202 1203 /** 1204 * Called when the user has released a contact tile after long-pressing it. 1205 */ 1206 @Override 1207 public void onDragFinished(int x, int y) { 1208 mListsFragment.showRemoveView(false); 1209 } 1210 1211 @Override 1212 public void onDroppedOnRemove() {} 1213 1214 /** 1215 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 1216 * once it has been attached to the activity. 1217 */ 1218 @Override 1219 public void setDragDropController(DragDropController dragController) { 1220 mDragDropController = dragController; 1221 mListsFragment.getRemoveView().setDragDropController(dragController); 1222 } 1223 1224 /** 1225 * Implemented to satisfy {@link SpeedDialFragment.HostInterface} 1226 */ 1227 @Override 1228 public void showAllContactsTab() { 1229 if (mListsFragment != null) { 1230 mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS); 1231 } 1232 } 1233 1234 /** 1235 * Implemented to satisfy {@link CallLogFragment.HostInterface} 1236 */ 1237 @Override 1238 public void showDialpad() { 1239 showDialpadFragment(true); 1240 } 1241 1242 @Override 1243 public void onPickPhoneNumberAction(Uri dataUri) { 1244 // Specify call-origin so that users will see the previous tab instead of 1245 // CallLog screen (search UI will be automatically exited). 1246 PhoneNumberInteraction.startInteractionForPhoneCall( 1247 DialtactsActivity.this, dataUri, getCallOrigin()); 1248 mClearSearchOnPause = true; 1249 } 1250 1251 @Override 1252 public void onCallNumberDirectly(String phoneNumber) { 1253 onCallNumberDirectly(phoneNumber, false /* isVideoCall */); 1254 } 1255 1256 @Override 1257 public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall) { 1258 if (phoneNumber == null) { 1259 // Invalid phone number, but let the call go through so that InCallUI can show 1260 // an error message. 1261 phoneNumber = ""; 1262 } 1263 Intent intent = isVideoCall ? 1264 IntentUtil.getVideoCallIntent(phoneNumber, getCallOrigin()) : 1265 IntentUtil.getCallIntent(phoneNumber, getCallOrigin()); 1266 DialerUtils.startActivityWithErrorToast(this, intent); 1267 mClearSearchOnPause = true; 1268 } 1269 1270 @Override 1271 public void onShortcutIntentCreated(Intent intent) { 1272 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 1273 } 1274 1275 @Override 1276 public void onHomeInActionBarSelected() { 1277 exitSearchUi(); 1278 } 1279 1280 @Override 1281 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 1282 int tabIndex = mListsFragment.getCurrentTabIndex(); 1283 1284 // Scroll the button from center to end when moving from the Speed Dial to Call History tab. 1285 // In RTL, scroll when the current tab is Call History instead, since the order of the tabs 1286 // is reversed and the ViewPager returns the left tab position during scroll. 1287 boolean isRtl = DialerUtils.isRtl(); 1288 if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) { 1289 mFloatingActionButtonController.onPageScrolled(positionOffset); 1290 } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_HISTORY && !mIsLandscape) { 1291 mFloatingActionButtonController.onPageScrolled(1 - positionOffset); 1292 } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) { 1293 mFloatingActionButtonController.onPageScrolled(1); 1294 } 1295 } 1296 1297 @Override 1298 public void onPageSelected(int position) { 1299 int tabIndex = mListsFragment.getCurrentTabIndex(); 1300 if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) { 1301 mFloatingActionButtonController.changeIcon( 1302 getResources().getDrawable(R.drawable.ic_person_add_24dp), 1303 getResources().getString(R.string.search_shortcut_create_new_contact)); 1304 } else { 1305 mFloatingActionButtonController.changeIcon( 1306 getResources().getDrawable(R.drawable.fab_ic_dial), 1307 getResources().getString(R.string.action_menu_dialpad_button)); 1308 } 1309 } 1310 1311 @Override 1312 public void onPageScrollStateChanged(int state) { 1313 } 1314 1315 private TelecomManager getTelecomManager() { 1316 return (TelecomManager) getSystemService(Context.TELECOM_SERVICE); 1317 } 1318 1319 @Override 1320 public boolean isActionBarShowing() { 1321 return mActionBarController.isActionBarShowing(); 1322 } 1323 1324 @Override 1325 public ActionBarController getActionBarController() { 1326 return mActionBarController; 1327 } 1328 1329 @Override 1330 public boolean isDialpadShown() { 1331 return mIsDialpadShown; 1332 } 1333 1334 @Override 1335 public int getDialpadHeight() { 1336 if (mDialpadFragment != null) { 1337 return mDialpadFragment.getDialpadHeight(); 1338 } 1339 return 0; 1340 } 1341 1342 @Override 1343 public int getActionBarHideOffset() { 1344 return getSupportActionBar().getHideOffset(); 1345 } 1346 1347 @Override 1348 public void setActionBarHideOffset(int offset) { 1349 getSupportActionBar().setHideOffset(offset); 1350 } 1351 1352 @Override 1353 public int getActionBarHeight() { 1354 return mActionBarHeight; 1355 } 1356 1357 private int getFabAlignment() { 1358 if (!mIsLandscape && !isInSearchUi() && 1359 mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1360 return FloatingActionButtonController.ALIGN_MIDDLE; 1361 } 1362 return FloatingActionButtonController.ALIGN_END; 1363 } 1364} 1365