DialtactsActivity.java revision 7b2f63c7c5485bdd62a3e0ed8446b6011a786d3b
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.ScreenTagConstants;
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(ScreenTagConstants.DIALPAD, this,
532                        ScreenTagConstants.DIALPAD_DIALER);
533            }
534            mIsRestarting = false;
535        }
536
537        prepareVoiceSearchButton();
538        mDialerDatabaseHelper.startSmartDialUpdateThread();
539        mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
540
541        if (Calls.CONTENT_TYPE.equals(getIntent().getType())) {
542            // Externally specified extras take precedence to EXTRA_SHOW_TAB, which is only
543            // used internally.
544            final Bundle extras = getIntent().getExtras();
545            if (extras != null
546                    && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) {
547                mListsFragment.showTab(ListsFragment.TAB_INDEX_VOICEMAIL);
548            } else {
549                mListsFragment.showTab(ListsFragment.TAB_INDEX_HISTORY);
550            }
551        } else if (getIntent().hasExtra(EXTRA_SHOW_TAB)) {
552            int index = getIntent().getIntExtra(EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_SPEED_DIAL);
553            if (index < mListsFragment.getTabCount()) {
554                mListsFragment.showTab(index);
555            }
556        }
557
558        setSearchBoxHint();
559
560        Trace.endSection();
561    }
562
563    @Override
564    protected void onRestart() {
565        super.onRestart();
566        mIsRestarting = true;
567    }
568
569    @Override
570    protected void onPause() {
571        if (mClearSearchOnPause) {
572            hideDialpadAndSearchUi();
573            mClearSearchOnPause = false;
574        }
575        if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) {
576            commitDialpadFragmentHide();
577        }
578        super.onPause();
579    }
580
581    @Override
582    protected void onSaveInstanceState(Bundle outState) {
583        super.onSaveInstanceState(outState);
584        outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
585        outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
586        outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
587        outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
588        outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown);
589        mActionBarController.saveInstanceState(outState);
590        mStateSaved = true;
591    }
592
593    @Override
594    public void onAttachFragment(Fragment fragment) {
595        if (fragment instanceof DialpadFragment) {
596            mDialpadFragment = (DialpadFragment) fragment;
597            if (!mIsDialpadShown && !mShowDialpadOnResume) {
598                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
599                transaction.hide(mDialpadFragment);
600                transaction.commit();
601            }
602        } else if (fragment instanceof SmartDialSearchFragment) {
603            mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
604            mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this);
605            if (!TextUtils.isEmpty(mDialpadQuery)) {
606                mSmartDialSearchFragment.setAddToContactNumber(mDialpadQuery);
607            }
608        } else if (fragment instanceof SearchFragment) {
609            mRegularSearchFragment = (RegularSearchFragment) fragment;
610            mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this);
611        } else if (fragment instanceof ListsFragment) {
612            mListsFragment = (ListsFragment) fragment;
613            mListsFragment.addOnPageChangeListener(this);
614        }
615    }
616
617    protected void handleMenuSettings() {
618        final Intent intent = new Intent(this, DialerSettingsActivity.class);
619        startActivity(intent);
620    }
621
622    @Override
623    public void onClick(View view) {
624        switch (view.getId()) {
625            case R.id.floating_action_button:
626                if (mListsFragment.getCurrentTabIndex()
627                        == ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch) {
628                    DialerUtils.startActivityWithErrorToast(
629                            this,
630                            IntentUtil.getNewContactIntent(),
631                            R.string.add_contact_not_available);
632                } else if (!mIsDialpadShown) {
633                    mInCallDialpadUp = false;
634                    showDialpadFragment(true);
635                }
636                break;
637            case R.id.voice_search_button:
638                try {
639                    startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
640                            ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
641                } catch (ActivityNotFoundException e) {
642                    Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available,
643                            Toast.LENGTH_SHORT).show();
644                }
645                break;
646            case R.id.dialtacts_options_menu_button:
647                mOverflowMenu.show();
648                break;
649            default: {
650                Log.wtf(TAG, "Unexpected onClick event from " + view);
651                break;
652            }
653        }
654    }
655
656    @Override
657    public boolean onMenuItemClick(MenuItem item) {
658        if (!isSafeToCommitTransactions()) {
659            return true;
660        }
661
662        switch (item.getItemId()) {
663            case R.id.menu_history:
664                // Use explicit CallLogActivity intent instead of ACTION_VIEW +
665                // CONTENT_TYPE, so that we always open our call log from our dialer
666                final Intent intent = new Intent(this, CallLogActivity.class);
667                startActivity(intent);
668                break;
669            case R.id.menu_add_contact:
670                DialerUtils.startActivityWithErrorToast(
671                        this,
672                        IntentUtil.getNewContactIntent(),
673                        R.string.add_contact_not_available);
674                break;
675            case R.id.menu_import_export:
676                // We hard-code the "contactsAreAvailable" argument because doing it properly would
677                // involve querying a {@link ProviderStatusLoader}, which we don't want to do right
678                // now in Dialtacts for (potential) performance reasons. Compare with how it is
679                // done in {@link PeopleActivity}.
680                if (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) {
681                    ImportExportDialogFragment.show(getFragmentManager(), true,
682                            DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_FAVORITES);
683                } else {
684                    ImportExportDialogFragment.show(getFragmentManager(), true,
685                            DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_DEFAULT);
686                }
687                Logger.logScreenView(ScreenTagConstants.IMPORT_EXPORT_CONTACTS, this, null);
688                return true;
689            case R.id.menu_clear_frequents:
690                ClearFrequentsDialog.show(getFragmentManager());
691                return true;
692            case R.id.menu_call_settings:
693                handleMenuSettings();
694                return true;
695        }
696        return false;
697    }
698
699    @Override
700    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
701        if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) {
702            if (resultCode == RESULT_OK) {
703                final ArrayList<String> matches = data.getStringArrayListExtra(
704                        RecognizerIntent.EXTRA_RESULTS);
705                if (matches.size() > 0) {
706                    final String match = matches.get(0);
707                    mVoiceSearchQuery = match;
708                } else {
709                    Log.e(TAG, "Voice search - nothing heard");
710                }
711            } else {
712                Log.e(TAG, "Voice search failed");
713            }
714        }
715        super.onActivityResult(requestCode, resultCode, data);
716    }
717
718    /**
719     * Update the number of unread voicemails (potentially other tabs) displayed next to the tab
720     * icon.
721     */
722    public void updateTabUnreadCounts() {
723        mListsFragment.updateTabUnreadCounts();
724    }
725
726    /**
727     * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual
728     * updates are handled by a callback which is invoked after the dialpad fragment is shown.
729     * @see #onDialpadShown
730     */
731    private void showDialpadFragment(boolean animate) {
732        if (mIsDialpadShown || mStateSaved) {
733            return;
734        }
735        mIsDialpadShown = true;
736
737        mListsFragment.setUserVisibleHint(false);
738
739        final FragmentTransaction ft = getFragmentManager().beginTransaction();
740        if (mDialpadFragment == null) {
741            mDialpadFragment = new DialpadFragment();
742            ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT);
743        } else {
744            ft.show(mDialpadFragment);
745        }
746
747        mDialpadFragment.setAnimate(animate);
748        // logScreenView is used here explicitly to provide the activity as the DialpadFragment
749        // might not have been attached yet.
750        Logger.logScreenView(ScreenTagConstants.DIALPAD, this,
751                ScreenTagConstants.DIALPAD_DIALER);
752        ft.commit();
753
754        if (animate) {
755            mFloatingActionButtonController.scaleOut();
756        } else {
757            mFloatingActionButtonController.setVisible(false);
758            maybeEnterSearchUi();
759        }
760        mActionBarController.onDialpadUp();
761
762        mListsFragment.getView().animate().alpha(0).withLayer();
763
764        //adjust the title, so the user will know where we're at when the activity start/resumes.
765        setTitle(R.string.launcherDialpadActivityLabel);
766    }
767
768    /**
769     * Callback from child DialpadFragment when the dialpad is shown.
770     */
771    public void onDialpadShown() {
772        Assert.assertNotNull(mDialpadFragment);
773        if (mDialpadFragment.getAnimate()) {
774            mDialpadFragment.getView().startAnimation(mSlideIn);
775        } else {
776            mDialpadFragment.setYFraction(0);
777        }
778
779        updateSearchFragmentPosition();
780    }
781
782    /**
783     * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in
784     * a callback after the hide animation ends.
785     * @see #commitDialpadFragmentHide
786     */
787    public void hideDialpadFragment(boolean animate, boolean clearDialpad) {
788        if (mDialpadFragment == null || mDialpadFragment.getView() == null) {
789            return;
790        }
791        if (clearDialpad) {
792            // Temporarily disable accessibility when we clear the dialpad, since it should be
793            // invisible and should not announce anything.
794            mDialpadFragment.getDigitsWidget().setImportantForAccessibility(
795                    View.IMPORTANT_FOR_ACCESSIBILITY_NO);
796            mDialpadFragment.clearDialpad();
797            mDialpadFragment.getDigitsWidget().setImportantForAccessibility(
798                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
799        }
800        if (!mIsDialpadShown) {
801            return;
802        }
803        mIsDialpadShown = false;
804        mDialpadFragment.setAnimate(animate);
805        mListsFragment.setUserVisibleHint(true);
806        mListsFragment.sendScreenViewForCurrentPosition();
807
808        updateSearchFragmentPosition();
809
810        mFloatingActionButtonController.align(getFabAlignment(), animate);
811        if (animate) {
812            mDialpadFragment.getView().startAnimation(mSlideOut);
813        } else {
814            commitDialpadFragmentHide();
815        }
816
817        mActionBarController.onDialpadDown();
818
819        if (isInSearchUi()) {
820            if (TextUtils.isEmpty(mSearchQuery)) {
821                exitSearchUi();
822            }
823        }
824        //reset the title to normal.
825        setTitle(R.string.launcherActivityLabel);
826    }
827
828    /**
829     * Finishes hiding the dialpad fragment after any animations are completed.
830     */
831    private void commitDialpadFragmentHide() {
832        if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) {
833            final FragmentTransaction ft = getFragmentManager().beginTransaction();
834            ft.hide(mDialpadFragment);
835            ft.commit();
836        }
837        mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
838    }
839
840    private void updateSearchFragmentPosition() {
841        SearchFragment fragment = null;
842        if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
843            fragment = mSmartDialSearchFragment;
844        } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
845            fragment = mRegularSearchFragment;
846        }
847        if (fragment != null && fragment.isVisible()) {
848            fragment.updatePosition(true /* animate */);
849        }
850    }
851
852    @Override
853    public boolean isInSearchUi() {
854        return mInDialpadSearch || mInRegularSearch;
855    }
856
857    @Override
858    public boolean hasSearchQuery() {
859        return !TextUtils.isEmpty(mSearchQuery);
860    }
861
862    @Override
863    public boolean shouldShowActionBar() {
864        return mListsFragment.shouldShowActionBar();
865    }
866
867    private void setNotInSearchUi() {
868        mInDialpadSearch = false;
869        mInRegularSearch = false;
870    }
871
872    private void hideDialpadAndSearchUi() {
873        if (mIsDialpadShown) {
874            hideDialpadFragment(false, true);
875        } else {
876            exitSearchUi();
877        }
878    }
879
880    private void prepareVoiceSearchButton() {
881        final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
882        if (canIntentBeHandled(voiceIntent)) {
883            mVoiceSearchButton.setVisibility(View.VISIBLE);
884            mVoiceSearchButton.setOnClickListener(this);
885        } else {
886            mVoiceSearchButton.setVisibility(View.GONE);
887        }
888    }
889
890    public boolean isNearbyPlacesSearchEnabled() {
891        return false;
892    }
893
894    protected int getSearchBoxHint () {
895        return R.string.dialer_hint_find_contact;
896    }
897
898    /**
899     * Sets the hint text for the contacts search box
900     */
901    private void setSearchBoxHint() {
902        SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) getSupportActionBar()
903                .getCustomView().findViewById(R.id.search_view_container);
904        ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search))
905                .setHint(getSearchBoxHint());
906    }
907
908    protected OptionsPopupMenu buildOptionsMenu(View invoker) {
909        final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker);
910        popupMenu.inflate(R.menu.dialtacts_options);
911        popupMenu.setOnMenuItemClickListener(this);
912        return popupMenu;
913    }
914
915    @Override
916    public boolean onCreateOptionsMenu(Menu menu) {
917        if (mPendingSearchViewQuery != null) {
918            mSearchView.setText(mPendingSearchViewQuery);
919            mPendingSearchViewQuery = null;
920        }
921        if (mActionBarController != null) {
922            mActionBarController.restoreActionBarOffset();
923        }
924        return false;
925    }
926
927    /**
928     * Returns true if the intent is due to hitting the green send key (hardware call button:
929     * KEYCODE_CALL) while in a call.
930     *
931     * @param intent the intent that launched this activity
932     * @return true if the intent is due to hitting the green send key while in a call
933     */
934    private boolean isSendKeyWhileInCall(Intent intent) {
935        // If there is a call in progress and the user launched the dialer by hitting the call
936        // button, go straight to the in-call screen.
937        final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction());
938
939        if (callKey) {
940            TelecomUtil.showInCallScreen(this, false);
941            return true;
942        }
943
944        return false;
945    }
946
947    /**
948     * Sets the current tab based on the intent's request type
949     *
950     * @param intent Intent that contains information about which tab should be selected
951     */
952    private void displayFragment(Intent intent) {
953        // If we got here by hitting send and we're in call forward along to the in-call activity
954        if (isSendKeyWhileInCall(intent)) {
955            finish();
956            return;
957        }
958
959        final boolean showDialpadChooser = phoneIsInUse() && !DialpadFragment.isAddCallMode(intent);
960        if (showDialpadChooser || (intent.getData() != null && isDialIntent(intent))) {
961            showDialpadFragment(false);
962            mDialpadFragment.setStartedFromNewIntent(true);
963            if (showDialpadChooser && !mDialpadFragment.isVisible()) {
964                mInCallDialpadUp = true;
965            }
966        }
967    }
968
969    @Override
970    public void onNewIntent(Intent newIntent) {
971        setIntent(newIntent);
972
973        mStateSaved = false;
974        displayFragment(newIntent);
975
976        invalidateOptionsMenu();
977    }
978
979    /** Returns true if the given intent contains a phone number to populate the dialer with */
980    private boolean isDialIntent(Intent intent) {
981        final String action = intent.getAction();
982        if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
983            return true;
984        }
985        if (Intent.ACTION_VIEW.equals(action)) {
986            final Uri data = intent.getData();
987            if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) {
988                return true;
989            }
990        }
991        return false;
992    }
993
994    /**
995     * Shows the search fragment
996     */
997    private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) {
998        if (mStateSaved || getFragmentManager().isDestroyed()) {
999            // Weird race condition where fragment is doing work after the activity is destroyed
1000            // due to talkback being on (b/10209937). Just return since we can't do any
1001            // constructive here.
1002            return;
1003        }
1004
1005        if (DEBUG) {
1006            Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch);
1007        }
1008
1009        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
1010        if (mInDialpadSearch && mSmartDialSearchFragment != null) {
1011            transaction.remove(mSmartDialSearchFragment);
1012        } else if (mInRegularSearch && mRegularSearchFragment != null) {
1013            transaction.remove(mRegularSearchFragment);
1014        }
1015
1016        final String tag;
1017        if (smartDialSearch) {
1018            tag = TAG_SMARTDIAL_SEARCH_FRAGMENT;
1019        } else {
1020            tag = TAG_REGULAR_SEARCH_FRAGMENT;
1021        }
1022        mInDialpadSearch = smartDialSearch;
1023        mInRegularSearch = !smartDialSearch;
1024
1025        mFloatingActionButtonController.scaleOut();
1026
1027        SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
1028        if (animate) {
1029            transaction.setCustomAnimations(android.R.animator.fade_in, 0);
1030        } else {
1031            transaction.setTransition(FragmentTransaction.TRANSIT_NONE);
1032        }
1033        if (fragment == null) {
1034            if (smartDialSearch) {
1035                fragment = new SmartDialSearchFragment();
1036            } else {
1037                fragment = ObjectFactory.newRegularSearchFragment();
1038                fragment.setOnTouchListener(new View.OnTouchListener() {
1039                    @Override
1040                    public boolean onTouch(View v, MotionEvent event) {
1041                        // Show the FAB when the user touches the lists fragment and the soft
1042                        // keyboard is hidden.
1043                        showFabInSearchUi();
1044                        return false;
1045                    }
1046                });
1047            }
1048            transaction.add(R.id.dialtacts_frame, fragment, tag);
1049        } else {
1050            transaction.show(fragment);
1051        }
1052        // DialtactsActivity will provide the options menu
1053        fragment.setHasOptionsMenu(false);
1054        fragment.setShowEmptyListForNullQuery(true);
1055        if (!smartDialSearch) {
1056            fragment.setQueryString(query, false /* delaySelection */);
1057        }
1058        transaction.commit();
1059
1060        if (animate) {
1061            mListsFragment.getView().animate().alpha(0).withLayer();
1062        }
1063        mListsFragment.setUserVisibleHint(false);
1064    }
1065
1066    /**
1067     * Hides the search fragment
1068     */
1069    private void exitSearchUi() {
1070        // See related bug in enterSearchUI();
1071        if (getFragmentManager().isDestroyed() || mStateSaved) {
1072            return;
1073        }
1074
1075        mSearchView.setText(null);
1076
1077        if (mDialpadFragment != null) {
1078            mDialpadFragment.clearDialpad();
1079        }
1080
1081        setNotInSearchUi();
1082
1083        // Restore the FAB for the lists fragment.
1084        if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) {
1085            mFloatingActionButtonController.setVisible(false);
1086        }
1087        mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
1088        onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */);
1089        onPageSelected(mListsFragment.getCurrentTabIndex());
1090
1091        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
1092        if (mSmartDialSearchFragment != null) {
1093            transaction.remove(mSmartDialSearchFragment);
1094        }
1095        if (mRegularSearchFragment != null) {
1096            transaction.remove(mRegularSearchFragment);
1097        }
1098        transaction.commit();
1099
1100        mListsFragment.getView().animate().alpha(1).withLayer();
1101
1102        if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
1103            // If the dialpad fragment wasn't previously visible, then send a screen view because
1104            // we are exiting regular search. Otherwise, the screen view will be sent by
1105            // {@link #hideDialpadFragment}.
1106            mListsFragment.sendScreenViewForCurrentPosition();
1107            mListsFragment.setUserVisibleHint(true);
1108        }
1109
1110        mActionBarController.onSearchUiExited();
1111    }
1112
1113    @Override
1114    public void onBackPressed() {
1115        if (mStateSaved) {
1116            return;
1117        }
1118        if (mIsDialpadShown) {
1119            if (TextUtils.isEmpty(mSearchQuery) ||
1120                    (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()
1121                            && mSmartDialSearchFragment.getAdapter().getCount() == 0)) {
1122                exitSearchUi();
1123            }
1124            hideDialpadFragment(true, false);
1125        } else if (isInSearchUi()) {
1126            exitSearchUi();
1127            DialerUtils.hideInputMethod(mParentLayout);
1128        } else {
1129            super.onBackPressed();
1130        }
1131    }
1132
1133    private void maybeEnterSearchUi() {
1134        if (!isInSearchUi()) {
1135            enterSearchUi(true /* isSmartDial */, mSearchQuery, false);
1136        }
1137    }
1138
1139    /**
1140     * @return True if the search UI was exited, false otherwise
1141     */
1142    private boolean maybeExitSearchUi() {
1143        if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) {
1144            exitSearchUi();
1145            DialerUtils.hideInputMethod(mParentLayout);
1146            return true;
1147        }
1148        return false;
1149    }
1150
1151    private void showFabInSearchUi() {
1152        mFloatingActionButtonController.changeIcon(
1153                getResources().getDrawable(R.drawable.fab_ic_dial),
1154                getResources().getString(R.string.action_menu_dialpad_button));
1155        mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
1156        mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
1157    }
1158
1159    @Override
1160    public void onDialpadQueryChanged(String query) {
1161        mDialpadQuery = query;
1162        if (mSmartDialSearchFragment != null) {
1163            mSmartDialSearchFragment.setAddToContactNumber(query);
1164        }
1165        final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
1166                SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
1167
1168        if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
1169            if (DEBUG) {
1170                Log.d(TAG, "onDialpadQueryChanged - new query: " + query);
1171            }
1172            if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
1173                // This callback can happen if the dialpad fragment is recreated because of
1174                // activity destruction. In that case, don't update the search view because
1175                // that would bring the user back to the search fragment regardless of the
1176                // previous state of the application. Instead, just return here and let the
1177                // fragment manager correctly figure out whatever fragment was last displayed.
1178                if (!TextUtils.isEmpty(normalizedQuery)) {
1179                    mPendingSearchViewQuery = normalizedQuery;
1180                }
1181                return;
1182            }
1183            mSearchView.setText(normalizedQuery);
1184        }
1185
1186        try {
1187            if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
1188                mDialpadFragment.process_quote_emergency_unquote(normalizedQuery);
1189            }
1190        } catch (Exception ignored) {
1191            // Skip any exceptions for this piece of code
1192        }
1193    }
1194
1195    @Override
1196    public boolean onDialpadSpacerTouchWithEmptyQuery() {
1197        if (mInDialpadSearch && mSmartDialSearchFragment != null
1198                && !mSmartDialSearchFragment.isShowingPermissionRequest()) {
1199            hideDialpadFragment(true /* animate */, true /* clearDialpad */);
1200            return true;
1201        }
1202        return false;
1203    }
1204
1205    @Override
1206    public void onListFragmentScrollStateChange(int scrollState) {
1207        if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1208            hideDialpadFragment(true, false);
1209            DialerUtils.hideInputMethod(mParentLayout);
1210        }
1211    }
1212
1213    @Override
1214    public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount,
1215                                     int totalItemCount) {
1216        // TODO: No-op for now. This should eventually show/hide the actionBar based on
1217        // interactions with the ListsFragments.
1218    }
1219
1220    private boolean phoneIsInUse() {
1221        return TelecomUtil.isInCall(this);
1222    }
1223
1224    private boolean canIntentBeHandled(Intent intent) {
1225        final PackageManager packageManager = getPackageManager();
1226        final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
1227                PackageManager.MATCH_DEFAULT_ONLY);
1228        return resolveInfo != null && resolveInfo.size() > 0;
1229    }
1230
1231    /**
1232     * Called when the user has long-pressed a contact tile to start a drag operation.
1233     */
1234    @Override
1235    public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
1236        mListsFragment.showRemoveView(true);
1237    }
1238
1239    @Override
1240    public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {
1241    }
1242
1243    /**
1244     * Called when the user has released a contact tile after long-pressing it.
1245     */
1246    @Override
1247    public void onDragFinished(int x, int y) {
1248        mListsFragment.showRemoveView(false);
1249    }
1250
1251    @Override
1252    public void onDroppedOnRemove() {}
1253
1254    /**
1255     * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer
1256     * once it has been attached to the activity.
1257     */
1258    @Override
1259    public void setDragDropController(DragDropController dragController) {
1260        mDragDropController = dragController;
1261        mListsFragment.getRemoveView().setDragDropController(dragController);
1262    }
1263
1264    /**
1265     * Implemented to satisfy {@link SpeedDialFragment.HostInterface}
1266     */
1267    @Override
1268    public void showAllContactsTab() {
1269        if (mListsFragment != null) {
1270            mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS);
1271        }
1272    }
1273
1274    /**
1275     * Implemented to satisfy {@link CallLogFragment.HostInterface}
1276     */
1277    @Override
1278    public void showDialpad() {
1279        showDialpadFragment(true);
1280    }
1281
1282    @Override
1283    public void onPickDataUri(Uri dataUri, int callInitiationType) {
1284        mClearSearchOnPause = true;
1285        PhoneNumberInteraction.startInteractionForPhoneCall(
1286                DialtactsActivity.this, dataUri, callInitiationType);
1287    }
1288
1289    @Override
1290    public void onPickPhoneNumber(String phoneNumber, boolean isVideoCall, int callInitiationType) {
1291        if (phoneNumber == null) {
1292            // Invalid phone number, but let the call go through so that InCallUI can show
1293            // an error message.
1294            phoneNumber = "";
1295        }
1296
1297        final Intent intent = new CallIntentBuilder(phoneNumber)
1298                .setIsVideoCall(isVideoCall)
1299                .setCallInitiationType(callInitiationType)
1300                .build();
1301
1302        DialerUtils.startActivityWithErrorToast(this, intent);
1303        mClearSearchOnPause = true;
1304    }
1305
1306    @Override
1307    public void onShortcutIntentCreated(Intent intent) {
1308        Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
1309    }
1310
1311    @Override
1312    public void onHomeInActionBarSelected() {
1313        exitSearchUi();
1314    }
1315
1316    @Override
1317    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
1318        int tabIndex = mListsFragment.getCurrentTabIndex();
1319
1320        // Scroll the button from center to end when moving from the Speed Dial to Call History tab.
1321        // In RTL, scroll when the current tab is Call History instead, since the order of the tabs
1322        // is reversed and the ViewPager returns the left tab position during scroll.
1323        boolean isRtl = DialerUtils.isRtl();
1324        if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) {
1325            mFloatingActionButtonController.onPageScrolled(positionOffset);
1326        } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_HISTORY && !mIsLandscape) {
1327            mFloatingActionButtonController.onPageScrolled(1 - positionOffset);
1328        } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) {
1329            mFloatingActionButtonController.onPageScrolled(1);
1330        }
1331    }
1332
1333    @Override
1334    public void onPageSelected(int position) {
1335        int tabIndex = mListsFragment.getCurrentTabIndex();
1336        if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
1337            mFloatingActionButtonController.changeIcon(
1338                    getResources().getDrawable(R.drawable.ic_person_add_24dp),
1339                    getResources().getString(R.string.search_shortcut_create_new_contact));
1340        } else {
1341            mFloatingActionButtonController.changeIcon(
1342                    getResources().getDrawable(R.drawable.fab_ic_dial),
1343                    getResources().getString(R.string.action_menu_dialpad_button));
1344        }
1345    }
1346
1347    @Override
1348    public void onPageScrollStateChanged(int state) {
1349    }
1350
1351    @Override
1352    public boolean isActionBarShowing() {
1353        return mActionBarController.isActionBarShowing();
1354    }
1355
1356    @Override
1357    public ActionBarController getActionBarController() {
1358        return mActionBarController;
1359    }
1360
1361    @Override
1362    public boolean isDialpadShown() {
1363        return mIsDialpadShown;
1364    }
1365
1366    @Override
1367    public int getDialpadHeight() {
1368        if (mDialpadFragment != null) {
1369            return mDialpadFragment.getDialpadHeight();
1370        }
1371        return 0;
1372    }
1373
1374    @Override
1375    public int getActionBarHideOffset() {
1376        return getSupportActionBar().getHideOffset();
1377    }
1378
1379    @Override
1380    public void setActionBarHideOffset(int offset) {
1381        getSupportActionBar().setHideOffset(offset);
1382    }
1383
1384    @Override
1385    public int getActionBarHeight() {
1386        return mActionBarHeight;
1387    }
1388
1389    private int getFabAlignment() {
1390        if (!mIsLandscape && !isInSearchUi() &&
1391                mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) {
1392            return FloatingActionButtonController.ALIGN_MIDDLE;
1393        }
1394        return FloatingActionButtonController.ALIGN_END;
1395    }
1396}
1397