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