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