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