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