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