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.Fragment;
20 import android.app.FragmentTransaction;
21 import android.content.ActivityNotFoundException;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.content.res.Configuration;
27 import android.content.res.Resources;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Trace;
31 import android.provider.CallLog.Calls;
32 import android.speech.RecognizerIntent;
33 import android.support.design.widget.CoordinatorLayout;
34 import android.support.v4.view.ViewPager;
35 import android.support.v7.app.ActionBar;
36 import android.telecom.PhoneAccount;
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.ViewTreeObserver;
50 import android.view.animation.Animation;
51 import android.view.animation.AnimationUtils;
52 import android.widget.AbsListView.OnScrollListener;
53 import android.widget.EditText;
54 import android.widget.ImageButton;
55 import android.widget.PopupMenu;
56 import android.widget.TextView;
57 import android.widget.Toast;
58 
59 import com.android.contacts.common.dialog.ClearFrequentsDialog;
60 import com.android.contacts.common.interactions.ImportExportDialogFragment;
61 import com.android.contacts.common.interactions.TouchPointManager;
62 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
63 import com.android.contacts.common.util.PermissionsUtil;
64 import com.android.contacts.common.widget.FloatingActionButtonController;
65 import com.android.dialer.calllog.CallLogActivity;
66 import com.android.dialer.calllog.CallLogFragment;
67 import com.android.dialer.database.DialerDatabaseHelper;
68 import com.android.dialer.dialpad.DialpadFragment;
69 import com.android.dialer.dialpad.SmartDialNameMatcher;
70 import com.android.dialer.dialpad.SmartDialPrefix;
71 import com.android.dialer.interactions.PhoneNumberInteraction;
72 import com.android.dialer.list.DragDropController;
73 import com.android.dialer.list.ListsFragment;
74 import com.android.dialer.list.OnDragDropListener;
75 import com.android.dialer.list.OnListFragmentScrolledListener;
76 import com.android.dialer.list.PhoneFavoriteSquareTileView;
77 import com.android.dialer.list.RegularSearchFragment;
78 import com.android.dialer.list.SearchFragment;
79 import com.android.dialer.list.SmartDialSearchFragment;
80 import com.android.dialer.list.SpeedDialFragment;
81 import com.android.dialer.logging.Logger;
82 import com.android.dialer.logging.ScreenEvent;
83 import com.android.dialer.settings.DialerSettingsActivity;
84 import com.android.dialer.util.Assert;
85 import com.android.dialer.util.DialerUtils;
86 import com.android.dialer.util.IntentUtil;
87 import com.android.dialer.util.IntentUtil.CallIntentBuilder;
88 import com.android.dialer.util.TelecomUtil;
89 import com.android.dialer.voicemail.VoicemailArchiveActivity;
90 import com.android.dialer.widget.ActionBarController;
91 import com.android.dialer.widget.SearchEditTextLayout;
92 import com.android.dialerbind.DatabaseHelperManager;
93 import com.android.dialerbind.ObjectFactory;
94 import com.android.phone.common.animation.AnimUtils;
95 import com.android.phone.common.animation.AnimationListenerAdapter;
96 import com.google.common.annotations.VisibleForTesting;
97 
98 import java.util.ArrayList;
99 import java.util.List;
100 
101 /**
102  * The dialer tab's title is 'phone', a more common name (see strings.xml).
103  */
104 public 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 {
OptionsPopupMenu(Context context, View anchor)250         public OptionsPopupMenu(Context context, View anchor) {
251             super(context, anchor, Gravity.END);
252         }
253 
254         @Override
show()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
onDrag(View v, DragEvent event)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
dispatchTouchEvent(MotionEvent ev)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
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 = 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
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                 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
onRestart()562     protected void onRestart() {
563         super.onRestart();
564         mIsRestarting = true;
565     }
566 
567     @Override
onPause()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
onSaveInstanceState(Bundle outState)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
onAttachFragment(Fragment fragment)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 
handleMenuSettings()620     protected void handleMenuSettings() {
621         final Intent intent = new Intent(this, DialerSettingsActivity.class);
622         startActivity(intent);
623     }
624 
625     @Override
onClick(View view)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
onMenuItemClick(MenuItem item)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
onActivityResult(int requestCode, int resultCode, Intent data)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      */
updateTabUnreadCounts()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      */
showDialpadFragment(boolean animate)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      */
onDialpadShown()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      */
hideDialpadFragment(boolean animate, boolean clearDialpad)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      */
commitDialpadFragmentHide()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 
updateSearchFragmentPosition()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
isInSearchUi()850     public boolean isInSearchUi() {
851         return mInDialpadSearch || mInRegularSearch;
852     }
853 
854     @Override
hasSearchQuery()855     public boolean hasSearchQuery() {
856         return !TextUtils.isEmpty(mSearchQuery);
857     }
858 
859     @Override
shouldShowActionBar()860     public boolean shouldShowActionBar() {
861         return mListsFragment.shouldShowActionBar();
862     }
863 
setNotInSearchUi()864     private void setNotInSearchUi() {
865         mInDialpadSearch = false;
866         mInRegularSearch = false;
867     }
868 
hideDialpadAndSearchUi()869     private void hideDialpadAndSearchUi() {
870         if (mIsDialpadShown) {
871             hideDialpadFragment(false, true);
872         } else {
873             exitSearchUi();
874         }
875     }
876 
prepareVoiceSearchButton()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 
isNearbyPlacesSearchEnabled()887     public boolean isNearbyPlacesSearchEnabled() {
888         return false;
889     }
890 
getSearchBoxHint()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      */
setSearchBoxHint()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 
buildOptionsMenu(View invoker)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
onCreateOptionsMenu(Menu menu)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      */
isSendKeyWhileInCall(Intent intent)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      */
displayFragment(Intent intent)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
onNewIntent(Intent newIntent)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 */
isDialIntent(Intent intent)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      */
enterSearchUi(boolean smartDialSearch, String query, boolean animate)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      */
exitSearchUi()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
onBackPressed()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 
maybeEnterSearchUi()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      */
maybeExitSearchUi()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 
showFabInSearchUi()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
onDialpadQueryChanged(String query)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
onDialpadSpacerTouchWithEmptyQuery()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
onListFragmentScrollStateChange(int scrollState)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
onListFragmentScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)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 
phoneIsInUse()1227     private boolean phoneIsInUse() {
1228         return TelecomUtil.isInCall(this);
1229     }
1230 
canIntentBeHandled(Intent intent)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
onDragStarted(int x, int y, PhoneFavoriteSquareTileView view)1242     public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
1243         mListsFragment.showRemoveView(true);
1244     }
1245 
1246     @Override
onDragHovered(int x, int y, PhoneFavoriteSquareTileView view)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
onDragFinished(int x, int y)1254     public void onDragFinished(int x, int y) {
1255         mListsFragment.showRemoveView(false);
1256     }
1257 
1258     @Override
onDroppedOnRemove()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
setDragDropController(DragDropController dragController)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
showAllContactsTab()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
showDialpad()1285     public void showDialpad() {
1286         showDialpadFragment(true);
1287     }
1288 
1289     @Override
onPickDataUri(Uri dataUri, boolean isVideoCall, int callInitiationType)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
onPickPhoneNumber(String phoneNumber, boolean isVideoCall, int callInitiationType)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
onShortcutIntentCreated(Intent intent)1314     public void onShortcutIntentCreated(Intent intent) {
1315         Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
1316     }
1317 
1318     @Override
onHomeInActionBarSelected()1319     public void onHomeInActionBarSelected() {
1320         exitSearchUi();
1321     }
1322 
1323     @Override
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)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
onPageSelected(int position)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
onPageScrollStateChanged(int state)1357     public void onPageScrollStateChanged(int state) {
1358     }
1359 
1360     @Override
isActionBarShowing()1361     public boolean isActionBarShowing() {
1362         return mActionBarController.isActionBarShowing();
1363     }
1364 
1365     @Override
getActionBarController()1366     public ActionBarController getActionBarController() {
1367         return mActionBarController;
1368     }
1369 
1370     @Override
isDialpadShown()1371     public boolean isDialpadShown() {
1372         return mIsDialpadShown;
1373     }
1374 
1375     @Override
getDialpadHeight()1376     public int getDialpadHeight() {
1377         if (mDialpadFragment != null) {
1378             return mDialpadFragment.getDialpadHeight();
1379         }
1380         return 0;
1381     }
1382 
1383     @Override
getActionBarHideOffset()1384     public int getActionBarHideOffset() {
1385         return getSupportActionBar().getHideOffset();
1386     }
1387 
1388     @Override
setActionBarHideOffset(int offset)1389     public void setActionBarHideOffset(int offset) {
1390         getSupportActionBar().setHideOffset(offset);
1391     }
1392 
1393     @Override
getActionBarHeight()1394     public int getActionBarHeight() {
1395         return mActionBarHeight;
1396     }
1397 
getFabAlignment()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 
updateMissedCalls()1406     private void updateMissedCalls() {
1407         if (mPreviouslySelectedTabIndex == ListsFragment.TAB_INDEX_HISTORY) {
1408             mListsFragment.markMissedCallsAsReadAndRemoveNotifications();
1409         }
1410     }
1411 }
1412