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