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