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