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