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