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