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.animation.Animator; 20 import android.animation.Animator.AnimatorListener; 21 import android.animation.AnimatorListenerAdapter; 22 import android.app.Activity; 23 import android.app.Fragment; 24 import android.app.FragmentManager; 25 import android.app.FragmentManager.BackStackEntry; 26 import android.app.FragmentTransaction; 27 import android.content.ActivityNotFoundException; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.provider.CallLog.Calls; 35 import android.provider.ContactsContract.Contacts; 36 import android.provider.ContactsContract.Intents; 37 import android.provider.ContactsContract.Intents.UI; 38 import android.speech.RecognizerIntent; 39 import android.telephony.TelephonyManager; 40 import android.text.Editable; 41 import android.text.TextUtils; 42 import android.text.TextWatcher; 43 import android.util.Log; 44 import android.view.Menu; 45 import android.view.MenuItem; 46 import android.view.View; 47 import android.view.View.OnFocusChangeListener; 48 import android.view.inputmethod.InputMethodManager; 49 import android.widget.AbsListView.OnScrollListener; 50 import android.widget.EditText; 51 import android.widget.PopupMenu; 52 import android.widget.Toast; 53 54 import com.android.contacts.common.CallUtil; 55 import com.android.contacts.common.activity.TransactionSafeActivity; 56 import com.android.contacts.common.dialog.ClearFrequentsDialog; 57 import com.android.contacts.common.interactions.ImportExportDialogFragment; 58 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 59 import com.android.dialer.calllog.CallLogActivity; 60 import com.android.dialer.database.DialerDatabaseHelper; 61 import com.android.dialer.dialpad.DialpadFragment; 62 import com.android.dialer.dialpad.SmartDialNameMatcher; 63 import com.android.dialer.dialpad.SmartDialPrefix; 64 import com.android.dialer.interactions.PhoneNumberInteraction; 65 import com.android.dialer.list.AllContactsActivity; 66 import com.android.dialer.list.OnListFragmentScrolledListener; 67 import com.android.dialer.list.PhoneFavoriteFragment; 68 import com.android.dialer.list.RegularSearchFragment; 69 import com.android.dialer.list.SearchFragment; 70 import com.android.dialer.list.SmartDialSearchFragment; 71 import com.android.dialerbind.DatabaseHelperManager; 72 import com.android.internal.telephony.ITelephony; 73 74 import java.util.ArrayList; 75 76 /** 77 * The dialer tab's title is 'phone', a more common name (see strings.xml). 78 */ 79 public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener, 80 DialpadFragment.OnDialpadQueryChangedListener, PopupMenu.OnMenuItemClickListener, 81 OnListFragmentScrolledListener, 82 DialpadFragment.OnDialpadFragmentStartedListener, 83 PhoneFavoriteFragment.OnShowAllContactsListener { 84 private static final String TAG = "DialtactsActivity"; 85 86 public static final boolean DEBUG = false; 87 88 public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences"; 89 90 /** Used to open Call Setting */ 91 private static final String PHONE_PACKAGE = "com.android.phone"; 92 private static final String CALL_SETTINGS_CLASS_NAME = 93 "com.android.phone.CallFeaturesSetting"; 94 /** @see #getCallOrigin() */ 95 private static final String CALL_ORIGIN_DIALTACTS = 96 "com.android.dialer.DialtactsActivity"; 97 98 private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; 99 private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; 100 private static final String KEY_SEARCH_QUERY = "search_query"; 101 private static final String KEY_FIRST_LAUNCH = "first_launch"; 102 103 private static final String TAG_DIALPAD_FRAGMENT = "dialpad"; 104 private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; 105 private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; 106 private static final String TAG_FAVORITES_FRAGMENT = "favorites"; 107 108 /** 109 * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. 110 */ 111 private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; 112 113 private static final int SUBACTIVITY_ACCOUNT_FILTER = 1; 114 115 private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1; 116 117 private String mFilterText; 118 119 /** 120 * The main fragment displaying the user's favorites and frequent contacts 121 */ 122 private PhoneFavoriteFragment mPhoneFavoriteFragment; 123 124 /** 125 * Fragment containing the dialpad that slides into view 126 */ 127 private DialpadFragment mDialpadFragment; 128 129 /** 130 * Fragment for searching phone numbers using the alphanumeric keyboard. 131 */ 132 private RegularSearchFragment mRegularSearchFragment; 133 134 /** 135 * Fragment for searching phone numbers using the dialpad. 136 */ 137 private SmartDialSearchFragment mSmartDialSearchFragment; 138 139 private View mMenuButton; 140 private View mCallHistoryButton; 141 private View mDialpadButton; 142 private PopupMenu mOverflowMenu; 143 144 // Padding view used to shift the fragments up when the dialpad is shown. 145 private View mBottomPaddingView; 146 private View mFragmentsFrame; 147 private View mActionBar; 148 149 private boolean mInDialpadSearch; 150 private boolean mInRegularSearch; 151 private boolean mClearSearchOnPause; 152 153 /** 154 * True if the dialpad is only temporarily showing due to being in call 155 */ 156 private boolean mInCallDialpadUp; 157 158 /** 159 * True when this activity has been launched for the first time. 160 */ 161 private boolean mFirstLaunch; 162 private View mSearchViewContainer; 163 private View mSearchViewCloseButton; 164 private View mVoiceSearchButton; 165 private EditText mSearchView; 166 167 private String mSearchQuery; 168 169 private DialerDatabaseHelper mDialerDatabaseHelper; 170 171 private class OverflowPopupMenu extends PopupMenu { 172 public OverflowPopupMenu(Context context, View anchor) { 173 super(context, anchor); 174 } 175 176 @Override 177 public void show() { 178 final Menu menu = getMenu(); 179 final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents); 180 clearFrequents.setVisible(mPhoneFavoriteFragment.hasFrequents()); 181 super.show(); 182 } 183 } 184 185 /** 186 * Listener used when one of phone numbers in search UI is selected. This will initiate a 187 * phone call using the phone number. 188 */ 189 private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener = 190 new OnPhoneNumberPickerActionListener() { 191 @Override 192 public void onPickPhoneNumberAction(Uri dataUri) { 193 // Specify call-origin so that users will see the previous tab instead of 194 // CallLog screen (search UI will be automatically exited). 195 PhoneNumberInteraction.startInteractionForPhoneCall( 196 DialtactsActivity.this, dataUri, getCallOrigin()); 197 mClearSearchOnPause = true; 198 } 199 200 @Override 201 public void onCallNumberDirectly(String phoneNumber) { 202 Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin()); 203 startActivity(intent); 204 mClearSearchOnPause = true; 205 } 206 207 @Override 208 public void onShortcutIntentCreated(Intent intent) { 209 Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring."); 210 } 211 212 @Override 213 public void onHomeInActionBarSelected() { 214 exitSearchUi(); 215 } 216 }; 217 218 /** 219 * Listener used to send search queries to the phone search fragment. 220 */ 221 private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { 222 @Override 223 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 224 } 225 226 @Override 227 public void onTextChanged(CharSequence s, int start, int before, int count) { 228 final String newText = s.toString(); 229 if (newText.equals(mSearchQuery)) { 230 // If the query hasn't changed (perhaps due to activity being destroyed 231 // and restored, or user launching the same DIAL intent twice), then there is 232 // no need to do anything here. 233 return; 234 } 235 mSearchQuery = newText; 236 if (DEBUG) { 237 Log.d(TAG, "onTextChange for mSearchView called with new query: " + s); 238 } 239 final boolean dialpadSearch = isDialpadShowing(); 240 241 // Show search result with non-empty text. Show a bare list otherwise. 242 if (TextUtils.isEmpty(newText) && getInSearchUi()) { 243 exitSearchUi(); 244 mSearchViewCloseButton.setVisibility(View.GONE); 245 mVoiceSearchButton.setVisibility(View.VISIBLE); 246 return; 247 } else if (!TextUtils.isEmpty(newText)) { 248 final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) || 249 (!dialpadSearch && mInRegularSearch); 250 if (!sameSearchMode) { 251 // call enterSearchUi only if we are switching search modes, or entering 252 // search ui for the first time 253 enterSearchUi(dialpadSearch, newText); 254 } 255 256 if (dialpadSearch && mSmartDialSearchFragment != null) { 257 mSmartDialSearchFragment.setQueryString(newText, false); 258 } else if (mRegularSearchFragment != null) { 259 mRegularSearchFragment.setQueryString(newText, false); 260 } 261 mSearchViewCloseButton.setVisibility(View.VISIBLE); 262 mVoiceSearchButton.setVisibility(View.GONE); 263 return; 264 } 265 } 266 267 @Override 268 public void afterTextChanged(Editable s) { 269 } 270 }; 271 272 private boolean isDialpadShowing() { 273 return mDialpadFragment != null && mDialpadFragment.isVisible(); 274 } 275 276 @Override 277 protected void onCreate(Bundle savedInstanceState) { 278 super.onCreate(savedInstanceState); 279 mFirstLaunch = true; 280 281 final Intent intent = getIntent(); 282 fixIntent(intent); 283 284 setContentView(R.layout.dialtacts_activity); 285 286 getActionBar().hide(); 287 288 // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState 289 // is null. Otherwise the fragment manager takes care of recreating these fragments. 290 if (savedInstanceState == null) { 291 final PhoneFavoriteFragment phoneFavoriteFragment = new PhoneFavoriteFragment(); 292 293 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 294 ft.add(R.id.dialtacts_frame, phoneFavoriteFragment, TAG_FAVORITES_FRAGMENT); 295 ft.add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT); 296 ft.commit(); 297 } else { 298 mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); 299 mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); 300 mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); 301 mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); 302 } 303 304 mBottomPaddingView = findViewById(R.id.dialtacts_bottom_padding); 305 mFragmentsFrame = findViewById(R.id.dialtacts_frame); 306 mActionBar = findViewById(R.id.fake_action_bar); 307 prepareSearchView(); 308 309 if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction()) 310 && savedInstanceState == null) { 311 setupFilterText(intent); 312 } 313 314 setupFakeActionBarItems(); 315 316 mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); 317 SmartDialPrefix.initializeNanpSettings(this); 318 } 319 320 @Override 321 protected void onResume() { 322 super.onResume(); 323 if (mFirstLaunch) { 324 displayFragment(getIntent()); 325 } else if (!phoneIsInUse() && mInCallDialpadUp) { 326 hideDialpadFragment(false, true); 327 mInCallDialpadUp = false; 328 } 329 330 mFirstLaunch = false; 331 mDialerDatabaseHelper.startSmartDialUpdateThread(); 332 } 333 334 @Override 335 protected void onPause() { 336 if (mClearSearchOnPause) { 337 hideDialpadAndSearchUi(); 338 mClearSearchOnPause = false; 339 } 340 super.onPause(); 341 } 342 343 @Override 344 protected void onSaveInstanceState(Bundle outState) { 345 super.onSaveInstanceState(outState); 346 outState.putString(KEY_SEARCH_QUERY, mSearchQuery); 347 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); 348 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); 349 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); 350 } 351 352 @Override 353 public void onAttachFragment(Fragment fragment) { 354 if (fragment instanceof DialpadFragment) { 355 mDialpadFragment = (DialpadFragment) fragment; 356 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 357 transaction.hide(mDialpadFragment); 358 transaction.commit(); 359 } else if (fragment instanceof SmartDialSearchFragment) { 360 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment; 361 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener( 362 mPhoneNumberPickerActionListener); 363 } else if (fragment instanceof SearchFragment) { 364 mRegularSearchFragment = (RegularSearchFragment) fragment; 365 mRegularSearchFragment.setOnPhoneNumberPickerActionListener( 366 mPhoneNumberPickerActionListener); 367 } else if (fragment instanceof PhoneFavoriteFragment) { 368 mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment; 369 mPhoneFavoriteFragment.setListener(mPhoneFavoriteListener); 370 } 371 } 372 373 @Override 374 public boolean onMenuItemClick(MenuItem item) { 375 switch (item.getItemId()) { 376 case R.id.menu_import_export: 377 // We hard-code the "contactsAreAvailable" argument because doing it properly would 378 // involve querying a {@link ProviderStatusLoader}, which we don't want to do right 379 // now in Dialtacts for (potential) performance reasons. Compare with how it is 380 // done in {@link PeopleActivity}. 381 ImportExportDialogFragment.show(getFragmentManager(), true, 382 DialtactsActivity.class); 383 return true; 384 case R.id.menu_clear_frequents: 385 ClearFrequentsDialog.show(getFragmentManager()); 386 return true; 387 case R.id.menu_add_contact: 388 try { 389 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 390 } catch (ActivityNotFoundException e) { 391 Toast toast = Toast.makeText(this, 392 R.string.add_contact_not_available, 393 Toast.LENGTH_SHORT); 394 toast.show(); 395 } 396 return true; 397 case R.id.menu_call_settings: 398 handleMenuSettings(); 399 return true; 400 case R.id.menu_all_contacts: 401 onShowAllContacts(); 402 return true; 403 } 404 return false; 405 } 406 407 protected void handleMenuSettings() { 408 openTelephonySetting(this); 409 } 410 411 public static void openTelephonySetting(Activity activity) { 412 final Intent settingsIntent = getCallSettingsIntent(); 413 activity.startActivity(settingsIntent); 414 } 415 416 @Override 417 public void onClick(View view) { 418 switch (view.getId()) { 419 case R.id.overflow_menu: { 420 mOverflowMenu.show(); 421 break; 422 } 423 case R.id.dialpad_button: 424 // Reset the boolean flag that tracks whether the dialpad was up because 425 // we were in call. Regardless of whether it was true before, we want to 426 // show the dialpad because the user has explicitly clicked the dialpad 427 // button. 428 mInCallDialpadUp = false; 429 showDialpadFragment(true); 430 break; 431 case R.id.call_history_on_dialpad_button: 432 case R.id.call_history_button: 433 // Use explicit CallLogActivity intent instead of ACTION_VIEW + 434 // CONTENT_TYPE, so that we always open our call log from our dialer 435 final Intent intent = new Intent(this, CallLogActivity.class); 436 startActivity(intent); 437 break; 438 case R.id.search_close_button: 439 // Clear the search field 440 if (!TextUtils.isEmpty(mSearchView.getText())) { 441 mDialpadFragment.clearDialpad(); 442 mSearchView.setText(""); 443 } 444 break; 445 case R.id.voice_search_button: 446 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 447 startActivityForResult(voiceIntent, ACTIVITY_REQUEST_CODE_VOICE_SEARCH); 448 break; 449 default: { 450 Log.wtf(TAG, "Unexpected onClick event from " + view); 451 break; 452 } 453 } 454 } 455 456 @Override 457 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 458 if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { 459 if (resultCode == RESULT_OK) { 460 final ArrayList<String> matches = data.getStringArrayListExtra( 461 RecognizerIntent.EXTRA_RESULTS); 462 if (matches.size() > 0) { 463 final String match = matches.get(0); 464 mSearchView.setText(match); 465 } else { 466 Log.e(TAG, "Voice search - nothing heard"); 467 } 468 } else { 469 Log.e(TAG, "Voice search failed"); 470 } 471 } 472 super.onActivityResult(requestCode, resultCode, data); 473 } 474 475 private void showDialpadFragment(boolean animate) { 476 mDialpadFragment.setAdjustTranslationForAnimation(animate); 477 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 478 if (animate) { 479 ft.setCustomAnimations(R.anim.slide_in, 0); 480 } else { 481 mDialpadFragment.setYFraction(0); 482 } 483 ft.show(mDialpadFragment); 484 ft.commit(); 485 } 486 487 public void hideDialpadFragment(boolean animate, boolean clearDialpad) { 488 if (mDialpadFragment == null) return; 489 if (clearDialpad) { 490 mDialpadFragment.clearDialpad(); 491 } 492 if (!mDialpadFragment.isVisible()) return; 493 mDialpadFragment.setAdjustTranslationForAnimation(animate); 494 final FragmentTransaction ft = getFragmentManager().beginTransaction(); 495 if (animate) { 496 ft.setCustomAnimations(0, R.anim.slide_out); 497 } 498 ft.hide(mDialpadFragment); 499 ft.commit(); 500 } 501 502 private void prepareSearchView() { 503 mSearchViewContainer = findViewById(R.id.search_view_container); 504 mSearchViewCloseButton = findViewById(R.id.search_close_button); 505 mSearchViewCloseButton.setOnClickListener(this); 506 mVoiceSearchButton = findViewById(R.id.voice_search_button); 507 mVoiceSearchButton.setOnClickListener(this); 508 mSearchView = (EditText) findViewById(R.id.search_view); 509 mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); 510 mSearchView.setHint(getString(R.string.dialer_hint_find_contact)); 511 } 512 513 final AnimatorListener mHideListener = new AnimatorListenerAdapter() { 514 @Override 515 public void onAnimationEnd(Animator animation) { 516 mSearchViewContainer.setVisibility(View.GONE); 517 } 518 }; 519 520 private boolean getInSearchUi() { 521 return mInDialpadSearch || mInRegularSearch; 522 } 523 524 private void setNotInSearchUi() { 525 mInDialpadSearch = false; 526 mInRegularSearch = false; 527 } 528 529 private void hideDialpadAndSearchUi() { 530 mSearchView.setText(null); 531 hideDialpadFragment(false, true); 532 } 533 534 public void hideSearchBar() { 535 hideSearchBar(true); 536 } 537 538 public void hideSearchBar(boolean shiftView) { 539 if (shiftView) { 540 mSearchViewContainer.animate().cancel(); 541 mSearchViewContainer.setAlpha(1); 542 mSearchViewContainer.setTranslationY(0); 543 mSearchViewContainer.animate().withLayer().alpha(0).translationY(-mSearchView.getHeight()) 544 .setDuration(200).setListener(mHideListener); 545 546 mFragmentsFrame.animate().withLayer() 547 .translationY(-mSearchViewContainer.getHeight()).setDuration(200).setListener( 548 new AnimatorListenerAdapter() { 549 @Override 550 public void onAnimationEnd(Animator animation) { 551 mBottomPaddingView.setVisibility(View.VISIBLE); 552 mFragmentsFrame.setTranslationY(0); 553 mActionBar.setVisibility(View.INVISIBLE); 554 } 555 }); 556 } else { 557 mSearchViewContainer.setTranslationY(-mSearchView.getHeight()); 558 mActionBar.setVisibility(View.INVISIBLE); 559 } 560 } 561 562 public void showSearchBar() { 563 mSearchViewContainer.animate().cancel(); 564 mSearchViewContainer.setAlpha(0); 565 mSearchViewContainer.setTranslationY(-mSearchViewContainer.getHeight()); 566 mSearchViewContainer.animate().withLayer().alpha(1).translationY(0).setDuration(200) 567 .setListener(new AnimatorListenerAdapter() { 568 @Override 569 public void onAnimationStart(Animator animation) { 570 mSearchViewContainer.setVisibility(View.VISIBLE); 571 mActionBar.setVisibility(View.VISIBLE); 572 } 573 }); 574 575 mFragmentsFrame.setTranslationY(-mSearchViewContainer.getHeight()); 576 mFragmentsFrame.animate().withLayer().translationY(0).setDuration(200) 577 .setListener( 578 new AnimatorListenerAdapter() { 579 @Override 580 public void onAnimationStart(Animator animation) { 581 mBottomPaddingView.setVisibility(View.GONE); 582 } 583 }); 584 } 585 586 587 public void setupFakeActionBarItems() { 588 mMenuButton = findViewById(R.id.overflow_menu); 589 if (mMenuButton != null) { 590 mMenuButton.setOnClickListener(this); 591 592 mOverflowMenu = new OverflowPopupMenu(DialtactsActivity.this, mMenuButton); 593 final Menu menu = mOverflowMenu.getMenu(); 594 mOverflowMenu.inflate(R.menu.dialtacts_options); 595 mOverflowMenu.setOnMenuItemClickListener(this); 596 mMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener()); 597 } 598 599 mCallHistoryButton = findViewById(R.id.call_history_button); 600 // mCallHistoryButton.setMinimumWidth(fakeMenuItemWidth); 601 mCallHistoryButton.setOnClickListener(this); 602 603 mDialpadButton = findViewById(R.id.dialpad_button); 604 // DialpadButton.setMinimumWidth(fakeMenuItemWidth); 605 mDialpadButton.setOnClickListener(this); 606 } 607 608 public void setupFakeActionBarItemsForDialpadFragment() { 609 final View callhistoryButton = findViewById(R.id.call_history_on_dialpad_button); 610 callhistoryButton.setOnClickListener(this); 611 } 612 613 private void fixIntent(Intent intent) { 614 // This should be cleaned up: the call key used to send an Intent 615 // that just said to go to the recent calls list. It now sends this 616 // abstract action, but this class hasn't been rewritten to deal with it. 617 if (Intent.ACTION_CALL_BUTTON.equals(intent.getAction())) { 618 intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE); 619 intent.putExtra("call_key", true); 620 setIntent(intent); 621 } 622 } 623 624 /** 625 * Returns true if the intent is due to hitting the green send key (hardware call button: 626 * KEYCODE_CALL) while in a call. 627 * 628 * @param intent the intent that launched this activity 629 * @param recentCallsRequest true if the intent is requesting to view recent calls 630 * @return true if the intent is due to hitting the green send key while in a call 631 */ 632 private boolean isSendKeyWhileInCall(Intent intent, boolean recentCallsRequest) { 633 // If there is a call in progress go to the call screen 634 if (recentCallsRequest) { 635 final boolean callKey = intent.getBooleanExtra("call_key", false); 636 637 try { 638 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 639 if (callKey && phone != null && phone.showCallScreen()) { 640 return true; 641 } 642 } catch (RemoteException e) { 643 Log.e(TAG, "Failed to handle send while in call", e); 644 } 645 } 646 647 return false; 648 } 649 650 /** 651 * Sets the current tab based on the intent's request type 652 * 653 * @param intent Intent that contains information about which tab should be selected 654 */ 655 private void displayFragment(Intent intent) { 656 // If we got here by hitting send and we're in call forward along to the in-call activity 657 boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.resolveType( 658 getContentResolver())); 659 if (isSendKeyWhileInCall(intent, recentCallsRequest)) { 660 finish(); 661 return; 662 } 663 664 if (mDialpadFragment != null) { 665 final boolean phoneIsInUse = phoneIsInUse(); 666 if (phoneIsInUse || isDialIntent(intent)) { 667 mDialpadFragment.setStartedFromNewIntent(true); 668 if (phoneIsInUse && !mDialpadFragment.isVisible()) { 669 mInCallDialpadUp = true; 670 } 671 showDialpadFragment(false); 672 } 673 } 674 } 675 676 @Override 677 public void onNewIntent(Intent newIntent) { 678 setIntent(newIntent); 679 fixIntent(newIntent); 680 displayFragment(newIntent); 681 final String action = newIntent.getAction(); 682 683 invalidateOptionsMenu(); 684 } 685 686 /** Returns true if the given intent contains a phone number to populate the dialer with */ 687 private boolean isDialIntent(Intent intent) { 688 final String action = intent.getAction(); 689 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) { 690 return true; 691 } 692 if (Intent.ACTION_VIEW.equals(action)) { 693 final Uri data = intent.getData(); 694 if (data != null && CallUtil.SCHEME_TEL.equals(data.getScheme())) { 695 return true; 696 } 697 } 698 return false; 699 } 700 701 /** 702 * Returns an appropriate call origin for this Activity. May return null when no call origin 703 * should be used (e.g. when some 3rd party application launched the screen. Call origin is 704 * for remembering the tab in which the user made a phone call, so the external app's DIAL 705 * request should not be counted.) 706 */ 707 public String getCallOrigin() { 708 return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null; 709 } 710 711 /** 712 * Retrieves the filter text stored in {@link #setupFilterText(Intent)}. 713 * This text originally came from a FILTER_CONTACTS_ACTION intent received 714 * by this activity. The stored text will then be cleared after after this 715 * method returns. 716 * 717 * @return The stored filter text 718 */ 719 public String getAndClearFilterText() { 720 String filterText = mFilterText; 721 mFilterText = null; 722 return filterText; 723 } 724 725 /** 726 * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent. 727 * This is so child activities can check if they are supposed to display a filter. 728 * 729 * @param intent The intent received in {@link #onNewIntent(Intent)} 730 */ 731 private void setupFilterText(Intent intent) { 732 // If the intent was relaunched from history, don't apply the filter text. 733 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { 734 return; 735 } 736 String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY); 737 if (filter != null && filter.length() > 0) { 738 mFilterText = filter; 739 } 740 } 741 742 private final PhoneFavoriteFragment.Listener mPhoneFavoriteListener = 743 new PhoneFavoriteFragment.Listener() { 744 @Override 745 public void onContactSelected(Uri contactUri) { 746 PhoneNumberInteraction.startInteractionForPhoneCall( 747 DialtactsActivity.this, contactUri, getCallOrigin()); 748 } 749 750 @Override 751 public void onCallNumberDirectly(String phoneNumber) { 752 Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin()); 753 startActivity(intent); 754 } 755 }; 756 757 /* TODO krelease: This is only relevant for phones that have a hard button search key (i.e. 758 * Nexus S). Supporting it is a little more tricky because of the dialpad fragment might 759 * be showing when the search key is pressed so there is more state management involved. 760 761 @Override 762 public void startSearch(String initialQuery, boolean selectInitialQuery, 763 Bundle appSearchData, boolean globalSearch) { 764 if (mRegularSearchFragment != null && mRegularSearchFragment.isAdded() && !globalSearch) { 765 if (mInSearchUi) { 766 if (mSearchView.hasFocus()) { 767 showInputMethod(mSearchView.findFocus()); 768 } else { 769 mSearchView.requestFocus(); 770 } 771 } else { 772 enterSearchUi(); 773 } 774 } else { 775 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch); 776 } 777 }*/ 778 779 private void showInputMethod(View view) { 780 final InputMethodManager imm = (InputMethodManager) getSystemService( 781 Context.INPUT_METHOD_SERVICE); 782 if (imm != null) { 783 imm.showSoftInput(view, 0); 784 } 785 } 786 787 private void hideInputMethod(View view) { 788 final InputMethodManager imm = (InputMethodManager) getSystemService( 789 Context.INPUT_METHOD_SERVICE); 790 if (imm != null && view != null) { 791 imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 792 } 793 } 794 795 /** 796 * Shows the search fragment 797 */ 798 private void enterSearchUi(boolean smartDialSearch, String query) { 799 if (getFragmentManager().isDestroyed()) { 800 // Weird race condition where fragment is doing work after the activity is destroyed 801 // due to talkback being on (b/10209937). Just return since we can't do any 802 // constructive here. 803 return; 804 } 805 806 if (DEBUG) { 807 Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch); 808 } 809 810 final FragmentTransaction transaction = getFragmentManager().beginTransaction(); 811 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 812 813 SearchFragment fragment; 814 if (mInDialpadSearch) { 815 transaction.remove(mSmartDialSearchFragment); 816 } else if (mInRegularSearch) { 817 transaction.remove(mRegularSearchFragment); 818 } else { 819 transaction.remove(mPhoneFavoriteFragment); 820 } 821 822 final String tag; 823 if (smartDialSearch) { 824 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; 825 } else { 826 tag = TAG_REGULAR_SEARCH_FRAGMENT; 827 } 828 mInDialpadSearch = smartDialSearch; 829 mInRegularSearch = !smartDialSearch; 830 831 fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag); 832 if (fragment == null) { 833 if (smartDialSearch) { 834 fragment = new SmartDialSearchFragment(); 835 } else { 836 fragment = new RegularSearchFragment(); 837 } 838 } 839 transaction.replace(R.id.dialtacts_frame, fragment, tag); 840 transaction.addToBackStack(null); 841 fragment.setQueryString(query, false); 842 transaction.commit(); 843 } 844 845 /** 846 * Hides the search fragment 847 */ 848 private void exitSearchUi() { 849 // See related bug in enterSearchUI(); 850 if (getFragmentManager().isDestroyed()) { 851 return; 852 } 853 // Go all the way back to the favorites fragment, regardless of how many times we 854 // transitioned between search fragments 855 final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(0); 856 getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE); 857 setNotInSearchUi(); 858 } 859 860 /** Returns an Intent to launch Call Settings screen */ 861 public static Intent getCallSettingsIntent() { 862 final Intent intent = new Intent(Intent.ACTION_MAIN); 863 intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME); 864 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 865 return intent; 866 } 867 868 @Override 869 public void onBackPressed() { 870 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 871 hideDialpadFragment(true, false); 872 } else if (getInSearchUi()) { 873 mSearchView.setText(null); 874 mDialpadFragment.clearDialpad(); 875 } else if (isTaskRoot()) { 876 // Instead of stopping, simply push this to the back of the stack. 877 // This is only done when running at the top of the stack; 878 // otherwise, we have been launched by someone else so need to 879 // allow the user to go back to the caller. 880 moveTaskToBack(false); 881 } else { 882 super.onBackPressed(); 883 } 884 } 885 886 @Override 887 public void onDialpadQueryChanged(String query) { 888 final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query, 889 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP); 890 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) { 891 if (DEBUG) { 892 Log.d(TAG, "onDialpadQueryChanged - new query: " + query); 893 } 894 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) { 895 // This callback can happen if the dialpad fragment is recreated because of 896 // activity destruction. In that case, don't update the search view because 897 // that would bring the user back to the search fragment regardless of the 898 // previous state of the application. Instead, just return here and let the 899 // fragment manager correctly figure out whatever fragment was last displayed. 900 return; 901 } 902 mSearchView.setText(normalizedQuery); 903 } 904 } 905 906 @Override 907 public void onListFragmentScrollStateChange(int scrollState) { 908 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { 909 hideDialpadFragment(true, false); 910 hideInputMethod(getCurrentFocus()); 911 } 912 } 913 914 @Override 915 public void onDialpadFragmentStarted() { 916 setupFakeActionBarItemsForDialpadFragment(); 917 } 918 919 private boolean phoneIsInUse() { 920 final TelephonyManager tm = (TelephonyManager) getSystemService( 921 Context.TELEPHONY_SERVICE); 922 return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE; 923 } 924 925 @Override 926 public void onShowAllContacts() { 927 final Intent intent = new Intent(this, AllContactsActivity.class); 928 startActivity(intent); 929 } 930 931 public static Intent getAddNumberToContactIntent(CharSequence text) { 932 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 933 intent.putExtra(Intents.Insert.PHONE, text); 934 intent.setType(Contacts.CONTENT_ITEM_TYPE); 935 return intent; 936 } 937 938 public static Intent getInsertContactWithNameIntent(CharSequence text) { 939 final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 940 intent.putExtra(Intents.Insert.NAME, text); 941 return intent; 942 } 943 } 944