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