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 17 package com.android.dialer; 18 19 import android.app.ActionBar; 20 import android.app.Fragment; 21 import android.app.FragmentTransaction; 22 import android.content.ActivityNotFoundException; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.content.res.Configuration; 28 import android.content.res.Resources; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Trace; 32 import android.provider.CallLog.Calls; 33 import android.speech.RecognizerIntent; 34 import android.support.v4.view.ViewPager; 35 import android.telecom.PhoneAccount; 36 import android.telecom.TelecomManager; 37 import android.text.Editable; 38 import android.text.TextUtils; 39 import android.text.TextWatcher; 40 import android.util.Log; 41 import android.view.DragEvent; 42 import android.view.Gravity; 43 import android.view.KeyEvent; 44 import android.view.Menu; 45 import android.view.MenuItem; 46 import android.view.MotionEvent; 47 import android.view.View; 48 import android.view.View.OnDragListener; 49 import android.view.View.OnTouchListener; 50 import android.view.ViewTreeObserver; 51 import android.view.animation.Animation; 52 import android.view.animation.AnimationUtils; 53 import android.widget.AbsListView.OnScrollListener; 54 import android.widget.EditText; 55 import android.widget.FrameLayout; 56 import android.widget.ImageButton; 57 import android.widget.PopupMenu; 58 import android.widget.Toast; 59 60 import com.android.contacts.common.activity.TransactionSafeActivity; 61 import com.android.contacts.common.dialog.ClearFrequentsDialog; 62 import com.android.contacts.common.interactions.ImportExportDialogFragment; 63 import com.android.contacts.common.interactions.TouchPointManager; 64 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 65 import com.android.contacts.common.util.PermissionsUtil; 66 import com.android.contacts.common.widget.FloatingActionButtonController; 67 import com.android.contacts.commonbind.analytics.AnalyticsUtil; 68 import com.android.dialer.calllog.CallLogActivity; 69 import com.android.dialer.calllog.CallLogFragment; 70 import com.android.dialer.database.DialerDatabaseHelper; 71 import com.android.dialer.dialpad.DialpadFragment; 72 import com.android.dialer.dialpad.SmartDialNameMatcher; 73 import com.android.dialer.dialpad.SmartDialPrefix; 74 import com.android.dialer.interactions.PhoneNumberInteraction; 75 import com.android.dialer.list.DragDropController; 76 import com.android.dialer.list.ListsFragment; 77 import com.android.dialer.list.OnDragDropListener; 78 import com.android.dialer.list.OnListFragmentScrolledListener; 79 import com.android.dialer.list.PhoneFavoriteSquareTileView; 80 import com.android.dialer.list.RegularSearchFragment; 81 import com.android.dialer.list.SearchFragment; 82 import com.android.dialer.list.SmartDialSearchFragment; 83 import com.android.dialer.list.SpeedDialFragment; 84 import com.android.dialer.settings.DialerSettingsActivity; 85 import com.android.dialer.util.IntentUtil; 86 import com.android.dialer.util.DialerUtils; 87 import com.android.dialer.widget.ActionBarController; 88 import com.android.dialer.widget.SearchEditTextLayout; 89 import com.android.dialer.widget.SearchEditTextLayout.Callback; 90 import com.android.dialerbind.DatabaseHelperManager; 91 import com.android.phone.common.animation.AnimUtils; 92 import com.android.phone.common.animation.AnimationListenerAdapter; 93 94 import junit.framework.Assert; 95 96 import java.util.ArrayList; 97 import java.util.List; 98 99 /** 100 * The dialer tab's title is 'phone', a more common name (see strings.xml). 101 */ 102 public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, 103 DialpadFragment.OnDialpadQueryChangedListener, 104 OnListFragmentScrolledListener, 105 CallLogFragment.HostInterface, 106 DialpadFragment.HostInterface, 107 ListsFragment.HostInterface, 108 SpeedDialFragment.HostInterface, 109 SearchFragment.HostInterface, 110 OnDragDropListener, 111 OnPhoneNumberPickerActionListener, 112 PopupMenu.OnMenuItemClickListener, 113 ViewPager.OnPageChangeListener, 114 ActionBarController.ActivityUi { 115 private static final String TAG = "DialtactsActivity"; 116 117 public static final boolean DEBUG = false; 118 119 public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; 120 121 /** @see #getCallOrigin() */ 122 private static final String CALL_ORIGIN_DIALTACTS = 123 "com.android.dialer.DialtactsActivity"; 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 private 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 FrameLayout 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, recents list, and all contacts list. 192 */ 193 private ListsFragment mListsFragment; 194 195 /** 196 * Tracks whether onSaveInstanceState has been called. If true, no fragment transactions can 197 * be commited. 198 */ 199 private boolean mStateSaved; 200 private boolean mIsRestarting; 201 private boolean mInDialpadSearch; 202 private boolean mInRegularSearch; 203 private boolean mClearSearchOnPause; 204 private boolean mIsDialpadShown; 205 private boolean mShowDialpadOnResume; 206 207 /** 208 * Whether or not the device is in landscape orientation. 209 */ 210 private boolean mIsLandscape; 211 212 /** 213 * True if the dialpad is only temporarily showing due to being in call 214 */ 215 private boolean mInCallDialpadUp; 216 217 /** 218 * True when this activity has been launched for the first time. 219 */ 220 private boolean mFirstLaunch; 221 222 /** 223 * Search query to be applied to the SearchView in the ActionBar once 224 * onCreateOptionsMenu has been called. 225 */ 226 private String mPendingSearchViewQuery; 227 228 private PopupMenu mOverflowMenu; 229 private EditText mSearchView; 230 private View mVoiceSearchButton; 231 232 private String mSearchQuery; 233 234 private DialerDatabaseHelper mDialerDatabaseHelper; 235 private DragDropController mDragDropController; 236 private ActionBarController mActionBarController; 237 238 private FloatingActionButtonController mFloatingActionButtonController; 239 240 private int mActionBarHeight; 241 242 /** 243 * The text returned from a voice search query. Set in {@link #onActivityResult} and used in 244 * {@link #onResume()} to populate the search box. 245 */ 246 private String mVoiceSearchQuery; 247 248 protected class OptionsPopupMenu extends PopupMenu { OptionsPopupMenu(Context context, View anchor)249 public OptionsPopupMenu(Context context, View anchor) { 250 super(context, anchor, Gravity.END); 251 } 252 253 @Override show()254 public void show() { 255 final boolean hasContactsPermission = 256 PermissionsUtil.hasContactsPermissions(DialtactsActivity.this); 257 final Menu menu = getMenu(); 258 final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); 259 clearFrequents.setVisible(mListsFragment != null && 260 mListsFragment.getSpeedDialFragment() != null && 261 mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission); 262 263 menu.findItem(R.id.menu_import_export).setVisible(hasContactsPermission); 264 menu.findItem(R.id.menu_add_contact).setVisible(hasContactsPermission); 265 266 menu.findItem(R.id.menu_history).setVisible( 267 PermissionsUtil.hasPhonePermissions(DialtactsActivity.this)); 268 super.show(); 269 } 270 } 271 272 /** 273 * Listener that listens to drag events and sends their x and y coordinates to a 274 * {@link DragDropController}. 275 */ 276 private class LayoutOnDragListener implements OnDragListener { 277 @Override onDrag(View v, DragEvent event)278 public boolean onDrag(View v, DragEvent event) { 279 if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { 280 mDragDropController.handleDragHovered(v, (int) event.getX(), (int) event.getY()); 281 } 282 return true; 283 } 284 } 285 286 /** 287 * Listener used to send search queries to the phone search fragment. 288 */ 289 private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { 290 @Override 291 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 292 } 293 294 @Override 295 public void onTextChanged(CharSequence s, int start, int before, int count) { 296 final String newText = s.toString(); 297 if (newText.equals(mSearchQuery)) { 298 // If the query hasn't changed (perhaps due to activity being destroyed 299 // and restored, or user launching the same DIAL intent twice), then there is 300 // no need to do anything here. 301 return; 302 } 303 if (DEBUG) { 304 Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText); 305 Log.d(TAG, "Previous Query: " + mSearchQuery); 306 } 307 mSearchQuery = newText; 308 309 // Show search fragment only when the query string is changed to non-empty text. 310 if (!TextUtils.isEmpty(newText)) { 311 // Call enterSearchUi only if we are switching search modes, or showing a search 312 // fragment for the first time. 313 final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) || 314 (!mIsDialpadShown && mInRegularSearch); 315 if (!sameSearchMode) { 316 enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */); 317 } 318 } 319 320 if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { 321 mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */); 322 } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { 323 mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */); 324 } 325 } 326 327 @Override 328 public void afterTextChanged(Editable s) { 329 } 330 }; 331 332 333 /** 334 * Open the search UI when the user clicks on the search box. 335 */ 336 private final View.OnClickListener mSearchViewOnClickListener = new View.OnClickListener() { 337 @Override 338 public void onClick(View v) { 339 if (!isInSearchUi()) { 340 mActionBarController.onSearchBoxTapped(); 341 enterSearchUi(false /* smartDialSearch */, mSearchView.getText().toString(), 342 true /* animate */); 343 } 344 } 345 }; 346 347 /** 348 * Handles the user closing the soft keyboard. 349 */ 350 private final View.OnKeyListener mSearchEditTextLayoutListener = new View.OnKeyListener() { 351 @Override 352 public boolean onKey(View v, int keyCode, KeyEvent event) { 353 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { 354 if (TextUtils.isEmpty(mSearchView.getText().toString())) { 355 // If the search term is empty, close the search UI. 356 maybeExitSearchUi(); 357 } else { 358 // If the search term is not empty, show the dialpad fab. 359 showFabInSearchUi(); 360 } 361 } 362 return false; 363 } 364 }; 365 366 @Override dispatchTouchEvent(MotionEvent ev)367 public boolean dispatchTouchEvent(MotionEvent ev) { 368 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 369 TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY()); 370 } 371 return super.dispatchTouchEvent(ev); 372 373 } 374 375 @Override onCreate(Bundle savedInstanceState)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 = getActionBar(); 392 actionBar.setCustomView(R.layout.search_edittext); 393 actionBar.setDisplayShowCustomEnabled(true); 394 actionBar.setBackgroundDrawable(null); 395 396 SearchEditTextLayout searchEditTextLayout = 397 (SearchEditTextLayout) actionBar.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 = (FrameLayout) 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 onResume()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 AnalyticsUtil.sendScreenView(mDialpadFragment, this); 531 } 532 mIsRestarting = false; 533 } 534 535 prepareVoiceSearchButton(); 536 mDialerDatabaseHelper.startSmartDialUpdateThread(); 537 mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); 538 539 if (getIntent().hasExtra(EXTRA_SHOW_TAB)) { 540 int index = getIntent().getIntExtra(EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_SPEED_DIAL); 541 if (index < mListsFragment.getTabCount()) { 542 mListsFragment.showTab(index); 543 } 544 } else if (Calls.CONTENT_TYPE.equals(getIntent().getType())) { 545 mListsFragment.showTab(ListsFragment.TAB_INDEX_RECENTS); 546 } 547 548 Trace.endSection(); 549 } 550 551 @Override onRestart()552 protected void onRestart() { 553 super.onRestart(); 554 mIsRestarting = true; 555 } 556 557 @Override onPause()558 protected void onPause() { 559 if (mClearSearchOnPause) { 560 hideDialpadAndSearchUi(); 561 mClearSearchOnPause = false; 562 } 563 if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) { 564 commitDialpadFragmentHide(); 565 } 566 super.onPause(); 567 } 568 569 @Override onSaveInstanceState(Bundle outState)570 protected void onSaveInstanceState(Bundle outState) { 571 super.onSaveInstanceState(outState); 572 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 573 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 574 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 575 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 576 outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown); 577 mActionBarController.saveInstanceState(outState); 578 mStateSaved = true; 579 } 580 581 @Override onAttachFragment(Fragment fragment)582 public void onAttachFragment(Fragment fragment) { 583 if (fragment instanceof DialpadFragment) { 584 mDialpadFragment = (DialpadFragment) fragment; 585 if (!mIsDialpadShown && !mShowDialpadOnResume) { 586 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 587 transaction.hide(mDialpadFragment); 588 transaction.commit(); 589 } 590 } else if (fragment instanceof SmartDialSearchFragment) { 591 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 592 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); 593 } else if (fragment instanceof SearchFragment) { 594 mRegularSearchFragment = (RegularSearchFragment) fragment; 595 mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this); 596 } else if (fragment instanceof ListsFragment) { 597 mListsFragment = (ListsFragment) fragment; 598 mListsFragment.addOnPageChangeListener(this); 599 } 600 } 601 handleMenuSettings()602 protected void handleMenuSettings() { 603 final Intent intent = new Intent(this, DialerSettingsActivity.class); 604 startActivity(intent); 605 } 606 607 @Override onClick(View view)608 public void onClick(View view) { 609 switch (view.getId()) { 610 case R.id.floating_action_button: 611 if (mListsFragment.getCurrentTabIndex() 612 == ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch) { 613 DialerUtils.startActivityWithErrorToast( 614 this, 615 IntentUtil.getNewContactIntent(), 616 R.string.add_contact_not_available); 617 } else if (!mIsDialpadShown) { 618 mInCallDialpadUp = false; 619 showDialpadFragment(true); 620 } 621 break; 622 case R.id.voice_search_button: 623 try { 624 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 625 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 626 } catch (ActivityNotFoundException e) { 627 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 628 Toast.LENGTH_SHORT).show(); 629 } 630 break; 631 case R.id.dialtacts_options_menu_button: 632 mOverflowMenu.show(); 633 break; 634 default: { 635 Log.wtf(TAG, "Unexpected onClick event from " + view); 636 break; 637 } 638 } 639 } 640 641 @Override onMenuItemClick(MenuItem item)642 public boolean onMenuItemClick(MenuItem item) { 643 switch (item.getItemId()) { 644 case R.id.menu_history: 645 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 646 // CONTENT_TYPE, so that we always open our call log from our dialer 647 final Intent intent = new Intent(this, CallLogActivity.class); 648 startActivity(intent); 649 break; 650 case R.id.menu_add_contact: 651 DialerUtils.startActivityWithErrorToast( 652 this, 653 IntentUtil.getNewContactIntent(), 654 R.string.add_contact_not_available); 655 break; 656 case R.id.menu_import_export: 657 // We hard-code the "contactsAreAvailable" argument because doing it properly would 658 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 659 // now in Dialtacts for (potential) performance reasons. Compare with how it is 660 // done in {@link PeopleActivity}. 661 ImportExportDialogFragment.show(getFragmentManager(), true, 662 DialtactsActivity.class); 663 return true; 664 case R.id.menu_clear_frequents: 665 ClearFrequentsDialog.show(getFragmentManager()); 666 return true; 667 case R.id.menu_call_settings: 668 handleMenuSettings(); 669 return true; 670 } 671 return false; 672 } 673 674 @Override onActivityResult(int requestCode, int resultCode, Intent data)675 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 676 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 677 if (resultCode == RESULT_OK) { 678 final ArrayList<String> matches = data.getStringArrayListExtra( 679 RecognizerIntent.EXTRA_RESULTS); 680 if (matches.size() > 0) { 681 final String match = matches.get(0); 682 mVoiceSearchQuery = match; 683 } else { 684 Log.e(TAG, "Voice search - nothing heard"); 685 } 686 } else { 687 Log.e(TAG, "Voice search failed"); 688 } 689 } 690 super.onActivityResult(requestCode, resultCode, data); 691 } 692 693 /** 694 * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual 695 * updates are handled by a callback which is invoked after the dialpad fragment is shown. 696 * @see #onDialpadShown 697 */ showDialpadFragment(boolean animate)698 private void showDialpadFragment(boolean animate) { 699 if (mIsDialpadShown || mStateSaved) { 700 return; 701 } 702 mIsDialpadShown = true; 703 704 mListsFragment.setUserVisibleHint(false); 705 706 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 707 if (mDialpadFragment == null) { 708 mDialpadFragment = new DialpadFragment(); 709 ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT); 710 } else { 711 ft.show(mDialpadFragment); 712 } 713 714 mDialpadFragment.setAnimate(animate); 715 AnalyticsUtil.sendScreenView(mDialpadFragment); 716 ft.commit(); 717 718 if (animate) { 719 mFloatingActionButtonController.scaleOut(); 720 } else { 721 mFloatingActionButtonController.setVisible(false); 722 maybeEnterSearchUi(); 723 } 724 mActionBarController.onDialpadUp(); 725 726 mListsFragment.getView().animate().alpha(0).withLayer(); 727 } 728 729 /** 730 * Callback from child DialpadFragment when the dialpad is shown. 731 */ onDialpadShown()732 public void onDialpadShown() { 733 Assert.assertNotNull(mDialpadFragment); 734 if (mDialpadFragment.getAnimate()) { 735 mDialpadFragment.getView().startAnimation(mSlideIn); 736 } else { 737 mDialpadFragment.setYFraction(0); 738 } 739 740 updateSearchFragmentPosition(); 741 } 742 743 /** 744 * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in 745 * a callback after the hide animation ends. 746 * @see #commitDialpadFragmentHide 747 */ hideDialpadFragment(boolean animate, boolean clearDialpad)748 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 749 if (mDialpadFragment == null || mDialpadFragment.getView() == null) { 750 return; 751 } 752 if (clearDialpad) { 753 mDialpadFragment.clearDialpad(); 754 } 755 if (!mIsDialpadShown) { 756 return; 757 } 758 mIsDialpadShown = false; 759 mDialpadFragment.setAnimate(animate); 760 mListsFragment.setUserVisibleHint(true); 761 mListsFragment.sendScreenViewForCurrentPosition(); 762 763 updateSearchFragmentPosition(); 764 765 mFloatingActionButtonController.align(getFabAlignment(), animate); 766 if (animate) { 767 mDialpadFragment.getView().startAnimation(mSlideOut); 768 } else { 769 commitDialpadFragmentHide(); 770 } 771 772 mActionBarController.onDialpadDown(); 773 774 if (isInSearchUi()) { 775 if (TextUtils.isEmpty(mSearchQuery)) { 776 exitSearchUi(); 777 } 778 } 779 } 780 781 /** 782 * Finishes hiding the dialpad fragment after any animations are completed. 783 */ commitDialpadFragmentHide()784 private void commitDialpadFragmentHide() { 785 if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) { 786 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 787 ft.hide(mDialpadFragment); 788 ft.commit(); 789 } 790 mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY); 791 } 792 updateSearchFragmentPosition()793 private void updateSearchFragmentPosition() { 794 SearchFragment fragment = null; 795 if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { 796 fragment = mSmartDialSearchFragment; 797 } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { 798 fragment = mRegularSearchFragment; 799 } 800 if (fragment != null && fragment.isVisible()) { 801 fragment.updatePosition(true /* animate */); 802 } 803 } 804 805 @Override isInSearchUi()806 public boolean isInSearchUi() { 807 return mInDialpadSearch || mInRegularSearch; 808 } 809 810 @Override hasSearchQuery()811 public boolean hasSearchQuery() { 812 return !TextUtils.isEmpty(mSearchQuery); 813 } 814 815 @Override shouldShowActionBar()816 public boolean shouldShowActionBar() { 817 return mListsFragment.shouldShowActionBar(); 818 } 819 setNotInSearchUi()820 private void setNotInSearchUi() { 821 mInDialpadSearch = false; 822 mInRegularSearch = false; 823 } 824 hideDialpadAndSearchUi()825 private void hideDialpadAndSearchUi() { 826 if (mIsDialpadShown) { 827 hideDialpadFragment(false, true); 828 } else { 829 exitSearchUi(); 830 } 831 } 832 prepareVoiceSearchButton()833 private void prepareVoiceSearchButton() { 834 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 835 if (canIntentBeHandled(voiceIntent)) { 836 mVoiceSearchButton.setVisibility(View.VISIBLE); 837 mVoiceSearchButton.setOnClickListener(this); 838 } else { 839 mVoiceSearchButton.setVisibility(View.GONE); 840 } 841 } 842 buildOptionsMenu(View invoker)843 protected OptionsPopupMenu buildOptionsMenu(View invoker) { 844 final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker); 845 popupMenu.inflate(R.menu.dialtacts_options); 846 popupMenu.setOnMenuItemClickListener(this); 847 return popupMenu; 848 } 849 850 @Override onCreateOptionsMenu(Menu menu)851 public boolean onCreateOptionsMenu(Menu menu) { 852 if (mPendingSearchViewQuery != null) { 853 mSearchView.setText(mPendingSearchViewQuery); 854 mPendingSearchViewQuery = null; 855 } 856 if (mActionBarController != null) { 857 mActionBarController.restoreActionBarOffset(); 858 } 859 return false; 860 } 861 862 /** 863 * Returns true if the intent is due to hitting the green send key (hardware call button: 864 * KEYCODE_CALL) while in a call. 865 * 866 * @param intent the intent that launched this activity 867 * @return true if the intent is due to hitting the green send key while in a call 868 */ isSendKeyWhileInCall(Intent intent)869 private boolean isSendKeyWhileInCall(Intent intent) { 870 // If there is a call in progress and the user launched the dialer by hitting the call 871 // button, go straight to the in-call screen. 872 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); 873 874 if (callKey) { 875 getTelecomManager().showInCallScreen(false); 876 return true; 877 } 878 879 return false; 880 } 881 882 /** 883 * Sets the current tab based on the intent's request type 884 * 885 * @param intent Intent that contains information about which tab should be selected 886 */ displayFragment(Intent intent)887 private void displayFragment(Intent intent) { 888 // If we got here by hitting send and we're in call forward along to the in-call activity 889 if (isSendKeyWhileInCall(intent)) { 890 finish(); 891 return; 892 } 893 894 final boolean phoneIsInUse = phoneIsInUse(); 895 if (phoneIsInUse || (intent.getData() != null && isDialIntent(intent))) { 896 showDialpadFragment(false); 897 mDialpadFragment.setStartedFromNewIntent(true); 898 if (phoneIsInUse && !mDialpadFragment.isVisible()) { 899 mInCallDialpadUp = true; 900 } 901 } 902 } 903 904 @Override onNewIntent(Intent newIntent)905 public void onNewIntent(Intent newIntent) { 906 setIntent(newIntent); 907 908 mStateSaved = false; 909 displayFragment(newIntent); 910 911 invalidateOptionsMenu(); 912 } 913 914 /** Returns true if the given intent contains a phone number to populate the dialer with */ isDialIntent(Intent intent)915 private boolean isDialIntent(Intent intent) { 916 final String action = intent.getAction(); 917 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 918 return true; 919 } 920 if (Intent.ACTION_VIEW.equals(action)) { 921 final Uri data = intent.getData(); 922 if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) { 923 return true; 924 } 925 } 926 return false; 927 } 928 929 /** 930 * Returns an appropriate call origin for this Activity. May return null when no call origin 931 * should be used (e.g. when some 3rd party application launched the screen. Call origin is 932 * for remembering the tab in which the user made a phone call, so the external app's DIAL 933 * request should not be counted.) 934 */ getCallOrigin()935 public String getCallOrigin() { 936 return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null; 937 } 938 939 /** 940 * Shows the search fragment 941 */ enterSearchUi(boolean smartDialSearch, String query, boolean animate)942 private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) { 943 if (mStateSaved || getFragmentManager().isDestroyed()) { 944 // Weird race condition where fragment is doing work after the activity is destroyed 945 // due to talkback being on (b/10209937). Just return since we can't do any 946 // constructive here. 947 return; 948 } 949 950 if (DEBUG) { 951 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 952 } 953 954 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 955 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 956 transaction.remove(mSmartDialSearchFragment); 957 } else if (mInRegularSearch && mRegularSearchFragment != null) { 958 transaction.remove(mRegularSearchFragment); 959 } 960 961 final String tag; 962 if (smartDialSearch) { 963 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 964 } else { 965 tag = TAG_REGULAR_SEARCH_FRAGMENT; 966 } 967 mInDialpadSearch = smartDialSearch; 968 mInRegularSearch = !smartDialSearch; 969 970 mFloatingActionButtonController.scaleOut(); 971 972 SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 973 if (animate) { 974 transaction.setCustomAnimations(android.R.animator.fade_in, 0); 975 } else { 976 transaction.setTransition(FragmentTransaction.TRANSIT_NONE); 977 } 978 if (fragment == null) { 979 if (smartDialSearch) { 980 fragment = new SmartDialSearchFragment(); 981 } else { 982 fragment = new RegularSearchFragment(); 983 fragment.setOnTouchListener(new View.OnTouchListener() { 984 @Override 985 public boolean onTouch(View v, MotionEvent event) { 986 // Show the FAB when the user touches the lists fragment and the soft 987 // keyboard is hidden. 988 showFabInSearchUi(); 989 return false; 990 } 991 }); 992 } 993 transaction.add(R.id.dialtacts_frame, fragment, tag); 994 } else { 995 transaction.show(fragment); 996 } 997 // DialtactsActivity will provide the options menu 998 fragment.setHasOptionsMenu(false); 999 fragment.setShowEmptyListForNullQuery(true); 1000 if (!smartDialSearch) { 1001 fragment.setQueryString(query, false /* delaySelection */); 1002 } 1003 transaction.commit(); 1004 1005 if (animate) { 1006 mListsFragment.getView().animate().alpha(0).withLayer(); 1007 } 1008 mListsFragment.setUserVisibleHint(false); 1009 } 1010 1011 /** 1012 * Hides the search fragment 1013 */ exitSearchUi()1014 private void exitSearchUi() { 1015 // See related bug in enterSearchUI(); 1016 if (getFragmentManager().isDestroyed() || mStateSaved) { 1017 return; 1018 } 1019 1020 mSearchView.setText(null); 1021 1022 if (mDialpadFragment != null) { 1023 mDialpadFragment.clearDialpad(); 1024 } 1025 1026 setNotInSearchUi(); 1027 1028 // Restore the FAB for the lists fragment. 1029 if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) { 1030 mFloatingActionButtonController.setVisible(false); 1031 } 1032 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); 1033 onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */); 1034 onPageSelected(mListsFragment.getCurrentTabIndex()); 1035 1036 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1037 if (mSmartDialSearchFragment != null) { 1038 transaction.remove(mSmartDialSearchFragment); 1039 } 1040 if (mRegularSearchFragment != null) { 1041 transaction.remove(mRegularSearchFragment); 1042 } 1043 transaction.commit(); 1044 1045 mListsFragment.getView().animate().alpha(1).withLayer(); 1046 1047 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 1048 // If the dialpad fragment wasn't previously visible, then send a screen view because 1049 // we are exiting regular search. Otherwise, the screen view will be sent by 1050 // {@link #hideDialpadFragment}. 1051 mListsFragment.sendScreenViewForCurrentPosition(); 1052 mListsFragment.setUserVisibleHint(true); 1053 } 1054 1055 mActionBarController.onSearchUiExited(); 1056 } 1057 1058 @Override onBackPressed()1059 public void onBackPressed() { 1060 if (mStateSaved) { 1061 return; 1062 } 1063 if (mIsDialpadShown) { 1064 if (TextUtils.isEmpty(mSearchQuery) || 1065 (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible() 1066 && mSmartDialSearchFragment.getAdapter().getCount() == 0)) { 1067 exitSearchUi(); 1068 } 1069 hideDialpadFragment(true, false); 1070 } else if (isInSearchUi()) { 1071 exitSearchUi(); 1072 DialerUtils.hideInputMethod(mParentLayout); 1073 } else { 1074 super.onBackPressed(); 1075 } 1076 } 1077 maybeEnterSearchUi()1078 private void maybeEnterSearchUi() { 1079 if (!isInSearchUi()) { 1080 enterSearchUi(true /* isSmartDial */, mSearchQuery, false); 1081 } 1082 } 1083 1084 /** 1085 * @return True if the search UI was exited, false otherwise 1086 */ maybeExitSearchUi()1087 private boolean maybeExitSearchUi() { 1088 if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) { 1089 exitSearchUi(); 1090 DialerUtils.hideInputMethod(mParentLayout); 1091 return true; 1092 } 1093 return false; 1094 } 1095 showFabInSearchUi()1096 private void showFabInSearchUi() { 1097 mFloatingActionButtonController.changeIcon( 1098 getResources().getDrawable(R.drawable.fab_ic_dial), 1099 getResources().getString(R.string.action_menu_dialpad_button)); 1100 mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); 1101 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); 1102 } 1103 1104 @Override onDialpadQueryChanged(String query)1105 public void onDialpadQueryChanged(String query) { 1106 if (mSmartDialSearchFragment != null) { 1107 mSmartDialSearchFragment.setAddToContactNumber(query); 1108 } 1109 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 1110 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 1111 1112 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 1113 if (DEBUG) { 1114 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 1115 } 1116 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 1117 // This callback can happen if the dialpad fragment is recreated because of 1118 // activity destruction. In that case, don't update the search view because 1119 // that would bring the user back to the search fragment regardless of the 1120 // previous state of the application. Instead, just return here and let the 1121 // fragment manager correctly figure out whatever fragment was last displayed. 1122 if (!TextUtils.isEmpty(normalizedQuery)) { 1123 mPendingSearchViewQuery = normalizedQuery; 1124 } 1125 return; 1126 } 1127 mSearchView.setText(normalizedQuery); 1128 } 1129 1130 try { 1131 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 1132 mDialpadFragment.process_quote_emergency_unquote(normalizedQuery); 1133 } 1134 } catch (Exception ignored) { 1135 // Skip any exceptions for this piece of code 1136 } 1137 } 1138 1139 @Override onDialpadSpacerTouchWithEmptyQuery()1140 public boolean onDialpadSpacerTouchWithEmptyQuery() { 1141 if (mInDialpadSearch && mSmartDialSearchFragment != null 1142 && !mSmartDialSearchFragment.isShowingPermissionRequest()) { 1143 hideDialpadFragment(true /* animate */, true /* clearDialpad */); 1144 return true; 1145 } 1146 return false; 1147 } 1148 1149 @Override onListFragmentScrollStateChange(int scrollState)1150 public void onListFragmentScrollStateChange(int scrollState) { 1151 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 1152 hideDialpadFragment(true, false); 1153 DialerUtils.hideInputMethod(mParentLayout); 1154 } 1155 } 1156 1157 @Override onListFragmentScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)1158 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 1159 int totalItemCount) { 1160 // TODO: No-op for now. This should eventually show/hide the actionBar based on 1161 // interactions with the ListsFragments. 1162 } 1163 phoneIsInUse()1164 private boolean phoneIsInUse() { 1165 return getTelecomManager().isInCall(); 1166 } 1167 canIntentBeHandled(Intent intent)1168 private boolean canIntentBeHandled(Intent intent) { 1169 final PackageManager packageManager = getPackageManager(); 1170 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 1171 PackageManager.MATCH_DEFAULT_ONLY); 1172 return resolveInfo != null && resolveInfo.size() > 0; 1173 } 1174 1175 /** 1176 * Called when the user has long-pressed a contact tile to start a drag operation. 1177 */ 1178 @Override onDragStarted(int x, int y, PhoneFavoriteSquareTileView view)1179 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 1180 mListsFragment.showRemoveView(true); 1181 } 1182 1183 @Override onDragHovered(int x, int y, PhoneFavoriteSquareTileView view)1184 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 1185 } 1186 1187 /** 1188 * Called when the user has released a contact tile after long-pressing it. 1189 */ 1190 @Override onDragFinished(int x, int y)1191 public void onDragFinished(int x, int y) { 1192 mListsFragment.showRemoveView(false); 1193 } 1194 1195 @Override onDroppedOnRemove()1196 public void onDroppedOnRemove() {} 1197 1198 /** 1199 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 1200 * once it has been attached to the activity. 1201 */ 1202 @Override setDragDropController(DragDropController dragController)1203 public void setDragDropController(DragDropController dragController) { 1204 mDragDropController = dragController; 1205 mListsFragment.getRemoveView().setDragDropController(dragController); 1206 } 1207 1208 /** 1209 * Implemented to satisfy {@link SpeedDialFragment.HostInterface} 1210 */ 1211 @Override showAllContactsTab()1212 public void showAllContactsTab() { 1213 if (mListsFragment != null) { 1214 mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS); 1215 } 1216 } 1217 1218 /** 1219 * Implemented to satisfy {@link CallLogFragment.HostInterface} 1220 */ 1221 @Override showDialpad()1222 public void showDialpad() { 1223 showDialpadFragment(true); 1224 } 1225 1226 @Override onPickPhoneNumberAction(Uri dataUri)1227 public void onPickPhoneNumberAction(Uri dataUri) { 1228 // Specify call-origin so that users will see the previous tab instead of 1229 // CallLog screen (search UI will be automatically exited). 1230 PhoneNumberInteraction.startInteractionForPhoneCall( 1231 DialtactsActivity.this, dataUri, getCallOrigin()); 1232 mClearSearchOnPause = true; 1233 } 1234 1235 @Override onCallNumberDirectly(String phoneNumber)1236 public void onCallNumberDirectly(String phoneNumber) { 1237 onCallNumberDirectly(phoneNumber, false /* isVideoCall */); 1238 } 1239 1240 @Override onCallNumberDirectly(String phoneNumber, boolean isVideoCall)1241 public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall) { 1242 if (phoneNumber == null) { 1243 // Invalid phone number, but let the call go through so that InCallUI can show 1244 // an error message. 1245 phoneNumber = ""; 1246 } 1247 Intent intent = isVideoCall ? 1248 IntentUtil.getVideoCallIntent(phoneNumber, getCallOrigin()) : 1249 IntentUtil.getCallIntent(phoneNumber, getCallOrigin()); 1250 DialerUtils.startActivityWithErrorToast(this, intent); 1251 mClearSearchOnPause = true; 1252 } 1253 1254 @Override onShortcutIntentCreated(Intent intent)1255 public void onShortcutIntentCreated(Intent intent) { 1256 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 1257 } 1258 1259 @Override onHomeInActionBarSelected()1260 public void onHomeInActionBarSelected() { 1261 exitSearchUi(); 1262 } 1263 1264 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)1265 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 1266 int tabIndex = mListsFragment.getCurrentTabIndex(); 1267 1268 // Scroll the button from center to end when moving from the Speed Dial to Recents tab. 1269 // In RTL, scroll when the current tab is Recents instead of Speed Dial, because the order 1270 // of the tabs is reversed and the ViewPager returns the left tab position during scroll. 1271 boolean isRtl = DialerUtils.isRtl(); 1272 if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) { 1273 mFloatingActionButtonController.onPageScrolled(positionOffset); 1274 } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_RECENTS && !mIsLandscape) { 1275 mFloatingActionButtonController.onPageScrolled(1 - positionOffset); 1276 } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) { 1277 mFloatingActionButtonController.onPageScrolled(1); 1278 } 1279 } 1280 1281 @Override onPageSelected(int position)1282 public void onPageSelected(int position) { 1283 int tabIndex = mListsFragment.getCurrentTabIndex(); 1284 if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) { 1285 mFloatingActionButtonController.changeIcon( 1286 getResources().getDrawable(R.drawable.ic_person_add_24dp), 1287 getResources().getString(R.string.search_shortcut_create_new_contact)); 1288 } else { 1289 mFloatingActionButtonController.changeIcon( 1290 getResources().getDrawable(R.drawable.fab_ic_dial), 1291 getResources().getString(R.string.action_menu_dialpad_button)); 1292 } 1293 } 1294 1295 @Override onPageScrollStateChanged(int state)1296 public void onPageScrollStateChanged(int state) { 1297 } 1298 getTelecomManager()1299 private TelecomManager getTelecomManager() { 1300 return (TelecomManager) getSystemService(Context.TELECOM_SERVICE); 1301 } 1302 1303 @Override isActionBarShowing()1304 public boolean isActionBarShowing() { 1305 return mActionBarController.isActionBarShowing(); 1306 } 1307 1308 @Override getActionBarController()1309 public ActionBarController getActionBarController() { 1310 return mActionBarController; 1311 } 1312 1313 @Override isDialpadShown()1314 public boolean isDialpadShown() { 1315 return mIsDialpadShown; 1316 } 1317 1318 @Override getDialpadHeight()1319 public int getDialpadHeight() { 1320 if (mDialpadFragment != null) { 1321 return mDialpadFragment.getDialpadHeight(); 1322 } 1323 return 0; 1324 } 1325 1326 @Override getActionBarHideOffset()1327 public int getActionBarHideOffset() { 1328 return getActionBar().getHideOffset(); 1329 } 1330 1331 @Override setActionBarHideOffset(int offset)1332 public void setActionBarHideOffset(int offset) { 1333 getActionBar().setHideOffset(offset); 1334 } 1335 1336 @Override getActionBarHeight()1337 public int getActionBarHeight() { 1338 return mActionBarHeight; 1339 } 1340 getFabAlignment()1341 private int getFabAlignment() { 1342 if (!mIsLandscape && !isInSearchUi() && 1343 mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1344 return FloatingActionButtonController.ALIGN_MIDDLE; 1345 } 1346 return FloatingActionButtonController.ALIGN_END; 1347 } 1348 } 1349