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