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