DialtactsActivity.java revision edf798aaf0eec1e94e1dc889f929a68c9c89922f
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.Fragment;
24import android.app.FragmentManager;
25import android.app.FragmentManager.BackStackEntry;
26import android.app.FragmentTransaction;
27import android.content.ActivityNotFoundException;
28import android.content.Context;
29import android.content.Intent;
30import android.graphics.drawable.Drawable;
31import android.net.Uri;
32import android.os.Bundle;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.provider.CallLog.Calls;
36import android.provider.ContactsContract.Contacts;
37import android.provider.ContactsContract.Intents;
38import android.provider.ContactsContract.Intents.UI;
39import android.speech.RecognizerIntent;
40import android.telephony.TelephonyManager;
41import android.text.Editable;
42import android.text.Spannable;
43import android.text.SpannableStringBuilder;
44import android.text.TextUtils;
45import android.text.TextWatcher;
46import android.text.style.ImageSpan;
47import android.util.Log;
48import android.view.Menu;
49import android.view.MenuItem;
50import android.view.View;
51import android.view.View.OnFocusChangeListener;
52import android.view.inputmethod.InputMethodManager;
53import android.widget.AbsListView.OnScrollListener;
54import android.widget.EditText;
55import android.widget.PopupMenu;
56import android.widget.Toast;
57
58import com.android.contacts.common.CallUtil;
59import com.android.contacts.common.activity.TransactionSafeActivity;
60import com.android.contacts.common.dialog.ClearFrequentsDialog;
61import com.android.contacts.common.interactions.ImportExportDialogFragment;
62import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
63import com.android.dialer.calllog.CallLogActivity;
64import com.android.dialer.database.DialerDatabaseHelper;
65import com.android.dialer.dialpad.DialpadFragment;
66import com.android.dialer.dialpad.SmartDialNameMatcher;
67import com.android.dialer.dialpad.SmartDialPrefix;
68import com.android.dialer.interactions.PhoneNumberInteraction;
69import com.android.dialer.list.AllContactsActivity;
70import com.android.dialer.list.OnListFragmentScrolledListener;
71import com.android.dialer.list.PhoneFavoriteFragment;
72import com.android.dialer.list.RegularSearchFragment;
73import com.android.dialer.list.SearchFragment;
74import com.android.dialer.list.SmartDialSearchFragment;
75import com.android.dialerbind.DatabaseHelperManager;
76import com.android.internal.telephony.ITelephony;
77
78import java.util.ArrayList;
79
80/**
81 * The dialer tab's title is 'phone', a more common name (see strings.xml).
82 */
83public 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 {
891            super.onBackPressed();
892        }
893    }
894
895    @Override
896    public void onDialpadQueryChanged(String query) {
897        final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
898                SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
899        if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
900            if (DEBUG) {
901                Log.d(TAG, "onDialpadQueryChanged - new query: " + query);
902            }
903            if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
904                // This callback can happen if the dialpad fragment is recreated because of
905                // activity destruction. In that case, don't update the search view because
906                // that would bring the user back to the search fragment regardless of the
907                // previous state of the application. Instead, just return here and let the
908                // fragment manager correctly figure out whatever fragment was last displayed.
909                return;
910            }
911            mSearchView.setText(normalizedQuery);
912        }
913    }
914
915    @Override
916    public void onListFragmentScrollStateChange(int scrollState) {
917        if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
918            hideDialpadFragment(true, false);
919            hideInputMethod(getCurrentFocus());
920        }
921    }
922
923    @Override
924    public void onDialpadFragmentStarted() {
925        setupFakeActionBarItemsForDialpadFragment();
926    }
927
928    private boolean phoneIsInUse() {
929        final TelephonyManager tm = (TelephonyManager) getSystemService(
930                Context.TELEPHONY_SERVICE);
931        return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE;
932    }
933
934    @Override
935    public void onShowAllContacts() {
936        final Intent intent = new Intent(this, AllContactsActivity.class);
937        startActivity(intent);
938    }
939
940    public static Intent getAddNumberToContactIntent(CharSequence text) {
941        final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
942        intent.putExtra(Intents.Insert.PHONE, text);
943        intent.setType(Contacts.CONTENT_ITEM_TYPE);
944        return intent;
945    }
946
947    public static Intent getInsertContactWithNameIntent(CharSequence text) {
948        final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
949        intent.putExtra(Intents.Insert.NAME, text);
950        return intent;
951    }
952}
953