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