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