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