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