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