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