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