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