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