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