DialtactsActivity.java revision 0c66770b304d03367f093e1916cf565d9cf70146
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dialer; 18 19import android.animation.LayoutTransition; 20import android.app.ActionBar; 21import android.app.Activity; 22import android.app.Fragment; 23import android.app.FragmentManager; 24import android.app.FragmentTransaction; 25import android.content.ActivityNotFoundException; 26import android.content.Context; 27import android.content.Intent; 28import android.content.pm.PackageManager; 29import android.content.pm.ResolveInfo; 30import android.content.res.TypedArray; 31import android.net.Uri; 32import android.os.Bundle; 33import android.os.RemoteException; 34import android.os.ServiceManager; 35import android.provider.ContactsContract.Contacts; 36import android.provider.ContactsContract.Intents; 37import android.speech.RecognizerIntent; 38import android.support.v4.view.ViewPager; 39import android.telephony.TelephonyManager; 40import android.text.Editable; 41import android.text.TextUtils; 42import android.text.TextWatcher; 43import android.util.Log; 44import android.view.DragEvent; 45import android.view.Menu; 46import android.view.MenuInflater; 47import android.view.MenuItem; 48import android.view.View; 49import android.view.View.OnDragListener; 50import android.view.animation.AccelerateInterpolator; 51import android.view.animation.DecelerateInterpolator; 52import android.view.animation.Interpolator; 53import android.view.inputmethod.InputMethodManager; 54import android.widget.AbsListView.OnScrollListener; 55import android.widget.EditText; 56import android.widget.PopupMenu; 57import android.widget.RelativeLayout; 58import android.widget.Toast; 59 60import com.android.contacts.common.CallUtil; 61import com.android.contacts.common.activity.TransactionSafeActivity; 62import com.android.contacts.common.dialog.ClearFrequentsDialog; 63import com.android.contacts.common.interactions.ImportExportDialogFragment; 64import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 65import com.android.contacts.common.util.ViewUtil; 66import com.android.dialer.calllog.CallLogActivity; 67import com.android.dialer.database.DialerDatabaseHelper; 68import com.android.dialer.dialpad.DialpadFragment; 69import com.android.dialer.dialpad.SmartDialNameMatcher; 70import com.android.dialer.dialpad.SmartDialPrefix; 71import com.android.dialer.interactions.PhoneNumberInteraction; 72import com.android.dialer.list.DragDropController; 73import com.android.dialer.list.ListsFragment; 74import com.android.dialer.list.OnDragDropListener; 75import com.android.dialer.list.OnListFragmentScrolledListener; 76import com.android.dialer.list.SpeedDialFragment; 77import com.android.dialer.list.PhoneFavoriteSquareTileView; 78import com.android.dialer.list.RegularSearchFragment; 79import com.android.dialer.list.RemoveView; 80import com.android.dialer.list.SearchFragment; 81import com.android.dialer.list.SmartDialSearchFragment; 82import com.android.dialerbind.DatabaseHelperManager; 83import com.android.internal.telephony.ITelephony; 84 85import java.util.ArrayList; 86import java.util.List; 87 88/** 89 * The dialer tab's title is 'phone', a more common name (see strings.xml). 90 */ 91public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, 92 DialpadFragment.OnDialpadQueryChangedListener, 93 OnListFragmentScrolledListener, 94 DialpadFragment.HostInterface, 95 ListsFragment.HostInterface, 96 SpeedDialFragment.HostInterface, 97 OnDragDropListener, View.OnLongClickListener, 98 OnPhoneNumberPickerActionListener, 99 ViewPager.OnPageChangeListener { 100 private static final String TAG = "DialtactsActivity"; 101 102 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 103 104 public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; 105 106 /** Used to open Call Setting */ 107 private static final String PHONE_PACKAGE = "com.android.phone"; 108 private static final String CALL_SETTINGS_CLASS_NAME = 109 "com.android.phone.CallFeaturesSetting"; 110 /** @see #getCallOrigin() */ 111 private static final String CALL_ORIGIN_DIALTACTS = 112 "com.android.dialer.DialtactsActivity"; 113 114 private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; 115 private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; 116 private static final String KEY_SEARCH_QUERY = "search_query"; 117 private static final String KEY_FIRST_LAUNCH = "first_launch"; 118 119 private static final String TAG_DIALPAD_FRAGMENT = "dialpad"; 120 private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; 121 private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; 122 private static final String TAG_FAVORITES_FRAGMENT = "favorites"; 123 124 /** 125 * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. 126 */ 127 private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; 128 129 private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; 130 131 private static final int ANIMATION_DURATION = 250; 132 133 private RelativeLayout parentLayout; 134 135 /** 136 * Fragment containing the dialpad that slides into view 137 */ 138 private DialpadFragment mDialpadFragment; 139 140 /** 141 * Fragment for searching phone numbers using the alphanumeric keyboard. 142 */ 143 private RegularSearchFragment mRegularSearchFragment; 144 145 /** 146 * Fragment for searching phone numbers using the dialpad. 147 */ 148 private SmartDialSearchFragment mSmartDialSearchFragment; 149 150 /** 151 * Fragment containing the speed dial list, recents list, and all contacts list. 152 */ 153 private ListsFragment mListsFragment; 154 155 private View mFloatingActionButton; 156 private View mDialpadButton; 157 private View mDialButton; 158 159 private View mFragmentsFrame; 160 161 private int mActionBarHeight; 162 private boolean mInDialpadSearch; 163 private boolean mInRegularSearch; 164 private boolean mClearSearchOnPause; 165 166 /** 167 * The position of the currently selected tab in the attached {@link ListsFragment}. 168 */ 169 private int mCurrentTabPosition = 0; 170 171 /** 172 * True if the dialpad is only temporarily showing due to being in call 173 */ 174 private boolean mInCallDialpadUp; 175 176 /** 177 * True when this activity has been launched for the first time. 178 */ 179 private boolean mFirstLaunch; 180 181 /** 182 * Search query to be applied to the SearchView in the ActionBar once 183 * onCreateOptionsMenu has been called. 184 */ 185 private String mPendingSearchViewQuery; 186 187 private EditText mSearchView; 188 private View mSearchViewCloseButton; 189 private View mVoiceSearchButton; 190 /** 191 * View that contains the "Remove" dialog that shows up when the user long presses a contact. 192 * If the user releases a contact when hovering on top of this, the contact is unfavorited and 193 * removed from the speed dial list. 194 */ 195 private View mRemoveViewContainer; 196 197 final Interpolator hideActionBarInterpolator = new AccelerateInterpolator(1.5f); 198 final Interpolator showActionBarInterpolator = new DecelerateInterpolator(1.5f); 199 private String mSearchQuery; 200 201 private DialerDatabaseHelper mDialerDatabaseHelper; 202 private DragDropController mDragDropController; 203 204 private int mDialerBackgroundColor; 205 private int mContactListBackgroundColor; 206 207 private class OverflowPopupMenu extends PopupMenu { 208 public OverflowPopupMenu(Context context, View anchor) { 209 super(context, anchor); 210 } 211 212 @Override 213 public void show() { 214 final Menu menu = getMenu(); 215 final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); 216 // TODO: Check mSpeedDialFragment.hasFrequents() 217 clearFrequents.setVisible(true); 218 super.show(); 219 } 220 } 221 222 /** 223 * Listener that listens to drag events and sends their x and y coordinates to a 224 * {@link DragDropController}. 225 */ 226 private class LayoutOnDragListener implements OnDragListener { 227 @Override 228 public boolean onDrag(View v, DragEvent event) { 229 if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { 230 mDragDropController.handleDragHovered(v, (int) event.getX(), 231 (int) event.getY()); 232 } 233 return true; 234 } 235 } 236 237 /** 238 * Listener used when one of phone numbers in search UI is selected. This will initiate a 239 * phone call using the phone number. 240 */ 241 private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener = 242 new OnPhoneNumberPickerActionListener() { 243 @Override 244 public void onPickPhoneNumberAction(Uri dataUri) { 245 // Specify call-origin so that users will see the previous tab instead of 246 // CallLog screen (search UI will be automatically exited). 247 PhoneNumberInteraction.startInteractionForPhoneCall( 248 DialtactsActivity.this, dataUri, getCallOrigin()); 249 mClearSearchOnPause = true; 250 } 251 252 @Override 253 public void onCallNumberDirectly(String phoneNumber) { 254 Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin()); 255 startActivity(intent); 256 mClearSearchOnPause = true; 257 } 258 259 @Override 260 public void onShortcutIntentCreated(Intent intent) { 261 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 262 } 263 264 @Override 265 public void onHomeInActionBarSelected() { 266 exitSearchUi(); 267 } 268 }; 269 270 /** 271 * Listener used to send search queries to the phone search fragment. 272 */ 273 private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { 274 @Override 275 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 276 } 277 278 @Override 279 public void onTextChanged(CharSequence s, int start, int before, int count) { 280 final String newText = s.toString(); 281 if (newText.equals(mSearchQuery)) { 282 // If the query hasn't changed (perhaps due to activity being destroyed 283 // and restored, or user launching the same DIAL intent twice), then there is 284 // no need to do anything here. 285 return; 286 } 287 mSearchQuery = newText; 288 if (DEBUG) { 289 Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText); 290 } 291 final boolean dialpadSearch = isDialpadShowing(); 292 293 // Show search result with non-empty text. Show a bare list otherwise. 294 if (TextUtils.isEmpty(newText) && getInSearchUi()) { 295 exitSearchUi(); 296 mSearchViewCloseButton.setVisibility(View.GONE); 297 mVoiceSearchButton.setVisibility(View.VISIBLE); 298 return; 299 } else if (!TextUtils.isEmpty(newText)) { 300 final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) || 301 (!dialpadSearch && mInRegularSearch); 302 if (!sameSearchMode) { 303 // call enterSearchUi only if we are switching search modes, or entering 304 // search ui for the first time 305 enterSearchUi(dialpadSearch, newText); 306 } 307 308 if (dialpadSearch && mSmartDialSearchFragment != null) { 309 mSmartDialSearchFragment.setQueryString(newText, false); 310 } else if (mRegularSearchFragment != null) { 311 mRegularSearchFragment.setQueryString(newText, false); 312 } 313 mSearchViewCloseButton.setVisibility(View.VISIBLE); 314 mVoiceSearchButton.setVisibility(View.GONE); 315 return; 316 } 317 return; 318 } 319 320 @Override 321 public void afterTextChanged(Editable s) { 322 } 323 }; 324 325 private boolean isDialpadShowing() { 326 return mDialpadFragment != null && mDialpadFragment.isVisible(); 327 } 328 329 @Override 330 protected void onCreate(Bundle savedInstanceState) { 331 super.onCreate(savedInstanceState); 332 mFirstLaunch = true; 333 334 setContentView(R.layout.dialtacts_activity); 335 getWindow().setBackgroundDrawable(null); 336 337 final ActionBar actionBar = getActionBar(); 338 actionBar.setCustomView(R.layout.search_edittext); 339 actionBar.setDisplayShowCustomEnabled(true); 340 341 final View customView = actionBar.getCustomView(); 342 343 mSearchView = (EditText) customView.findViewById(R.id.search_view); 344 mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); 345 346 mSearchViewCloseButton = customView.findViewById(R.id.search_close_button); 347 mSearchViewCloseButton.setOnClickListener(this); 348 349 final TypedArray styledAttributes = getTheme().obtainStyledAttributes( 350 new int[] { android.R.attr.actionBarSize }); 351 mActionBarHeight = (int) styledAttributes.getDimension(0, 0); 352 styledAttributes.recycle(); 353 354 // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState 355 // is null. Otherwise the fragment manager takes care of recreating these fragments. 356 if (savedInstanceState == null) { 357 getFragmentManager().beginTransaction() 358 .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT) 359 .add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT) 360 .commit(); 361 } else { 362 mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); 363 mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); 364 mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); 365 mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); 366 } 367 368 parentLayout = (RelativeLayout) findViewById(R.id.dialtacts_mainlayout); 369 parentLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); 370 parentLayout.setOnDragListener(new LayoutOnDragListener()); 371 372 mDialerBackgroundColor = getResources().getColor(R.color.background_dialer_light); 373 mContactListBackgroundColor = 374 getResources().getColor(R.color.contact_list_background_color); 375 376 mFragmentsFrame = findViewById(R.id.dialtacts_frame); 377 378 mFloatingActionButton = findViewById(R.id.floating_action_button); 379 ViewUtil.setupFloatingActionButton(mFloatingActionButton, getResources()); 380 381 mDialButton = findViewById(R.id.dial_button); 382 mDialButton.setOnClickListener(this); 383 mDialpadButton = findViewById(R.id.dialpad_button); 384 mDialpadButton.setOnClickListener(this); 385 386 mRemoveViewContainer = findViewById(R.id.remove_view_container); 387 388 mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); 389 SmartDialPrefix.initializeNanpSettings(this); 390 } 391 392 @Override 393 protected void onResume() { 394 super.onResume(); 395 if (mFirstLaunch) { 396 displayFragment(getIntent()); 397 } else if (!phoneIsInUse() && mInCallDialpadUp) { 398 hideDialpadFragment(false, true); 399 mInCallDialpadUp = false; 400 } 401 mFirstLaunch = false; 402 prepareVoiceSearchButton(); 403 mDialerDatabaseHelper.startSmartDialUpdateThread(); 404 } 405 406 @Override 407 protected void onPause() { 408 if (mClearSearchOnPause) { 409 hideDialpadAndSearchUi(); 410 mClearSearchOnPause = false; 411 } 412 super.onPause(); 413 } 414 415 @Override 416 protected void onSaveInstanceState(Bundle outState) { 417 super.onSaveInstanceState(outState); 418 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 419 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 420 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 421 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 422 } 423 424 @Override 425 public void onAttachFragment(Fragment fragment) { 426 if (fragment instanceof DialpadFragment) { 427 mDialpadFragment = (DialpadFragment) fragment; 428 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 429 transaction.hide(mDialpadFragment); 430 transaction.commit(); 431 } else if (fragment instanceof SmartDialSearchFragment) { 432 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 433 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener( 434 mPhoneNumberPickerActionListener); 435 if (mFragmentsFrame != null) { 436 mFragmentsFrame.setAlpha(1.0f); 437 } 438 } else if (fragment instanceof SearchFragment) { 439 mRegularSearchFragment = (RegularSearchFragment) fragment; 440 mRegularSearchFragment.setOnPhoneNumberPickerActionListener( 441 mPhoneNumberPickerActionListener); 442 if (mFragmentsFrame != null) { 443 mFragmentsFrame.setAlpha(1.0f); 444 } 445 } else if (fragment instanceof ListsFragment) { 446 mListsFragment = (ListsFragment) fragment; 447 mListsFragment.addOnPageChangeListener(this); 448 } 449 } 450 451 protected void handleMenuSettings() { 452 openTelephonySetting(this); 453 } 454 455 public static void openTelephonySetting(Activity activity) { 456 final Intent settingsIntent = getCallSettingsIntent(); 457 activity.startActivity(settingsIntent); 458 } 459 460 @Override 461 public void onClick(View view) { 462 switch (view.getId()) { 463 case R.id.dialpad_button: 464 // Reset the boolean flag that tracks whether the dialpad was up because 465 // we were in call. Regardless of whether it was true before, we want to 466 // show the dialpad because the user has explicitly clicked the dialpad 467 // button. 468 mInCallDialpadUp = false; 469 showDialpadFragment(true); 470 break; 471 case R.id.dial_button: 472 // Dial button was pressed; tell the Dialpad fragment 473 mDialpadFragment.dialButtonPressed(); 474 break; 475 case R.id.search_close_button: 476 // Clear the search field 477 if (!TextUtils.isEmpty(mSearchView.getText())) { 478 mDialpadFragment.clearDialpad(); 479 mSearchView.setText(null); 480 } 481 break; 482 case R.id.voice_search_button: 483 try { 484 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 485 ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 486 } catch (ActivityNotFoundException e) { 487 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available, 488 Toast.LENGTH_SHORT).show(); 489 } 490 break; 491 default: { 492 Log.wtf(TAG, "Unexpected onClick event from " + view); 493 break; 494 } 495 } 496 } 497 498 @Override 499 public boolean onOptionsItemSelected(MenuItem item) { 500 switch (item.getItemId()) { 501 case R.id.menu_history: 502 showCallHistory(); 503 break; 504 case R.id.menu_add_contact: 505 try { 506 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 507 } catch (ActivityNotFoundException e) { 508 Toast toast = Toast.makeText(this, 509 R.string.add_contact_not_available, 510 Toast.LENGTH_SHORT); 511 toast.show(); 512 } 513 break; 514 case R.id.menu_import_export: 515 // We hard-code the "contactsAreAvailable" argument because doing it properly would 516 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 517 // now in Dialtacts for (potential) performance reasons. Compare with how it is 518 // done in {@link PeopleActivity}. 519 ImportExportDialogFragment.show(getFragmentManager(), true, 520 DialtactsActivity.class); 521 return true; 522 case R.id.menu_clear_frequents: 523 // TODO: This should be enabled/disabled based on 524 // SpeedDialFragment.hasFrequents 525 ClearFrequentsDialog.show(getFragmentManager()); 526 return true; 527 case R.id.menu_call_settings: 528 handleMenuSettings(); 529 return true; 530 } 531 return false; 532 } 533 534 @Override 535 public boolean onLongClick(View view) { 536 switch (view.getId()) { 537 case R.id.dial_button: { 538 // Dial button was pressed; tell the Dialpad fragment 539 mDialpadFragment.dialButtonPressed(); 540 return true; // Consume the event 541 } 542 default: { 543 Log.wtf(TAG, "Unexpected onClick event from " + view); 544 break; 545 } 546 } 547 return false; 548 } 549 550 @Override 551 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 552 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 553 if (resultCode == RESULT_OK) { 554 final ArrayList<String> matches = data.getStringArrayListExtra( 555 RecognizerIntent.EXTRA_RESULTS); 556 if (matches.size() > 0) { 557 final String match = matches.get(0); 558 mSearchView.setText(match); 559 } else { 560 Log.e(TAG, "Voice search - nothing heard"); 561 } 562 } else { 563 Log.e(TAG, "Voice search failed"); 564 } 565 } 566 super.onActivityResult(requestCode, resultCode, data); 567 } 568 569 private void showDialpadFragment(boolean animate) { 570 mDialpadFragment.setAnimate(animate); 571 572 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 573 if (animate) { 574 ft.setCustomAnimations(R.anim.slide_in, 0); 575 } else { 576 mDialpadFragment.setYFraction(0); 577 } 578 ft.show(mDialpadFragment); 579 ft.commit(); 580 } 581 582 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 583 if (mDialpadFragment == null) return; 584 if (clearDialpad) { 585 mDialpadFragment.clearDialpad(); 586 } 587 if (!mDialpadFragment.isVisible()) return; 588 mDialpadFragment.setAnimate(animate); 589 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 590 if (animate) { 591 ft.setCustomAnimations(0, R.anim.slide_out); 592 } 593 ft.hide(mDialpadFragment); 594 ft.commit(); 595 } 596 597 private boolean getInSearchUi() { 598 return mInDialpadSearch || mInRegularSearch; 599 } 600 601 private void setNotInSearchUi() { 602 mInDialpadSearch = false; 603 mInRegularSearch = false; 604 } 605 606 private void hideDialpadAndSearchUi() { 607 mSearchView.setText(null); 608 hideDialpadFragment(false, true); 609 } 610 611 /** 612 * Callback from child DialpadFragment when the dialpad is shown. 613 */ 614 public void onDialpadShown() { 615 mDialButton.setVisibility(View.VISIBLE); 616 mDialpadButton.setVisibility(View.GONE); 617 618 SearchFragment fragment = null; 619 if (mInDialpadSearch) { 620 fragment = mSmartDialSearchFragment; 621 } else if (mInRegularSearch) { 622 fragment = mRegularSearchFragment; 623 } 624 if (fragment != null && fragment.isVisible()) { 625 fragment.getView().animate().translationY(-mActionBarHeight) 626 .setInterpolator(hideActionBarInterpolator).setDuration(ANIMATION_DURATION); 627 } 628 629 if (mListsFragment != null && mListsFragment.isResumed() && mListsFragment.isVisible()) { 630 // If the favorites fragment is showing, fade to blank. 631 mFragmentsFrame.animate().alpha(0.0f); 632 parentLayout.setBackgroundColor(mContactListBackgroundColor); 633 } 634 getActionBar().hide(); 635 alignFloatingActionButtonMiddle(); 636 } 637 638 /** 639 * Callback from child DialpadFragment when the dialpad is hidden. 640 */ 641 public void onDialpadHidden() { 642 mDialButton.setVisibility(View.GONE); 643 mDialpadButton.setVisibility(View.VISIBLE); 644 645 SearchFragment fragment = null; 646 if (mInDialpadSearch) { 647 fragment = mSmartDialSearchFragment; 648 } else if (mInRegularSearch) { 649 fragment = mRegularSearchFragment; 650 } 651 if (fragment != null && fragment.isVisible()) { 652 fragment.getView().animate().translationY(0) 653 .setInterpolator(showActionBarInterpolator).setDuration(ANIMATION_DURATION); 654 } 655 656 if (mListsFragment != null && mListsFragment.isVisible()) { 657 mFragmentsFrame.animate().alpha(1.0f); 658 parentLayout.setBackgroundColor(mDialerBackgroundColor); 659 660 } 661 getActionBar().show(); 662 alignFloatingActionButtonByTab(mCurrentTabPosition); 663 } 664 665 private void hideInputMethod(View view) { 666 final InputMethodManager imm = (InputMethodManager) getSystemService( 667 Context.INPUT_METHOD_SERVICE); 668 if (imm != null && view != null) { 669 imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 670 } 671 } 672 673 private void prepareVoiceSearchButton() { 674 mVoiceSearchButton = getActionBar().getCustomView().findViewById(R.id.voice_search_button); 675 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 676 if (canIntentBeHandled(voiceIntent)) { 677 mVoiceSearchButton.setVisibility(View.VISIBLE); 678 mVoiceSearchButton.setOnClickListener(this); 679 } else { 680 mVoiceSearchButton.setVisibility(View.GONE); 681 } 682 } 683 684 @Override 685 public boolean onCreateOptionsMenu(Menu menu) { 686 if (DEBUG) { 687 Log.d(TAG, "onCreateOptionsMenu"); 688 } 689 MenuInflater inflater = getMenuInflater(); 690 inflater.inflate(R.menu.dialtacts_options, menu); 691 692 if (mPendingSearchViewQuery != null) { 693 mSearchView.setText(mPendingSearchViewQuery); 694 mPendingSearchViewQuery = null; 695 } 696 return super.onCreateOptionsMenu(menu); 697 } 698 699 /** 700 * Returns true if the intent is due to hitting the green send key (hardware call button: 701 * KEYCODE_CALL) while in a call. 702 * 703 * @param intent the intent that launched this activity 704 * @return true if the intent is due to hitting the green send key while in a call 705 */ 706 private boolean isSendKeyWhileInCall(Intent intent) { 707 // If there is a call in progress and the user launched the dialer by hitting the call 708 // button, go straight to the in-call screen. 709 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction()); 710 711 try { 712 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 713 if (callKey && phone != null && phone.showCallScreen()) { 714 return true; 715 } 716 } catch (RemoteException e) { 717 Log.e(TAG, "Failed to handle send while in call", e); 718 } 719 720 return false; 721 } 722 723 /** 724 * Sets the current tab based on the intent's request type 725 * 726 * @param intent Intent that contains information about which tab should be selected 727 */ 728 private void displayFragment(Intent intent) { 729 // If we got here by hitting send and we're in call forward along to the in-call activity 730 if (isSendKeyWhileInCall(intent)) { 731 finish(); 732 return; 733 } 734 735 if (mDialpadFragment != null) { 736 final boolean phoneIsInUse = phoneIsInUse(); 737 if (phoneIsInUse || isDialIntent(intent)) { 738 mDialpadFragment.setStartedFromNewIntent(true); 739 if (phoneIsInUse && !mDialpadFragment.isVisible()) { 740 mInCallDialpadUp = true; 741 } 742 showDialpadFragment(false); 743 } 744 } 745 } 746 747 @Override 748 public void onNewIntent(Intent newIntent) { 749 setIntent(newIntent); 750 displayFragment(newIntent); 751 752 invalidateOptionsMenu(); 753 } 754 755 /** Returns true if the given intent contains a phone number to populate the dialer with */ 756 private boolean isDialIntent(Intent intent) { 757 final String action = intent.getAction(); 758 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 759 return true; 760 } 761 if (Intent.ACTION_VIEW.equals(action)) { 762 final Uri data = intent.getData(); 763 if (data != null && CallUtil.SCHEME_TEL.equals(data.getScheme())) { 764 return true; 765 } 766 } 767 return false; 768 } 769 770 /** 771 * Returns an appropriate call origin for this Activity. May return null when no call origin 772 * should be used (e.g. when some 3rd party application launched the screen. Call origin is 773 * for remembering the tab in which the user made a phone call, so the external app's DIAL 774 * request should not be counted.) 775 */ 776 public String getCallOrigin() { 777 return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null; 778 } 779 780 /** 781 * Shows the search fragment 782 */ 783 private void enterSearchUi(boolean smartDialSearch, String query) { 784 if (getFragmentManager().isDestroyed()) { 785 // Weird race condition where fragment is doing work after the activity is destroyed 786 // due to talkback being on (b/10209937). Just return since we can't do any 787 // constructive here. 788 return; 789 } 790 791 if (DEBUG) { 792 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 793 } 794 795 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 796 797 SearchFragment fragment; 798 if (mInDialpadSearch && mSmartDialSearchFragment != null) { 799 transaction.remove(mSmartDialSearchFragment); 800 } else if (mInRegularSearch && mRegularSearchFragment != null) { 801 transaction.remove(mRegularSearchFragment); 802 } 803 804 final String tag; 805 if (smartDialSearch) { 806 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 807 } else { 808 tag = TAG_REGULAR_SEARCH_FRAGMENT; 809 } 810 mInDialpadSearch = smartDialSearch; 811 mInRegularSearch = !smartDialSearch; 812 813 fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 814 if (fragment == null) { 815 if (smartDialSearch) { 816 fragment = new SmartDialSearchFragment(); 817 } else { 818 fragment = new RegularSearchFragment(); 819 } 820 } 821 // DialtactsActivity will provide the options menu 822 fragment.setHasOptionsMenu(false); 823 transaction.replace(R.id.dialtacts_frame, fragment, tag); 824 transaction.addToBackStack(null); 825 fragment.setQueryString(query, false); 826 transaction.commit(); 827 } 828 829 /** 830 * Hides the search fragment 831 */ 832 private void exitSearchUi() { 833 // See related bug in enterSearchUI(); 834 if (getFragmentManager().isDestroyed()) { 835 return; 836 } 837 // Go all the way back to the favorites fragment, regardless of how many times we 838 // transitioned between search fragments 839 getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE); 840 setNotInSearchUi(); 841 842 if (isDialpadShowing()) { 843 mFragmentsFrame.setAlpha(0); 844 } 845 } 846 847 /** Returns an Intent to launch Call Settings screen */ 848 public static Intent getCallSettingsIntent() { 849 final Intent intent = new Intent(Intent.ACTION_MAIN); 850 intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME); 851 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 852 return intent; 853 } 854 855 @Override 856 public void onBackPressed() { 857 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 858 hideDialpadFragment(true, false); 859 } else if (getInSearchUi()) { 860 mSearchView.setText(null); 861 mDialpadFragment.clearDialpad(); 862 } else { 863 super.onBackPressed(); 864 } 865 } 866 867 @Override 868 public void onDialpadQueryChanged(String query) { 869 if (mSmartDialSearchFragment != null) { 870 mSmartDialSearchFragment.setAddToContactNumber(query); 871 } 872 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 873 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 874 875 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 876 if (DEBUG) { 877 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 878 } 879 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 880 // This callback can happen if the dialpad fragment is recreated because of 881 // activity destruction. In that case, don't update the search view because 882 // that would bring the user back to the search fragment regardless of the 883 // previous state of the application. Instead, just return here and let the 884 // fragment manager correctly figure out whatever fragment was last displayed. 885 if (!TextUtils.isEmpty(normalizedQuery)) { 886 mPendingSearchViewQuery = normalizedQuery; 887 } 888 return; 889 } 890 mSearchView.setText(normalizedQuery); 891 } 892 } 893 894 @Override 895 public void onListFragmentScrollStateChange(int scrollState) { 896 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 897 hideDialpadFragment(true, false); 898 hideInputMethod(getCurrentFocus()); 899 } 900 } 901 902 @Override 903 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, 904 int totalItemCount) { 905 // TODO: No-op for now. This should eventually show/hide the actionBar based on 906 // interactions with the ListsFragments. 907 } 908 909 @Override 910 public void setDialButtonEnabled(boolean enabled) { 911 if (mDialButton != null) { 912 mDialButton.setEnabled(enabled); 913 } 914 } 915 916 @Override 917 public void setDialButtonContainerVisible(boolean visible) { 918 mFloatingActionButton.setVisibility(visible ? View.VISIBLE : View.GONE); 919 } 920 921 private boolean phoneIsInUse() { 922 final TelephonyManager tm = (TelephonyManager) getSystemService( 923 Context.TELEPHONY_SERVICE); 924 return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE; 925 } 926 927 public static Intent getAddNumberToContactIntent(CharSequence text) { 928 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 929 intent.putExtra(Intents.Insert.PHONE, text); 930 intent.setType(Contacts.CONTENT_ITEM_TYPE); 931 return intent; 932 } 933 934 private boolean canIntentBeHandled(Intent intent) { 935 final PackageManager packageManager = getPackageManager(); 936 final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, 937 PackageManager.MATCH_DEFAULT_ONLY); 938 return resolveInfo != null && resolveInfo.size() > 0; 939 } 940 941 @Override 942 public void showCallHistory() { 943 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 944 // CONTENT_TYPE, so that we always open our call log from our dialer 945 final Intent intent = new Intent(this, CallLogActivity.class); 946 startActivity(intent); 947 } 948 949 /** 950 * Called when the user has long-pressed a contact tile to start a drag operation. 951 */ 952 @Override 953 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 954 getActionBar().hide(); 955 mRemoveViewContainer.setVisibility(View.VISIBLE); 956 } 957 958 @Override 959 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) { 960 } 961 962 /** 963 * Called when the user has released a contact tile after long-pressing it. 964 */ 965 @Override 966 public void onDragFinished(int x, int y) { 967 getActionBar().show(); 968 mRemoveViewContainer.setVisibility(View.GONE); 969 } 970 971 @Override 972 public void onDroppedOnRemove() {} 973 974 /** 975 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer 976 * once it has been attached to the activity. 977 */ 978 @Override 979 public void setDragDropController(DragDropController dragController) { 980 mDragDropController = dragController; 981 ((RemoveView) findViewById(R.id.remove_view)) 982 .setDragDropController(dragController); 983 } 984 985 @Override 986 public void onPickPhoneNumberAction(Uri dataUri) { 987 mPhoneNumberPickerActionListener.onPickPhoneNumberAction(dataUri); 988 } 989 990 @Override 991 public void onCallNumberDirectly(String phoneNumber) { 992 mPhoneNumberPickerActionListener.onCallNumberDirectly(phoneNumber); 993 } 994 995 @Override 996 public void onShortcutIntentCreated(Intent intent) { 997 mPhoneNumberPickerActionListener.onShortcutIntentCreated(intent); 998 } 999 1000 @Override 1001 public void onHomeInActionBarSelected() { 1002 mPhoneNumberPickerActionListener.onHomeInActionBarSelected(); 1003 } 1004 1005 public int getActionBarHeight() { 1006 return mActionBarHeight; 1007 } 1008 1009 @Override 1010 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 1011 1012 } 1013 1014 @Override 1015 public void onPageSelected(int position) { 1016 mCurrentTabPosition = position; 1017 // If the dialpad is showing, the floating action button should always be middle aligned. 1018 if (!isDialpadShowing()) { 1019 alignFloatingActionButtonByTab(mCurrentTabPosition); 1020 } 1021 } 1022 1023 @Override 1024 public void onPageScrollStateChanged(int state) { 1025 } 1026 1027 private void alignFloatingActionButtonByTab(int position) { 1028 if (position == ListsFragment.TAB_INDEX_SPEED_DIAL) { 1029 alignFloatingActionButtonMiddle(); 1030 } else { 1031 alignFloatingActionButtonRight(); 1032 } 1033 } 1034 1035 private void alignFloatingActionButtonRight() { 1036 final RelativeLayout.LayoutParams params = 1037 (RelativeLayout.LayoutParams) mFloatingActionButton.getLayoutParams(); 1038 params.removeRule(RelativeLayout.CENTER_HORIZONTAL); 1039 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1040 mFloatingActionButton.setLayoutParams(params); 1041 } 1042 1043 private void alignFloatingActionButtonMiddle() { 1044 final RelativeLayout.LayoutParams params = 1045 (RelativeLayout.LayoutParams) mFloatingActionButton.getLayoutParams(); 1046 params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); 1047 params.addRule(RelativeLayout.CENTER_HORIZONTAL); 1048 mFloatingActionButton.setLayoutParams(params); 1049 } 1050} 1051