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