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