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