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