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