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