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