DialtactsActivity.java revision 5953e8c0237cdbc40a0439734d6ef212b2afa27f
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 if (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) { 695 ImportExportDialogFragment.show(getFragmentManager(), true, 696 DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_FAVORITES); 697 } else { 698 ImportExportDialogFragment.show(getFragmentManager(), true, 699 DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_DEFAULT); 700 } 701 return true; 702 case R.id.menu_clear_frequents: 703 ClearFrequentsDialog.show(getFragmentManager()); 704 return true; 705 case R.id.menu_call_settings: 706 handleMenuSettings(); 707 return true; 708 } 709 return false; 710 } 711 712 @Override 713 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 714 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 715 if (resultCode == RESULT_OK) { 716 final ArrayList<String> matches = data.getStringArrayListExtra( 717 RecognizerIntent.EXTRA_RESULTS); 718 if (matches.size() > 0) { 719 final String match = matches.get(0); 720 mVoiceSearchQuery = match; 721 } else { 722 Log.e(TAG, "Voice search - nothing heard"); 723 } 724 } else { 725 Log.e(TAG, "Voice search failed"); 726 } 727 } 728 super.onActivityResult(requestCode, resultCode, data); 729 } 730 731 /** 732 * Update the number of unread voicemails (potentially other tabs) displayed next to the tab 733 * icon. 734 */ 735 public void updateTabUnreadCounts() { 736 mListsFragment.updateTabUnreadCounts(); 737 } 738 739 /** 740 * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual 741 * updates are handled by a callback which is invoked after the dialpad fragment is shown. 742 * @see #onDialpadShown 743 */ 744 private void showDialpadFragment(boolean animate) { 745 if (mIsDialpadShown || mStateSaved) { 746 return; 747 } 748 mIsDialpadShown = true; 749 750 mListsFragment.setUserVisibleHint(false); 751 752 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 753 if (mDialpadFragment == null) { 754 mDialpadFragment = new DialpadFragment(); 755 ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT); 756 } else { 757 ft.show(mDialpadFragment); 758 } 759 760 mDialpadFragment.setAnimate(animate); 761 AnalyticsUtil.sendScreenView(mDialpadFragment); 762 ft.commit(); 763 764 if (animate) { 765 mFloatingActionButtonController.scaleOut(); 766 } else { 767 mFloatingActionButtonController.setVisible(false); 768 maybeEnterSearchUi(); 769 } 770 mActionBarController.onDialpadUp(); 771 772 mListsFragment.getView().animate().alpha(0).withLayer(); 773 } 774 775 /** 776 * Callback from child DialpadFragment when the dialpad is shown. 777 */ 778 public void onDialpadShown() { 779 Assert.assertNotNull(mDialpadFragment); 780 if (mDialpadFragment.getAnimate()) { 781 mDialpadFragment.getView().startAnimation(mSlideIn); 782 } else { 783 mDialpadFragment.setYFraction(0); 784 } 785 786 updateSearchFragmentPosition(); 787 } 788 789 /** 790 * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in 791 * a callback after the hide animation ends. 792 * @see #commitDialpadFragmentHide 793 */ 794 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 795 if (mDialpadFragment == null || mDialpadFragment.getView() == null) { 796 return; 797 } 798 if (clearDialpad) { 799 mDialpadFragment.clearDialpad(); 800 } 801 if (!mIsDialpadShown) { 802 return; 803 } 804 mIsDialpadShown = false; 805 mDialpadFragment.setAnimate(animate); 806 mListsFragment.setUserVisibleHint(true); 807 mListsFragment.sendScreenViewForCurrentPosition(); 808 809 updateSearchFragmentPosition(); 810 811 mFloatingActionButtonController.align(getFabAlignment(), animate); 812 if (animate) { 813 mDialpadFragment.getView().startAnimation(mSlideOut); 814 } else { 815 commitDialpadFragmentHide(); 816 } 817 818 mActionBarController.onDialpadDown(); 819 820 if (isInSearchUi()) { 821 if (TextUtils.isEmpty(mSearchQuery)) { 822 exitSearchUi(); 823 } 824 } 825 } 826 827 /** 828 * Finishes hiding the dialpad fragment after any animations are completed. 829 */ 830 private void commitDialpadFragmentHide() { 831 if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) { 832 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 833 ft.hide(mDialpadFragment); 834 ft.commit(); 835 } 836 mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY); 837 } 838 839 private void updateSearchFragmentPosition() { 840 SearchFragment fragment = null; 841 if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { 842 fragment = mSmartDialSearchFragment; 843 } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { 844 fragment = mRegularSearchFragment; 845 } 846 if (fragment != null && fragment.isVisible()) { 847 fragment.updatePosition(true /* animate */); 848 } 849 } 850 851 @Override 852 public boolean isInSearchUi() { 853 return mInDialpadSearch || mInRegularSearch; 854 } 855 856 @Override 857 public boolean hasSearchQuery() { 858 return !TextUtils.isEmpty(mSearchQuery); 859 } 860 861 @Override 862 public boolean shouldShowActionBar() { 863 return mListsFragment.shouldShowActionBar(); 864 } 865 866 private void setNotInSearchUi() { 867 mInDialpadSearch = false; 868 mInRegularSearch = false; 869 } 870 871 private void hideDialpadAndSearchUi() { 872 if (mIsDialpadShown) { 873 hideDialpadFragment(false, true); 874 } else { 875 exitSearchUi(); 876 } 877 } 878 879 private void prepareVoiceSearchButton() { 880 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 881 if (canIntentBeHandled(voiceIntent)) { 882 mVoiceSearchButton.setVisibility(View.VISIBLE); 883 mVoiceSearchButton.setOnClickListener(this); 884 } else { 885 mVoiceSearchButton.setVisibility(View.GONE); 886 } 887 } 888 889 protected int getSearchBoxHint () { 890 return R.string.dialer_hint_find_contact; 891 } 892 893 /** 894 * Sets the hint text for the contacts search box 895 */ 896 private void setSearchBoxHint() { 897 SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) getSupportActionBar() 898 .getCustomView().findViewById(R.id.search_view_container); 899 ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search)) 900 .setHint(getSearchBoxHint()); 901 } 902 903 protected OptionsPopupMenu buildOptionsMenu(View invoker) { 904 final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker); 905 popupMenu.inflate(R.menu.dialtacts_options); 906 popupMenu.setOnMenuItemClickListener(this); 907 return popupMenu; 908 } 909 910 @Override 911 public boolean onCreateOptionsMenu(Menu menu) { 912 if (mPendingSearchViewQuery != null) { 913 mSearchView.setText(mPendingSearchViewQuery); 914 mPendingSearchViewQuery = null; 915 } 916 if (mActionBarController != null) { 917 mActionBarController.restoreActionBarOffset(); 918 } 919 return false; 920 } 921 922 /** 923 * Returns true if the intent is due to hitting the green send key (hardware call button: 924 * KEYCODE_CALL) while in a call. 925 * 926 * @param intent the intent that launched this activity 927 * @return true if the intent is due to hitting the green send key while in a call 928 */ 929 private boolean isSendKeyWhileInCall(Intent intent) { 930 // If there is a call in progress and the user launched the dialer by hitting the call 931 // button, go straight to the in-call screen. 932 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); 933 934 if (callKey) { 935 TelecomUtil.showInCallScreen(this, false); 936 return true; 937 } 938 939 return false; 940 } 941 942 /** 943 * Sets the current tab based on the intent's request type 944 * 945 * @param intent Intent that contains information about which tab should be selected 946 */ 947 private void displayFragment(Intent intent) { 948 // If we got here by hitting send and we're in call forward along to the in-call activity 949 if (isSendKeyWhileInCall(intent)) { 950 finish(); 951 return; 952 } 953 954 final boolean showDialpadChooser = phoneIsInUse() && !DialpadFragment.isAddCallMode(intent); 955 if (showDialpadChooser || (intent.getData() != null && isDialIntent(intent))) { 956 showDialpadFragment(false); 957 mDialpadFragment.setStartedFromNewIntent(true); 958 if (showDialpadChooser && !mDialpadFragment.isVisible()) { 959 mInCallDialpadUp = true; 960 } 961 } 962 } 963 964 @Override 965 public void onNewIntent(Intent newIntent) { 966 setIntent(newIntent); 967 968 mStateSaved = false; 969 displayFragment(newIntent); 970 971 invalidateOptionsMenu(); 972 } 973 974 /** Returns true if the given intent contains a phone number to populate the dialer with */ 975 private boolean isDialIntent(Intent intent) { 976 final String action = intent.getAction(); 977 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 978 return true; 979 } 980 if (Intent.ACTION_VIEW.equals(action)) { 981 final Uri data = intent.getData(); 982 if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) { 983 return true; 984 } 985 } 986 return false; 987 } 988 989 /** 990 * Shows the search fragment 991 */ 992 private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) { 993 if (mStateSaved || getFragmentManager().isDestroyed()) { 994 // Weird race condition where fragment is doing work after the activity is destroyed 995 // due to talkback being on (b/10209937). Just return since we can't do any 996 // constructive here. 997 return; 998 } 999 1000 if (DEBUG) { 1001 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 1002 } 1003 1004 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1005 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 1006 transaction.remove(mSmartDialSearchFragment); 1007 } else if (mInRegularSearch && mRegularSearchFragment != null) { 1008 transaction.remove(mRegularSearchFragment); 1009 } 1010 1011 final String tag; 1012 if (smartDialSearch) { 1013 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 1014 } else { 1015 tag = TAG_REGULAR_SEARCH_FRAGMENT; 1016 } 1017 mInDialpadSearch = smartDialSearch; 1018 mInRegularSearch = !smartDialSearch; 1019 1020 mFloatingActionButtonController.scaleOut(); 1021 1022 SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 1023 if (animate) { 1024 transaction.setCustomAnimations(android.R.animator.fade_in, 0); 1025 } else { 1026 transaction.setTransition(FragmentTransaction.TRANSIT_NONE); 1027 } 1028 if (fragment == null) { 1029 if (smartDialSearch) { 1030 fragment = new SmartDialSearchFragment(); 1031 } else { 1032 fragment = new RegularSearchFragment(); 1033 fragment.setOnTouchListener(new View.OnTouchListener() { 1034 @Override 1035 public boolean onTouch(View v, MotionEvent event) { 1036 // Show the FAB when the user touches the lists fragment and the soft 1037 // keyboard is hidden. 1038 showFabInSearchUi(); 1039 return false; 1040 } 1041 }); 1042 } 1043 transaction.add(R.id.dialtacts_frame, fragment, tag); 1044 } else { 1045 transaction.show(fragment); 1046 } 1047 // DialtactsActivity will provide the options menu 1048 fragment.setHasOptionsMenu(false); 1049 fragment.setShowEmptyListForNullQuery(true); 1050 if (!smartDialSearch) { 1051 fragment.setQueryString(query, false /* delaySelection */); 1052 } 1053 transaction.commit(); 1054 1055 if (animate) { 1056 mListsFragment.getView().animate().alpha(0).withLayer(); 1057 } 1058 mListsFragment.setUserVisibleHint(false); 1059 } 1060 1061 /** 1062 * Hides the search fragment 1063 */ 1064 private void exitSearchUi() { 1065 // See related bug in enterSearchUI(); 1066 if (getFragmentManager().isDestroyed() || mStateSaved) { 1067 return; 1068 } 1069 1070 mSearchView.setText(null); 1071 1072 if (mDialpadFragment != null) { 1073 mDialpadFragment.clearDialpad(); 1074 } 1075 1076 setNotInSearchUi(); 1077 1078 // Restore the FAB for the lists fragment. 1079 if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) { 1080 mFloatingActionButtonController.setVisible(false); 1081 } 1082 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); 1083 onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */); 1084 onPageSelected(mListsFragment.getCurrentTabIndex()); 1085 1086 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1087 if (mSmartDialSearchFragment != null) { 1088 transaction.remove(mSmartDialSearchFragment); 1089 } 1090 if (mRegularSearchFragment != null) { 1091 transaction.remove(mRegularSearchFragment); 1092 } 1093 transaction.commit(); 1094 1095 mListsFragment.getView().animate().alpha(1).withLayer(); 1096 1097 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 1098 // If the dialpad fragment wasn't previously visible, then send a screen view because 1099 // we are exiting regular search. Otherwise, the screen view will be sent by 1100 // {@link #hideDialpadFragment}. 1101 mListsFragment.sendScreenViewForCurrentPosition(); 1102 mListsFragment.setUserVisibleHint(true); 1103 } 1104 1105 mActionBarController.onSearchUiExited(); 1106 } 1107 1108 @Override 1109 public void onBackPressed() { 1110 if (mStateSaved) { 1111 return; 1112 } 1113 if (mIsDialpadShown) { 1114 if (TextUtils.isEmpty(mSearchQuery) || 1115 (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible() 1116 && mSmartDialSearchFragment.getAdapter().getCount() == 0)) { 1117 exitSearchUi(); 1118 } 1119 hideDialpadFragment(true, false); 1120 } else if (isInSearchUi()) { 1121 exitSearchUi(); 1122 DialerUtils.hideInputMethod(mParentLayout); 1123 } else { 1124 super.onBackPressed(); 1125 } 1126 } 1127 1128 private void maybeEnterSearchUi() { 1129 if (!isInSearchUi()) { 1130 enterSearchUi(true /* isSmartDial */, mSearchQuery, false); 1131 } 1132 } 1133 1134 /** 1135 * @return True if the search UI was exited, false otherwise 1136 */ 1137 private boolean maybeExitSearchUi() { 1138 if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) { 1139 exitSearchUi(); 1140 DialerUtils.hideInputMethod(mParentLayout); 1141 return true; 1142 } 1143 return false; 1144 } 1145 1146 private void showFabInSearchUi() { 1147 mFloatingActionButtonController.changeIcon( 1148 getResources().getDrawable(R.drawable.fab_ic_dial), 1149 getResources().getString(R.string.action_menu_dialpad_button)); 1150 mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); 1151 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); 1152 } 1153 1154 @Override 1155 public void onDialpadQueryChanged(String query) { 1156 if (mSmartDialSearchFragment != null) { 1157 mSmartDialSearchFragment.setAddToContactNumber(query); 1158 } 1159 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 1160 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 1161 1162 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 1163 if (DEBUG) { 1164 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 1165 } 1166 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 1167 // This callback can happen if the dialpad fragment is recreated because of 1168 // activity destruction. In that case, don't update the search view because 1169 // that would bring the user back to the search fragment regardless of the 1170 // previous state of the application. Instead, just return here and let the 1171 // fragment manager correctly figure out whatever fragment was last displayed. 1172 if (!TextUtils.isEmpty(normalizedQuery)) { 1173 mPendingSearchViewQuery = normalizedQuery; 1174 } 1175 return; 1176 } 1177 mSearchView.setText(normalizedQuery); 1178 } 1179 1180 try { 1181 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 1182 mDialpadFragment.process_quote_emergency_unquote(normalizedQuery); 1183 } 1184 } catch (Exception ignored) { 1185 // Skip any exceptions for this piece of code 1186 } 1187 } 1188 1189 @Override 1190 public boolean onDialpadSpacerTouchWithEmptyQuery() { 1191 if (mInDialpadSearch && mSmartDialSearchFragment != null 1192 && !mSmartDialSearchFragment.isShowingPermissionRequest()) { 1193 hideDialpadFragment(true /* animate */, true /* clearDialpad */); 1194 return true; 1195 } 1196 return false; 1197 } 1198 1199 @Override 1200 public void onListFragmentScrollStateChange(int scrollState) { 1201 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 1202 hideDialpadFragment(true, false); 1203 DialerUtils.hideInputMethod(mParentLayout); 1204 } 1205 } 1206 1207 @Override 1208 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 1209 int totalItemCount) { 1210 // TODO: No-op for now. This should eventually show/hide the actionBar based on 1211 // interactions with the ListsFragments. 1212 } 1213 1214 private boolean phoneIsInUse() { 1215 return TelecomUtil.isInCall(this); 1216 } 1217 1218 private boolean canIntentBeHandled(Intent intent) { 1219 final PackageManager packageManager = getPackageManager(); 1220 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 1221 PackageManager.MATCH_DEFAULT_ONLY); 1222 return resolveInfo != null && resolveInfo.size() > 0; 1223 } 1224 1225 /** 1226 * Called when the user has long-pressed a contact tile to start a drag operation. 1227 */ 1228 @Override 1229 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 1230 mListsFragment.showRemoveView(true); 1231 } 1232 1233 @Override 1234 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 1235 } 1236 1237 /** 1238 * Called when the user has released a contact tile after long-pressing it. 1239 */ 1240 @Override 1241 public void onDragFinished(int x, int y) { 1242 mListsFragment.showRemoveView(false); 1243 } 1244 1245 @Override 1246 public void onDroppedOnRemove() {} 1247 1248 /** 1249 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 1250 * once it has been attached to the activity. 1251 */ 1252 @Override 1253 public void setDragDropController(DragDropController dragController) { 1254 mDragDropController = dragController; 1255 mListsFragment.getRemoveView().setDragDropController(dragController); 1256 } 1257 1258 /** 1259 * Implemented to satisfy {@link SpeedDialFragment.HostInterface} 1260 */ 1261 @Override 1262 public void showAllContactsTab() { 1263 if (mListsFragment != null) { 1264 mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS); 1265 } 1266 } 1267 1268 /** 1269 * Implemented to satisfy {@link CallLogFragment.HostInterface} 1270 */ 1271 @Override 1272 public void showDialpad() { 1273 showDialpadFragment(true); 1274 } 1275 1276 @Override 1277 public void onPickPhoneNumberAction(Uri dataUri, int callInitiationType) { 1278 mClearSearchOnPause = true; 1279 PhoneNumberInteraction.startInteractionForPhoneCall( 1280 DialtactsActivity.this, dataUri, callInitiationType); 1281 } 1282 1283 @Override 1284 public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall, 1285 int callInitiationType) { 1286 if (phoneNumber == null) { 1287 // Invalid phone number, but let the call go through so that InCallUI can show 1288 // an error message. 1289 phoneNumber = ""; 1290 } 1291 1292 final Intent intent = new CallIntentBuilder(phoneNumber) 1293 .setIsVideoCall(isVideoCall) 1294 .setCallInitiationType(callInitiationType) 1295 .build(); 1296 1297 DialerUtils.startActivityWithErrorToast(this, intent); 1298 mClearSearchOnPause = true; 1299 } 1300 1301 @Override 1302 public void onShortcutIntentCreated(Intent intent) { 1303 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 1304 } 1305 1306 @Override 1307 public void onHomeInActionBarSelected() { 1308 exitSearchUi(); 1309 } 1310 1311 @Override 1312 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 1313 int tabIndex = mListsFragment.getCurrentTabIndex(); 1314 1315 // Scroll the button from center to end when moving from the Speed Dial to Call History tab. 1316 // In RTL, scroll when the current tab is Call History instead, since the order of the tabs 1317 // is reversed and the ViewPager returns the left tab position during scroll. 1318 boolean isRtl = DialerUtils.isRtl(); 1319 if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) { 1320 mFloatingActionButtonController.onPageScrolled(positionOffset); 1321 } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_HISTORY && !mIsLandscape) { 1322 mFloatingActionButtonController.onPageScrolled(1 - positionOffset); 1323 } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) { 1324 mFloatingActionButtonController.onPageScrolled(1); 1325 } 1326 } 1327 1328 @Override 1329 public void onPageSelected(int position) { 1330 int tabIndex = mListsFragment.getCurrentTabIndex(); 1331 if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) { 1332 mFloatingActionButtonController.changeIcon( 1333 getResources().getDrawable(R.drawable.ic_person_add_24dp), 1334 getResources().getString(R.string.search_shortcut_create_new_contact)); 1335 } else { 1336 mFloatingActionButtonController.changeIcon( 1337 getResources().getDrawable(R.drawable.fab_ic_dial), 1338 getResources().getString(R.string.action_menu_dialpad_button)); 1339 } 1340 } 1341 1342 @Override 1343 public void onPageScrollStateChanged(int state) { 1344 } 1345 1346 @Override 1347 public boolean isActionBarShowing() { 1348 return mActionBarController.isActionBarShowing(); 1349 } 1350 1351 @Override 1352 public ActionBarController getActionBarController() { 1353 return mActionBarController; 1354 } 1355 1356 @Override 1357 public boolean isDialpadShown() { 1358 return mIsDialpadShown; 1359 } 1360 1361 @Override 1362 public int getDialpadHeight() { 1363 if (mDialpadFragment != null) { 1364 return mDialpadFragment.getDialpadHeight(); 1365 } 1366 return 0; 1367 } 1368 1369 @Override 1370 public int getActionBarHideOffset() { 1371 return getSupportActionBar().getHideOffset(); 1372 } 1373 1374 @Override 1375 public void setActionBarHideOffset(int offset) { 1376 getSupportActionBar().setHideOffset(offset); 1377 } 1378 1379 @Override 1380 public int getActionBarHeight() { 1381 return mActionBarHeight; 1382 } 1383 1384 private int getFabAlignment() { 1385 if (!mIsLandscape && !isInSearchUi() && 1386 mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1387 return FloatingActionButtonController.ALIGN_MIDDLE; 1388 } 1389 return FloatingActionButtonController.ALIGN_END; 1390 } 1391} 1392