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