DialtactsActivity.java revision 5953e8c0237cdbc40a0439734d6ef212b2afa27f
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.app.Fragment;
20import android.app.FragmentTransaction;
21import android.content.ActivityNotFoundException;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.content.res.Configuration;
27import android.content.res.Resources;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.Trace;
31import android.provider.CallLog.Calls;
32import android.speech.RecognizerIntent;
33import android.support.design.widget.CoordinatorLayout;
34import android.support.v4.view.ViewPager;
35import android.support.v7.app.ActionBar;
36import android.telecom.PhoneAccount;
37import android.text.Editable;
38import android.text.TextUtils;
39import android.text.TextWatcher;
40import android.util.Log;
41import android.view.DragEvent;
42import android.view.Gravity;
43import android.view.KeyEvent;
44import android.view.Menu;
45import android.view.MenuItem;
46import android.view.MotionEvent;
47import android.view.View;
48import android.view.View.OnDragListener;
49import android.view.View.OnTouchListener;
50import android.view.ViewTreeObserver;
51import android.view.animation.Animation;
52import android.view.animation.AnimationUtils;
53import android.widget.AbsListView.OnScrollListener;
54import android.widget.EditText;
55import android.widget.ImageButton;
56import android.widget.PopupMenu;
57import android.widget.TextView;
58import android.widget.Toast;
59
60import com.android.contacts.common.dialog.ClearFrequentsDialog;
61import com.android.contacts.common.interactions.ImportExportDialogFragment;
62import com.android.contacts.common.interactions.TouchPointManager;
63import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
64import com.android.contacts.common.util.PermissionsUtil;
65import com.android.contacts.common.widget.FloatingActionButtonController;
66import com.android.contacts.commonbind.analytics.AnalyticsUtil;
67import com.android.dialer.calllog.CallLogActivity;
68import com.android.dialer.calllog.CallLogFragment;
69import com.android.dialer.database.DialerDatabaseHelper;
70import com.android.dialer.dialpad.DialpadFragment;
71import com.android.dialer.dialpad.SmartDialNameMatcher;
72import com.android.dialer.dialpad.SmartDialPrefix;
73import com.android.dialer.interactions.PhoneNumberInteraction;
74import com.android.dialer.list.DragDropController;
75import com.android.dialer.list.ListsFragment;
76import com.android.dialer.list.OnDragDropListener;
77import com.android.dialer.list.OnListFragmentScrolledListener;
78import com.android.dialer.list.PhoneFavoriteSquareTileView;
79import com.android.dialer.list.RegularSearchFragment;
80import com.android.dialer.list.SearchFragment;
81import com.android.dialer.list.SmartDialSearchFragment;
82import com.android.dialer.list.SpeedDialFragment;
83import com.android.dialer.settings.DialerSettingsActivity;
84import com.android.dialer.util.IntentUtil;
85import com.android.dialer.util.TelecomUtil;
86import com.android.dialer.util.IntentUtil.CallIntentBuilder;
87import com.android.dialer.util.DialerUtils;
88import com.android.dialer.widget.ActionBarController;
89import com.android.dialer.widget.SearchEditTextLayout;
90import com.android.dialer.widget.SearchEditTextLayout.Callback;
91import com.android.dialerbind.DatabaseHelperManager;
92import com.android.incallui.Call.LogState;
93import com.android.phone.common.animation.AnimUtils;
94import com.android.phone.common.animation.AnimationListenerAdapter;
95
96import junit.framework.Assert;
97
98import java.util.ArrayList;
99import java.util.List;
100
101/**
102 * The dialer tab's title is 'phone', a more common name (see strings.xml).
103 */
104public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener,
105        DialpadFragment.OnDialpadQueryChangedListener,
106        OnListFragmentScrolledListener,
107        CallLogFragment.HostInterface,
108        DialpadFragment.HostInterface,
109        ListsFragment.HostInterface,
110        SpeedDialFragment.HostInterface,
111        SearchFragment.HostInterface,
112        OnDragDropListener,
113        OnPhoneNumberPickerActionListener,
114        PopupMenu.OnMenuItemClickListener,
115        ViewPager.OnPageChangeListener,
116        ActionBarController.ActivityUi {
117    private static final String TAG = "DialtactsActivity";
118
119    public static final boolean DEBUG = false;
120
121    public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences";
122
123    private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui";
124    private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui";
125    private static final String KEY_SEARCH_QUERY = "search_query";
126    private static final String KEY_FIRST_LAUNCH = "first_launch";
127    private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown";
128
129    private static final String TAG_DIALPAD_FRAGMENT = "dialpad";
130    private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search";
131    private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial";
132    private static final String TAG_FAVORITES_FRAGMENT = "favorites";
133
134    /**
135     * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
136     */
137    private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
138    public static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB";
139
140    private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1;
141
142    private static final int FAB_SCALE_IN_DELAY_MS = 300;
143
144    private CoordinatorLayout mParentLayout;
145
146    /**
147     * Fragment containing the dialpad that slides into view
148     */
149    protected DialpadFragment mDialpadFragment;
150
151    /**
152     * Fragment for searching phone numbers using the alphanumeric keyboard.
153     */
154    private RegularSearchFragment mRegularSearchFragment;
155
156    /**
157     * Fragment for searching phone numbers using the dialpad.
158     */
159    private SmartDialSearchFragment mSmartDialSearchFragment;
160
161    private boolean mIsVisible;
162
163    /**
164     * Animation that slides in.
165     */
166    private Animation mSlideIn;
167
168    /**
169     * Animation that slides out.
170     */
171    private Animation mSlideOut;
172
173    AnimationListenerAdapter mSlideInListener = new AnimationListenerAdapter() {
174        @Override
175        public void onAnimationEnd(Animation animation) {
176            maybeEnterSearchUi();
177        }
178    };
179
180    /**
181     * Listener for after slide out animation completes on dialer fragment.
182     */
183    AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
184        @Override
185        public void onAnimationEnd(Animation animation) {
186            commitDialpadFragmentHide();
187        }
188    };
189
190    /**
191     * Fragment containing the speed dial list, call history list, and all contacts list.
192     */
193    private ListsFragment mListsFragment;
194
195    /**
196     * Tracks whether onSaveInstanceState has been called. If true, no fragment transactions can
197     * be commited.
198     */
199    private boolean mStateSaved;
200    private boolean mIsRestarting;
201    private boolean mInDialpadSearch;
202    private boolean mInRegularSearch;
203    private boolean mClearSearchOnPause;
204    private boolean mIsDialpadShown;
205    private boolean mShowDialpadOnResume;
206
207    /**
208     * Whether or not the device is in landscape orientation.
209     */
210    private boolean mIsLandscape;
211
212    /**
213     * True if the dialpad is only temporarily showing due to being in call
214     */
215    private boolean mInCallDialpadUp;
216
217    /**
218     * True when this activity has been launched for the first time.
219     */
220    private boolean mFirstLaunch;
221
222    /**
223     * Search query to be applied to the SearchView in the ActionBar once
224     * onCreateOptionsMenu has been called.
225     */
226    private String mPendingSearchViewQuery;
227
228    private PopupMenu mOverflowMenu;
229    private EditText mSearchView;
230    private View mVoiceSearchButton;
231
232    private String mSearchQuery;
233
234    private DialerDatabaseHelper mDialerDatabaseHelper;
235    private DragDropController mDragDropController;
236    private ActionBarController mActionBarController;
237
238    private FloatingActionButtonController mFloatingActionButtonController;
239
240    private int mActionBarHeight;
241
242    /**
243     * The text returned from a voice search query.  Set in {@link #onActivityResult} and used in
244     * {@link #onResume()} to populate the search box.
245     */
246    private String mVoiceSearchQuery;
247
248    protected class OptionsPopupMenu extends PopupMenu {
249        public OptionsPopupMenu(Context context, View anchor) {
250            super(context, anchor, Gravity.END);
251        }
252
253        @Override
254        public void show() {
255            final boolean hasContactsPermission =
256                    PermissionsUtil.hasContactsPermissions(DialtactsActivity.this);
257            final Menu menu = getMenu();
258            final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
259            clearFrequents.setVisible(mListsFragment != null &&
260                    mListsFragment.getSpeedDialFragment() != null &&
261                    mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission);
262
263            menu.findItem(R.id.menu_import_export).setVisible(hasContactsPermission);
264            menu.findItem(R.id.menu_add_contact).setVisible(hasContactsPermission);
265
266            menu.findItem(R.id.menu_history).setVisible(
267                    PermissionsUtil.hasPhonePermissions(DialtactsActivity.this));
268            super.show();
269        }
270    }
271
272    /**
273     * Listener that listens to drag events and sends their x and y coordinates to a
274     * {@link DragDropController}.
275     */
276    private class LayoutOnDragListener implements OnDragListener {
277        @Override
278        public boolean onDrag(View v, DragEvent event) {
279            if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
280                mDragDropController.handleDragHovered(v, (int) event.getX(), (int) event.getY());
281            }
282            return true;
283        }
284    }
285
286    /**
287     * Listener used to send search queries to the phone search fragment.
288     */
289    private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {
290        @Override
291        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
292        }
293
294        @Override
295        public void onTextChanged(CharSequence s, int start, int before, int count) {
296            final String newText = s.toString();
297            if (newText.equals(mSearchQuery)) {
298                // If the query hasn't changed (perhaps due to activity being destroyed
299                // and restored, or user launching the same DIAL intent twice), then there is
300                // no need to do anything here.
301                return;
302            }
303            if (DEBUG) {
304                Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText);
305                Log.d(TAG, "Previous Query: " + mSearchQuery);
306            }
307            mSearchQuery = newText;
308
309            // Show search fragment only when the query string is changed to non-empty text.
310            if (!TextUtils.isEmpty(newText)) {
311                // Call enterSearchUi only if we are switching search modes, or showing a search
312                // fragment for the first time.
313                final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||
314                        (!mIsDialpadShown && mInRegularSearch);
315                if (!sameSearchMode) {
316                    enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);
317                }
318            }
319
320            if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
321                mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
322            } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
323                mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
324            }
325        }
326
327        @Override
328        public void afterTextChanged(Editable s) {
329        }
330    };
331
332
333    /**
334     * Open the search UI when the user clicks on the search box.
335     */
336    private final View.OnClickListener mSearchViewOnClickListener = new View.OnClickListener() {
337        @Override
338        public void onClick(View v) {
339            if (!isInSearchUi()) {
340                mActionBarController.onSearchBoxTapped();
341                enterSearchUi(false /* smartDialSearch */, mSearchView.getText().toString(),
342                        true /* animate */);
343            }
344        }
345    };
346
347    /**
348     * Handles the user closing the soft keyboard.
349     */
350    private final View.OnKeyListener mSearchEditTextLayoutListener = new View.OnKeyListener() {
351        @Override
352        public boolean onKey(View v, int keyCode, KeyEvent event) {
353            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
354                if (TextUtils.isEmpty(mSearchView.getText().toString())) {
355                    // If the search term is empty, close the search UI.
356                    maybeExitSearchUi();
357                } else {
358                    // If the search term is not empty, show the dialpad fab.
359                    showFabInSearchUi();
360                }
361            }
362            return false;
363        }
364    };
365
366    @Override
367    public boolean dispatchTouchEvent(MotionEvent ev) {
368        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
369            TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
370        }
371        return super.dispatchTouchEvent(ev);
372    }
373
374    @Override
375    protected void onCreate(Bundle savedInstanceState) {
376        Trace.beginSection(TAG + " onCreate");
377        super.onCreate(savedInstanceState);
378
379        mFirstLaunch = true;
380
381        final Resources resources = getResources();
382        mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height_large);
383
384        Trace.beginSection(TAG + " setContentView");
385        setContentView(R.layout.dialtacts_activity);
386        Trace.endSection();
387        getWindow().setBackgroundDrawable(null);
388
389        Trace.beginSection(TAG + " setup Views");
390        final ActionBar actionBar = getSupportActionBar();
391        actionBar.setCustomView(R.layout.search_edittext);
392        actionBar.setDisplayShowCustomEnabled(true);
393        actionBar.setBackgroundDrawable(null);
394
395        SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar
396                .getCustomView().findViewById(R.id.search_view_container);
397        searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener);
398
399        mActionBarController = new ActionBarController(this, searchEditTextLayout);
400
401        mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view);
402        mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
403        mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button);
404        searchEditTextLayout.findViewById(R.id.search_magnifying_glass)
405                .setOnClickListener(mSearchViewOnClickListener);
406        searchEditTextLayout.findViewById(R.id.search_box_start_search)
407                .setOnClickListener(mSearchViewOnClickListener);
408        searchEditTextLayout.setOnClickListener(mSearchViewOnClickListener);
409        searchEditTextLayout.setCallback(new SearchEditTextLayout.Callback() {
410            @Override
411            public void onBackButtonClicked() {
412                onBackPressed();
413            }
414
415            @Override
416            public void onSearchViewClicked() {
417                // Hide FAB, as the keyboard is shown.
418                mFloatingActionButtonController.scaleOut();
419            }
420        });
421
422        mIsLandscape = getResources().getConfiguration().orientation
423                == Configuration.ORIENTATION_LANDSCAPE;
424
425        final View floatingActionButtonContainer = findViewById(
426                R.id.floating_action_button_container);
427        ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
428        floatingActionButton.setOnClickListener(this);
429        mFloatingActionButtonController = new FloatingActionButtonController(this,
430                floatingActionButtonContainer, floatingActionButton);
431
432        ImageButton optionsMenuButton =
433                (ImageButton) searchEditTextLayout.findViewById(R.id.dialtacts_options_menu_button);
434        optionsMenuButton.setOnClickListener(this);
435        mOverflowMenu = buildOptionsMenu(searchEditTextLayout);
436        optionsMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener());
437
438        // Add the favorites fragment but only if savedInstanceState is null. Otherwise the
439        // fragment manager is responsible for recreating it.
440        if (savedInstanceState == null) {
441            getFragmentManager().beginTransaction()
442                    .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
443                    .commit();
444        } else {
445            mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
446            mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI);
447            mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI);
448            mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH);
449            mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN);
450            mActionBarController.restoreInstanceState(savedInstanceState);
451        }
452
453        final boolean isLayoutRtl = DialerUtils.isRtl();
454        if (mIsLandscape) {
455            mSlideIn = AnimationUtils.loadAnimation(this,
456                    isLayoutRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
457            mSlideOut = AnimationUtils.loadAnimation(this,
458                    isLayoutRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
459        } else {
460            mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
461            mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
462        }
463
464        mSlideIn.setInterpolator(AnimUtils.EASE_IN);
465        mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
466
467        mSlideIn.setAnimationListener(mSlideInListener);
468        mSlideOut.setAnimationListener(mSlideOutListener);
469
470        mParentLayout = (CoordinatorLayout) findViewById(R.id.dialtacts_mainlayout);
471        mParentLayout.setOnDragListener(new LayoutOnDragListener());
472        floatingActionButtonContainer.getViewTreeObserver().addOnGlobalLayoutListener(
473                new ViewTreeObserver.OnGlobalLayoutListener() {
474                    @Override
475                    public void onGlobalLayout() {
476                        final ViewTreeObserver observer =
477                                floatingActionButtonContainer.getViewTreeObserver();
478                        if (!observer.isAlive()) {
479                            return;
480                        }
481                        observer.removeOnGlobalLayoutListener(this);
482                        int screenWidth = mParentLayout.getWidth();
483                        mFloatingActionButtonController.setScreenWidth(screenWidth);
484                        mFloatingActionButtonController.align(
485                                getFabAlignment(), false /* animate */);
486                    }
487                });
488
489        Trace.endSection();
490
491        Trace.beginSection(TAG + " initialize smart dialing");
492        mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this);
493        SmartDialPrefix.initializeNanpSettings(this);
494        Trace.endSection();
495        Trace.endSection();
496    }
497
498    @Override
499    protected void onResume() {
500        Trace.beginSection(TAG + " onResume");
501        super.onResume();
502
503        mStateSaved = false;
504        if (mFirstLaunch) {
505            displayFragment(getIntent());
506        } else if (!phoneIsInUse() && mInCallDialpadUp) {
507            hideDialpadFragment(false, true);
508            mInCallDialpadUp = false;
509        } else if (mShowDialpadOnResume) {
510            showDialpadFragment(false);
511            mShowDialpadOnResume = false;
512        }
513
514        // If there was a voice query result returned in the {@link #onActivityResult} callback, it
515        // will have been stashed in mVoiceSearchQuery since the search results fragment cannot be
516        // shown until onResume has completed.  Active the search UI and set the search term now.
517        if (!TextUtils.isEmpty(mVoiceSearchQuery)) {
518            mActionBarController.onSearchBoxTapped();
519            mSearchView.setText(mVoiceSearchQuery);
520            mVoiceSearchQuery = null;
521        }
522
523        mFirstLaunch = false;
524
525        if (mIsRestarting) {
526            // This is only called when the activity goes from resumed -> paused -> resumed, so it
527            // will not cause an extra view to be sent out on rotation
528            if (mIsDialpadShown) {
529                AnalyticsUtil.sendScreenView(mDialpadFragment, this);
530            }
531            mIsRestarting = false;
532        }
533
534        prepareVoiceSearchButton();
535        mDialerDatabaseHelper.startSmartDialUpdateThread();
536        mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
537
538        if (Calls.CONTENT_TYPE.equals(getIntent().getType())) {
539            // Externally specified extras take precedence to EXTRA_SHOW_TAB, which is only
540            // used internally.
541            final Bundle extras = getIntent().getExtras();
542            if (extras != null
543                    && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) {
544                mListsFragment.showTab(ListsFragment.TAB_INDEX_VOICEMAIL);
545            } else {
546                mListsFragment.showTab(ListsFragment.TAB_INDEX_HISTORY);
547            }
548        } else if (getIntent().hasExtra(EXTRA_SHOW_TAB)) {
549            int index = getIntent().getIntExtra(EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_SPEED_DIAL);
550            if (index < mListsFragment.getTabCount()) {
551                mListsFragment.showTab(index);
552            }
553        }
554
555        setSearchBoxHint();
556
557        Trace.endSection();
558    }
559
560    @Override
561    protected void onRestart() {
562        super.onRestart();
563        mIsRestarting = true;
564    }
565
566    @Override
567    protected void onStart() {
568        super.onStart();
569        mIsVisible = true;
570    }
571
572    @Override
573    protected void onStop() {
574        mIsVisible = false;
575        super.onStop();
576    }
577
578    /**
579     * Returns true when the Activity is currently visible (between onStart and onStop).
580     */
581    /* package */ boolean isVisible() {
582        return mIsVisible;
583    }
584
585    @Override
586    protected void onPause() {
587        if (mClearSearchOnPause) {
588            hideDialpadAndSearchUi();
589            mClearSearchOnPause = false;
590        }
591        if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) {
592            commitDialpadFragmentHide();
593        }
594        super.onPause();
595    }
596
597    @Override
598    protected void onSaveInstanceState(Bundle outState) {
599        mIsVisible = false;
600        super.onSaveInstanceState(outState);
601        outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
602        outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
603        outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
604        outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
605        outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown);
606        mActionBarController.saveInstanceState(outState);
607        mStateSaved = true;
608    }
609
610    @Override
611    public void onAttachFragment(Fragment fragment) {
612        if (fragment instanceof DialpadFragment) {
613            mDialpadFragment = (DialpadFragment) fragment;
614            if (!mIsDialpadShown && !mShowDialpadOnResume) {
615                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
616                transaction.hide(mDialpadFragment);
617                transaction.commit();
618            }
619        } else if (fragment instanceof SmartDialSearchFragment) {
620            mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
621            mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this);
622        } else if (fragment instanceof SearchFragment) {
623            mRegularSearchFragment = (RegularSearchFragment) fragment;
624            mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this);
625        } else if (fragment instanceof ListsFragment) {
626            mListsFragment = (ListsFragment) fragment;
627            mListsFragment.addOnPageChangeListener(this);
628        }
629    }
630
631    protected void handleMenuSettings() {
632        final Intent intent = new Intent(this, DialerSettingsActivity.class);
633        startActivity(intent);
634    }
635
636    @Override
637    public void onClick(View view) {
638        switch (view.getId()) {
639            case R.id.floating_action_button:
640                if (mListsFragment.getCurrentTabIndex()
641                        == ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch) {
642                    DialerUtils.startActivityWithErrorToast(
643                            this,
644                            IntentUtil.getNewContactIntent(),
645                            R.string.add_contact_not_available);
646                } else if (!mIsDialpadShown) {
647                    mInCallDialpadUp = false;
648                    showDialpadFragment(true);
649                }
650                break;
651            case R.id.voice_search_button:
652                try {
653                    startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
654                            ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
655                } catch (ActivityNotFoundException e) {
656                    Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available,
657                            Toast.LENGTH_SHORT).show();
658                }
659                break;
660            case R.id.dialtacts_options_menu_button:
661                mOverflowMenu.show();
662                break;
663            default: {
664                Log.wtf(TAG, "Unexpected onClick event from " + view);
665                break;
666            }
667        }
668    }
669
670    @Override
671    public boolean onMenuItemClick(MenuItem item) {
672        if (!isVisible()) {
673            return true;
674        }
675
676        switch (item.getItemId()) {
677            case R.id.menu_history:
678                // Use explicit CallLogActivity intent instead of ACTION_VIEW +
679                // CONTENT_TYPE, so that we always open our call log from our dialer
680                final Intent intent = new Intent(this, CallLogActivity.class);
681                startActivity(intent);
682                break;
683            case R.id.menu_add_contact:
684                DialerUtils.startActivityWithErrorToast(
685                        this,
686                        IntentUtil.getNewContactIntent(),
687                        R.string.add_contact_not_available);
688                break;
689            case R.id.menu_import_export:
690                // We hard-code the "contactsAreAvailable" argument because doing it properly would
691                // involve querying a {@link ProviderStatusLoader}, which we don't want to do right
692                // now in Dialtacts for (potential) performance reasons. Compare with how it is
693                // done in {@link PeopleActivity}.
694                if (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) {
695                    ImportExportDialogFragment.show(getFragmentManager(), true,
696                            DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_FAVORITES);
697                } else {
698                    ImportExportDialogFragment.show(getFragmentManager(), true,
699                            DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_DEFAULT);
700                }
701                return true;
702            case R.id.menu_clear_frequents:
703                ClearFrequentsDialog.show(getFragmentManager());
704                return true;
705            case R.id.menu_call_settings:
706                handleMenuSettings();
707                return true;
708        }
709        return false;
710    }
711
712    @Override
713    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
714        if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) {
715            if (resultCode == RESULT_OK) {
716                final ArrayList<String> matches = data.getStringArrayListExtra(
717                        RecognizerIntent.EXTRA_RESULTS);
718                if (matches.size() > 0) {
719                    final String match = matches.get(0);
720                    mVoiceSearchQuery = match;
721                } else {
722                    Log.e(TAG, "Voice search - nothing heard");
723                }
724            } else {
725                Log.e(TAG, "Voice search failed");
726            }
727        }
728        super.onActivityResult(requestCode, resultCode, data);
729    }
730
731    /**
732     * Update the number of unread voicemails (potentially other tabs) displayed next to the tab
733     * icon.
734     */
735    public void updateTabUnreadCounts() {
736        mListsFragment.updateTabUnreadCounts();
737    }
738
739    /**
740     * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual
741     * updates are handled by a callback which is invoked after the dialpad fragment is shown.
742     * @see #onDialpadShown
743     */
744    private void showDialpadFragment(boolean animate) {
745        if (mIsDialpadShown || mStateSaved) {
746            return;
747        }
748        mIsDialpadShown = true;
749
750        mListsFragment.setUserVisibleHint(false);
751
752        final FragmentTransaction ft = getFragmentManager().beginTransaction();
753        if (mDialpadFragment == null) {
754            mDialpadFragment = new DialpadFragment();
755            ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT);
756        } else {
757            ft.show(mDialpadFragment);
758        }
759
760        mDialpadFragment.setAnimate(animate);
761        AnalyticsUtil.sendScreenView(mDialpadFragment);
762        ft.commit();
763
764        if (animate) {
765            mFloatingActionButtonController.scaleOut();
766        } else {
767            mFloatingActionButtonController.setVisible(false);
768            maybeEnterSearchUi();
769        }
770        mActionBarController.onDialpadUp();
771
772        mListsFragment.getView().animate().alpha(0).withLayer();
773    }
774
775    /**
776     * Callback from child DialpadFragment when the dialpad is shown.
777     */
778    public void onDialpadShown() {
779        Assert.assertNotNull(mDialpadFragment);
780        if (mDialpadFragment.getAnimate()) {
781            mDialpadFragment.getView().startAnimation(mSlideIn);
782        } else {
783            mDialpadFragment.setYFraction(0);
784        }
785
786        updateSearchFragmentPosition();
787    }
788
789    /**
790     * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in
791     * a callback after the hide animation ends.
792     * @see #commitDialpadFragmentHide
793     */
794    public void hideDialpadFragment(boolean animate, boolean clearDialpad) {
795        if (mDialpadFragment == null || mDialpadFragment.getView() == null) {
796            return;
797        }
798        if (clearDialpad) {
799            mDialpadFragment.clearDialpad();
800        }
801        if (!mIsDialpadShown) {
802            return;
803        }
804        mIsDialpadShown = false;
805        mDialpadFragment.setAnimate(animate);
806        mListsFragment.setUserVisibleHint(true);
807        mListsFragment.sendScreenViewForCurrentPosition();
808
809        updateSearchFragmentPosition();
810
811        mFloatingActionButtonController.align(getFabAlignment(), animate);
812        if (animate) {
813            mDialpadFragment.getView().startAnimation(mSlideOut);
814        } else {
815            commitDialpadFragmentHide();
816        }
817
818        mActionBarController.onDialpadDown();
819
820        if (isInSearchUi()) {
821            if (TextUtils.isEmpty(mSearchQuery)) {
822                exitSearchUi();
823            }
824        }
825    }
826
827    /**
828     * Finishes hiding the dialpad fragment after any animations are completed.
829     */
830    private void commitDialpadFragmentHide() {
831        if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) {
832            final FragmentTransaction ft = getFragmentManager().beginTransaction();
833            ft.hide(mDialpadFragment);
834            ft.commit();
835        }
836        mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
837    }
838
839    private void updateSearchFragmentPosition() {
840        SearchFragment fragment = null;
841        if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
842            fragment = mSmartDialSearchFragment;
843        } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
844            fragment = mRegularSearchFragment;
845        }
846        if (fragment != null && fragment.isVisible()) {
847            fragment.updatePosition(true /* animate */);
848        }
849    }
850
851    @Override
852    public boolean isInSearchUi() {
853        return mInDialpadSearch || mInRegularSearch;
854    }
855
856    @Override
857    public boolean hasSearchQuery() {
858        return !TextUtils.isEmpty(mSearchQuery);
859    }
860
861    @Override
862    public boolean shouldShowActionBar() {
863        return mListsFragment.shouldShowActionBar();
864    }
865
866    private void setNotInSearchUi() {
867        mInDialpadSearch = false;
868        mInRegularSearch = false;
869    }
870
871    private void hideDialpadAndSearchUi() {
872        if (mIsDialpadShown) {
873            hideDialpadFragment(false, true);
874        } else {
875            exitSearchUi();
876        }
877    }
878
879    private void prepareVoiceSearchButton() {
880        final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
881        if (canIntentBeHandled(voiceIntent)) {
882            mVoiceSearchButton.setVisibility(View.VISIBLE);
883            mVoiceSearchButton.setOnClickListener(this);
884        } else {
885            mVoiceSearchButton.setVisibility(View.GONE);
886        }
887    }
888
889    protected int getSearchBoxHint () {
890        return R.string.dialer_hint_find_contact;
891    }
892
893    /**
894     * Sets the hint text for the contacts search box
895     */
896    private void setSearchBoxHint() {
897        SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) getSupportActionBar()
898                .getCustomView().findViewById(R.id.search_view_container);
899        ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search))
900                .setHint(getSearchBoxHint());
901    }
902
903    protected OptionsPopupMenu buildOptionsMenu(View invoker) {
904        final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker);
905        popupMenu.inflate(R.menu.dialtacts_options);
906        popupMenu.setOnMenuItemClickListener(this);
907        return popupMenu;
908    }
909
910    @Override
911    public boolean onCreateOptionsMenu(Menu menu) {
912        if (mPendingSearchViewQuery != null) {
913            mSearchView.setText(mPendingSearchViewQuery);
914            mPendingSearchViewQuery = null;
915        }
916        if (mActionBarController != null) {
917            mActionBarController.restoreActionBarOffset();
918        }
919        return false;
920    }
921
922    /**
923     * Returns true if the intent is due to hitting the green send key (hardware call button:
924     * KEYCODE_CALL) while in a call.
925     *
926     * @param intent the intent that launched this activity
927     * @return true if the intent is due to hitting the green send key while in a call
928     */
929    private boolean isSendKeyWhileInCall(Intent intent) {
930        // If there is a call in progress and the user launched the dialer by hitting the call
931        // button, go straight to the in-call screen.
932        final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction());
933
934        if (callKey) {
935            TelecomUtil.showInCallScreen(this, false);
936            return true;
937        }
938
939        return false;
940    }
941
942    /**
943     * Sets the current tab based on the intent's request type
944     *
945     * @param intent Intent that contains information about which tab should be selected
946     */
947    private void displayFragment(Intent intent) {
948        // If we got here by hitting send and we're in call forward along to the in-call activity
949        if (isSendKeyWhileInCall(intent)) {
950            finish();
951            return;
952        }
953
954        final boolean showDialpadChooser = phoneIsInUse() && !DialpadFragment.isAddCallMode(intent);
955        if (showDialpadChooser || (intent.getData() != null && isDialIntent(intent))) {
956            showDialpadFragment(false);
957            mDialpadFragment.setStartedFromNewIntent(true);
958            if (showDialpadChooser && !mDialpadFragment.isVisible()) {
959                mInCallDialpadUp = true;
960            }
961        }
962    }
963
964    @Override
965    public void onNewIntent(Intent newIntent) {
966        setIntent(newIntent);
967
968        mStateSaved = false;
969        displayFragment(newIntent);
970
971        invalidateOptionsMenu();
972    }
973
974    /** Returns true if the given intent contains a phone number to populate the dialer with */
975    private boolean isDialIntent(Intent intent) {
976        final String action = intent.getAction();
977        if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
978            return true;
979        }
980        if (Intent.ACTION_VIEW.equals(action)) {
981            final Uri data = intent.getData();
982            if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) {
983                return true;
984            }
985        }
986        return false;
987    }
988
989    /**
990     * Shows the search fragment
991     */
992    private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) {
993        if (mStateSaved || getFragmentManager().isDestroyed()) {
994            // Weird race condition where fragment is doing work after the activity is destroyed
995            // due to talkback being on (b/10209937). Just return since we can't do any
996            // constructive here.
997            return;
998        }
999
1000        if (DEBUG) {
1001            Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch);
1002        }
1003
1004        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
1005        if (mInDialpadSearch && mSmartDialSearchFragment != null) {
1006            transaction.remove(mSmartDialSearchFragment);
1007        } else if (mInRegularSearch && mRegularSearchFragment != null) {
1008            transaction.remove(mRegularSearchFragment);
1009        }
1010
1011        final String tag;
1012        if (smartDialSearch) {
1013            tag = TAG_SMARTDIAL_SEARCH_FRAGMENT;
1014        } else {
1015            tag = TAG_REGULAR_SEARCH_FRAGMENT;
1016        }
1017        mInDialpadSearch = smartDialSearch;
1018        mInRegularSearch = !smartDialSearch;
1019
1020        mFloatingActionButtonController.scaleOut();
1021
1022        SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
1023        if (animate) {
1024            transaction.setCustomAnimations(android.R.animator.fade_in, 0);
1025        } else {
1026            transaction.setTransition(FragmentTransaction.TRANSIT_NONE);
1027        }
1028        if (fragment == null) {
1029            if (smartDialSearch) {
1030                fragment = new SmartDialSearchFragment();
1031            } else {
1032                fragment = new RegularSearchFragment();
1033                fragment.setOnTouchListener(new View.OnTouchListener() {
1034                    @Override
1035                    public boolean onTouch(View v, MotionEvent event) {
1036                        // Show the FAB when the user touches the lists fragment and the soft
1037                        // keyboard is hidden.
1038                        showFabInSearchUi();
1039                        return false;
1040                    }
1041                });
1042            }
1043            transaction.add(R.id.dialtacts_frame, fragment, tag);
1044        } else {
1045            transaction.show(fragment);
1046        }
1047        // DialtactsActivity will provide the options menu
1048        fragment.setHasOptionsMenu(false);
1049        fragment.setShowEmptyListForNullQuery(true);
1050        if (!smartDialSearch) {
1051            fragment.setQueryString(query, false /* delaySelection */);
1052        }
1053        transaction.commit();
1054
1055        if (animate) {
1056            mListsFragment.getView().animate().alpha(0).withLayer();
1057        }
1058        mListsFragment.setUserVisibleHint(false);
1059    }
1060
1061    /**
1062     * Hides the search fragment
1063     */
1064    private void exitSearchUi() {
1065        // See related bug in enterSearchUI();
1066        if (getFragmentManager().isDestroyed() || mStateSaved) {
1067            return;
1068        }
1069
1070        mSearchView.setText(null);
1071
1072        if (mDialpadFragment != null) {
1073            mDialpadFragment.clearDialpad();
1074        }
1075
1076        setNotInSearchUi();
1077
1078        // Restore the FAB for the lists fragment.
1079        if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) {
1080            mFloatingActionButtonController.setVisible(false);
1081        }
1082        mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
1083        onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */);
1084        onPageSelected(mListsFragment.getCurrentTabIndex());
1085
1086        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
1087        if (mSmartDialSearchFragment != null) {
1088            transaction.remove(mSmartDialSearchFragment);
1089        }
1090        if (mRegularSearchFragment != null) {
1091            transaction.remove(mRegularSearchFragment);
1092        }
1093        transaction.commit();
1094
1095        mListsFragment.getView().animate().alpha(1).withLayer();
1096
1097        if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
1098            // If the dialpad fragment wasn't previously visible, then send a screen view because
1099            // we are exiting regular search. Otherwise, the screen view will be sent by
1100            // {@link #hideDialpadFragment}.
1101            mListsFragment.sendScreenViewForCurrentPosition();
1102            mListsFragment.setUserVisibleHint(true);
1103        }
1104
1105        mActionBarController.onSearchUiExited();
1106    }
1107
1108    @Override
1109    public void onBackPressed() {
1110        if (mStateSaved) {
1111            return;
1112        }
1113        if (mIsDialpadShown) {
1114            if (TextUtils.isEmpty(mSearchQuery) ||
1115                    (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()
1116                            && mSmartDialSearchFragment.getAdapter().getCount() == 0)) {
1117                exitSearchUi();
1118            }
1119            hideDialpadFragment(true, false);
1120        } else if (isInSearchUi()) {
1121            exitSearchUi();
1122            DialerUtils.hideInputMethod(mParentLayout);
1123        } else {
1124            super.onBackPressed();
1125        }
1126    }
1127
1128    private void maybeEnterSearchUi() {
1129        if (!isInSearchUi()) {
1130            enterSearchUi(true /* isSmartDial */, mSearchQuery, false);
1131        }
1132    }
1133
1134    /**
1135     * @return True if the search UI was exited, false otherwise
1136     */
1137    private boolean maybeExitSearchUi() {
1138        if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) {
1139            exitSearchUi();
1140            DialerUtils.hideInputMethod(mParentLayout);
1141            return true;
1142        }
1143        return false;
1144    }
1145
1146    private void showFabInSearchUi() {
1147        mFloatingActionButtonController.changeIcon(
1148                getResources().getDrawable(R.drawable.fab_ic_dial),
1149                getResources().getString(R.string.action_menu_dialpad_button));
1150        mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
1151        mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
1152    }
1153
1154    @Override
1155    public void onDialpadQueryChanged(String query) {
1156        if (mSmartDialSearchFragment != null) {
1157            mSmartDialSearchFragment.setAddToContactNumber(query);
1158        }
1159        final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
1160                SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
1161
1162        if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
1163            if (DEBUG) {
1164                Log.d(TAG, "onDialpadQueryChanged - new query: " + query);
1165            }
1166            if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
1167                // This callback can happen if the dialpad fragment is recreated because of
1168                // activity destruction. In that case, don't update the search view because
1169                // that would bring the user back to the search fragment regardless of the
1170                // previous state of the application. Instead, just return here and let the
1171                // fragment manager correctly figure out whatever fragment was last displayed.
1172                if (!TextUtils.isEmpty(normalizedQuery)) {
1173                    mPendingSearchViewQuery = normalizedQuery;
1174                }
1175                return;
1176            }
1177            mSearchView.setText(normalizedQuery);
1178        }
1179
1180        try {
1181            if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
1182                mDialpadFragment.process_quote_emergency_unquote(normalizedQuery);
1183            }
1184        } catch (Exception ignored) {
1185            // Skip any exceptions for this piece of code
1186        }
1187    }
1188
1189    @Override
1190    public boolean onDialpadSpacerTouchWithEmptyQuery() {
1191        if (mInDialpadSearch && mSmartDialSearchFragment != null
1192                && !mSmartDialSearchFragment.isShowingPermissionRequest()) {
1193            hideDialpadFragment(true /* animate */, true /* clearDialpad */);
1194            return true;
1195        }
1196        return false;
1197    }
1198
1199    @Override
1200    public void onListFragmentScrollStateChange(int scrollState) {
1201        if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1202            hideDialpadFragment(true, false);
1203            DialerUtils.hideInputMethod(mParentLayout);
1204        }
1205    }
1206
1207    @Override
1208    public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount,
1209                                     int totalItemCount) {
1210        // TODO: No-op for now. This should eventually show/hide the actionBar based on
1211        // interactions with the ListsFragments.
1212    }
1213
1214    private boolean phoneIsInUse() {
1215        return TelecomUtil.isInCall(this);
1216    }
1217
1218    private boolean canIntentBeHandled(Intent intent) {
1219        final PackageManager packageManager = getPackageManager();
1220        final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
1221                PackageManager.MATCH_DEFAULT_ONLY);
1222        return resolveInfo != null && resolveInfo.size() > 0;
1223    }
1224
1225    /**
1226     * Called when the user has long-pressed a contact tile to start a drag operation.
1227     */
1228    @Override
1229    public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
1230        mListsFragment.showRemoveView(true);
1231    }
1232
1233    @Override
1234    public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {
1235    }
1236
1237    /**
1238     * Called when the user has released a contact tile after long-pressing it.
1239     */
1240    @Override
1241    public void onDragFinished(int x, int y) {
1242        mListsFragment.showRemoveView(false);
1243    }
1244
1245    @Override
1246    public void onDroppedOnRemove() {}
1247
1248    /**
1249     * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer
1250     * once it has been attached to the activity.
1251     */
1252    @Override
1253    public void setDragDropController(DragDropController dragController) {
1254        mDragDropController = dragController;
1255        mListsFragment.getRemoveView().setDragDropController(dragController);
1256    }
1257
1258    /**
1259     * Implemented to satisfy {@link SpeedDialFragment.HostInterface}
1260     */
1261    @Override
1262    public void showAllContactsTab() {
1263        if (mListsFragment != null) {
1264            mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS);
1265        }
1266    }
1267
1268    /**
1269     * Implemented to satisfy {@link CallLogFragment.HostInterface}
1270     */
1271    @Override
1272    public void showDialpad() {
1273        showDialpadFragment(true);
1274    }
1275
1276    @Override
1277    public void onPickPhoneNumberAction(Uri dataUri, int callInitiationType) {
1278        mClearSearchOnPause = true;
1279        PhoneNumberInteraction.startInteractionForPhoneCall(
1280                DialtactsActivity.this, dataUri, callInitiationType);
1281    }
1282
1283    @Override
1284    public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall,
1285            int callInitiationType) {
1286        if (phoneNumber == null) {
1287            // Invalid phone number, but let the call go through so that InCallUI can show
1288            // an error message.
1289            phoneNumber = "";
1290        }
1291
1292        final Intent intent = new CallIntentBuilder(phoneNumber)
1293                .setIsVideoCall(isVideoCall)
1294                .setCallInitiationType(callInitiationType)
1295                .build();
1296
1297        DialerUtils.startActivityWithErrorToast(this, intent);
1298        mClearSearchOnPause = true;
1299    }
1300
1301    @Override
1302    public void onShortcutIntentCreated(Intent intent) {
1303        Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
1304    }
1305
1306    @Override
1307    public void onHomeInActionBarSelected() {
1308        exitSearchUi();
1309    }
1310
1311    @Override
1312    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
1313        int tabIndex = mListsFragment.getCurrentTabIndex();
1314
1315        // Scroll the button from center to end when moving from the Speed Dial to Call History tab.
1316        // In RTL, scroll when the current tab is Call History instead, since the order of the tabs
1317        // is reversed and the ViewPager returns the left tab position during scroll.
1318        boolean isRtl = DialerUtils.isRtl();
1319        if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) {
1320            mFloatingActionButtonController.onPageScrolled(positionOffset);
1321        } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_HISTORY && !mIsLandscape) {
1322            mFloatingActionButtonController.onPageScrolled(1 - positionOffset);
1323        } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) {
1324            mFloatingActionButtonController.onPageScrolled(1);
1325        }
1326    }
1327
1328    @Override
1329    public void onPageSelected(int position) {
1330        int tabIndex = mListsFragment.getCurrentTabIndex();
1331        if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
1332            mFloatingActionButtonController.changeIcon(
1333                    getResources().getDrawable(R.drawable.ic_person_add_24dp),
1334                    getResources().getString(R.string.search_shortcut_create_new_contact));
1335        } else {
1336            mFloatingActionButtonController.changeIcon(
1337                    getResources().getDrawable(R.drawable.fab_ic_dial),
1338                    getResources().getString(R.string.action_menu_dialpad_button));
1339        }
1340    }
1341
1342    @Override
1343    public void onPageScrollStateChanged(int state) {
1344    }
1345
1346    @Override
1347    public boolean isActionBarShowing() {
1348        return mActionBarController.isActionBarShowing();
1349    }
1350
1351    @Override
1352    public ActionBarController getActionBarController() {
1353        return mActionBarController;
1354    }
1355
1356    @Override
1357    public boolean isDialpadShown() {
1358        return mIsDialpadShown;
1359    }
1360
1361    @Override
1362    public int getDialpadHeight() {
1363        if (mDialpadFragment != null) {
1364            return mDialpadFragment.getDialpadHeight();
1365        }
1366        return 0;
1367    }
1368
1369    @Override
1370    public int getActionBarHideOffset() {
1371        return getSupportActionBar().getHideOffset();
1372    }
1373
1374    @Override
1375    public void setActionBarHideOffset(int offset) {
1376        getSupportActionBar().setHideOffset(offset);
1377    }
1378
1379    @Override
1380    public int getActionBarHeight() {
1381        return mActionBarHeight;
1382    }
1383
1384    private int getFabAlignment() {
1385        if (!mIsLandscape && !isInSearchUi() &&
1386                mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) {
1387            return FloatingActionButtonController.ALIGN_MIDDLE;
1388        }
1389        return FloatingActionButtonController.ALIGN_END;
1390    }
1391}
1392