summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/app/DialtactsActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/app/DialtactsActivity.java')
-rw-r--r--java/com/android/dialer/app/DialtactsActivity.java1484
1 files changed, 1484 insertions, 0 deletions
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
new file mode 100644
index 000000000..4c57cda70
--- /dev/null
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -0,0 +1,1484 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.app;
+
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Trace;
+import android.provider.CallLog.Calls;
+import android.speech.RecognizerIntent;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBar;
+import android.telecom.PhoneAccount;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.DragEvent;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnDragListener;
+import android.view.ViewTreeObserver;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.contacts.common.dialog.ClearFrequentsDialog;
+import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
+import com.android.contacts.common.list.PhoneNumberListAdapter;
+import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery;
+import com.android.contacts.common.list.PhoneNumberPickerFragment.CursorReranker;
+import com.android.contacts.common.list.PhoneNumberPickerFragment.OnLoadFinishedListener;
+import com.android.contacts.common.widget.FloatingActionButtonController;
+import com.android.dialer.animation.AnimUtils;
+import com.android.dialer.animation.AnimationListenerAdapter;
+import com.android.dialer.app.calllog.CallLogFragment;
+import com.android.dialer.app.calllog.CallLogNotificationsService;
+import com.android.dialer.app.calllog.ClearCallLogDialog;
+import com.android.dialer.app.dialpad.DialpadFragment;
+import com.android.dialer.app.list.DragDropController;
+import com.android.dialer.app.list.ListsFragment;
+import com.android.dialer.app.list.OnDragDropListener;
+import com.android.dialer.app.list.OnListFragmentScrolledListener;
+import com.android.dialer.app.list.PhoneFavoriteSquareTileView;
+import com.android.dialer.app.list.RegularSearchFragment;
+import com.android.dialer.app.list.SearchFragment;
+import com.android.dialer.app.list.SmartDialSearchFragment;
+import com.android.dialer.app.list.SpeedDialFragment;
+import com.android.dialer.app.settings.DialerSettingsActivity;
+import com.android.dialer.app.widget.ActionBarController;
+import com.android.dialer.app.widget.SearchEditTextLayout;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.callintent.nano.CallSpecificAppData;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.database.Database;
+import com.android.dialer.database.DialerDatabaseHelper;
+import com.android.dialer.interactions.PhoneNumberInteraction;
+import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCode;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.logging.nano.DialerImpression;
+import com.android.dialer.logging.nano.ScreenEvent;
+import com.android.dialer.p13n.inference.P13nRanking;
+import com.android.dialer.p13n.inference.protocol.P13nRanker;
+import com.android.dialer.p13n.inference.protocol.P13nRanker.P13nRefreshCompleteListener;
+import com.android.dialer.p13n.logging.P13nLogger;
+import com.android.dialer.p13n.logging.P13nLogging;
+import com.android.dialer.proguard.UsedByReflection;
+import com.android.dialer.smartdial.SmartDialNameMatcher;
+import com.android.dialer.smartdial.SmartDialPrefix;
+import com.android.dialer.telecom.TelecomUtil;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.IntentUtil;
+import com.android.dialer.util.PermissionsUtil;
+import com.android.dialer.util.TouchPointManager;
+import com.android.dialer.util.TransactionSafeActivity;
+import com.android.dialer.util.ViewUtil;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/** The dialer tab's title is 'phone', a more common name (see strings.xml). */
+@UsedByReflection(value = "AndroidManifest-app.xml")
+public class DialtactsActivity extends TransactionSafeActivity
+ implements View.OnClickListener,
+ DialpadFragment.OnDialpadQueryChangedListener,
+ OnListFragmentScrolledListener,
+ CallLogFragment.HostInterface,
+ DialpadFragment.HostInterface,
+ ListsFragment.HostInterface,
+ SpeedDialFragment.HostInterface,
+ SearchFragment.HostInterface,
+ OnDragDropListener,
+ OnPhoneNumberPickerActionListener,
+ PopupMenu.OnMenuItemClickListener,
+ ViewPager.OnPageChangeListener,
+ ActionBarController.ActivityUi,
+ PhoneNumberInteraction.InteractionErrorListener,
+ PhoneNumberInteraction.DisambigDialogDismissedListener,
+ ActivityCompat.OnRequestPermissionsResultCallback {
+
+ public static final boolean DEBUG = false;
+ @VisibleForTesting public static final String TAG_DIALPAD_FRAGMENT = "dialpad";
+ private static final String ACTION_SHOW_TAB = "ACTION_SHOW_TAB";
+ @VisibleForTesting public static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB";
+ public static final String EXTRA_CLEAR_NEW_VOICEMAILS = "EXTRA_CLEAR_NEW_VOICEMAILS";
+ private static final String TAG = "DialtactsActivity";
+ private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui";
+ private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui";
+ private static final String KEY_SEARCH_QUERY = "search_query";
+ private static final String KEY_FIRST_LAUNCH = "first_launch";
+ private static final String KEY_WAS_CONFIGURATION_CHANGE = "was_configuration_change";
+ private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown";
+ private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search";
+ private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial";
+ private static final String TAG_FAVORITES_FRAGMENT = "favorites";
+ /** Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. */
+ private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
+
+ private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1;
+ public static final int ACTIVITY_REQUEST_CODE_CALL_COMPOSE = 2;
+
+ private static final int FAB_SCALE_IN_DELAY_MS = 300;
+ /** Fragment containing the dialpad that slides into view */
+ protected DialpadFragment mDialpadFragment;
+
+ private CoordinatorLayout mParentLayout;
+ /** Fragment for searching phone numbers using the alphanumeric keyboard. */
+ private RegularSearchFragment mRegularSearchFragment;
+
+ /** Fragment for searching phone numbers using the dialpad. */
+ private SmartDialSearchFragment mSmartDialSearchFragment;
+
+ /** Animation that slides in. */
+ private Animation mSlideIn;
+
+ /** Animation that slides out. */
+ private Animation mSlideOut;
+ /** Fragment containing the speed dial list, call history list, and all contacts list. */
+ private ListsFragment mListsFragment;
+ /**
+ * Tracks whether onSaveInstanceState has been called. If true, no fragment transactions can be
+ * commited.
+ */
+ private boolean mStateSaved;
+
+ private boolean mIsRestarting;
+ private boolean mInDialpadSearch;
+ private boolean mInRegularSearch;
+ private boolean mClearSearchOnPause;
+ private boolean mIsDialpadShown;
+ private boolean mShowDialpadOnResume;
+ /** Whether or not the device is in landscape orientation. */
+ private boolean mIsLandscape;
+ /** True if the dialpad is only temporarily showing due to being in call */
+ private boolean mInCallDialpadUp;
+ /** True when this activity has been launched for the first time. */
+ private boolean mFirstLaunch;
+ /**
+ * Search query to be applied to the SearchView in the ActionBar once onCreateOptionsMenu has been
+ * called.
+ */
+ private String mPendingSearchViewQuery;
+
+ private PopupMenu mOverflowMenu;
+ private EditText mSearchView;
+ private View mVoiceSearchButton;
+ private String mSearchQuery;
+ private String mDialpadQuery;
+ private DialerDatabaseHelper mDialerDatabaseHelper;
+ private DragDropController mDragDropController;
+ private ActionBarController mActionBarController;
+ private FloatingActionButtonController mFloatingActionButtonController;
+ private boolean mWasConfigurationChange;
+
+ private P13nLogger mP13nLogger;
+ private P13nRanker mP13nRanker;
+
+ AnimationListenerAdapter mSlideInListener =
+ new AnimationListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ maybeEnterSearchUi();
+ }
+ };
+ /** Listener for after slide out animation completes on dialer fragment. */
+ AnimationListenerAdapter mSlideOutListener =
+ new AnimationListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ commitDialpadFragmentHide();
+ }
+ };
+ /** Listener used to send search queries to the phone search fragment. */
+ private final TextWatcher mPhoneSearchQueryTextListener =
+ new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ final String newText = s.toString();
+ if (newText.equals(mSearchQuery)) {
+ // If the query hasn't changed (perhaps due to activity being destroyed
+ // and restored, or user launching the same DIAL intent twice), then there is
+ // no need to do anything here.
+ return;
+ }
+ if (DEBUG) {
+ LogUtil.v("DialtactsActivity.onTextChanged", "called with new query: " + newText);
+ LogUtil.v("DialtactsActivity.onTextChanged", "previous query: " + mSearchQuery);
+ }
+ mSearchQuery = newText;
+
+ // Show search fragment only when the query string is changed to non-empty text.
+ if (!TextUtils.isEmpty(newText)) {
+ // Call enterSearchUi only if we are switching search modes, or showing a search
+ // fragment for the first time.
+ final boolean sameSearchMode =
+ (mIsDialpadShown && mInDialpadSearch) || (!mIsDialpadShown && mInRegularSearch);
+ if (!sameSearchMode) {
+ enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);
+ }
+ }
+
+ if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
+ mSmartDialSearchFragment.setQueryString(mSearchQuery);
+ } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
+ mRegularSearchFragment.setQueryString(mSearchQuery);
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {}
+ };
+ /** Open the search UI when the user clicks on the search box. */
+ private final View.OnClickListener mSearchViewOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!isInSearchUi()) {
+ mActionBarController.onSearchBoxTapped();
+ enterSearchUi(
+ false /* smartDialSearch */, mSearchView.getText().toString(), true /* animate */);
+ }
+ }
+ };
+
+ private int mActionBarHeight;
+ private int mPreviouslySelectedTabIndex;
+ /** Handles the user closing the soft keyboard. */
+ private final View.OnKeyListener mSearchEditTextLayoutListener =
+ new View.OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (TextUtils.isEmpty(mSearchView.getText().toString())) {
+ // If the search term is empty, close the search UI.
+ maybeExitSearchUi();
+ } else {
+ // If the search term is not empty, show the dialpad fab.
+ showFabInSearchUi();
+ }
+ }
+ return false;
+ }
+ };
+ /**
+ * The text returned from a voice search query. Set in {@link #onActivityResult} and used in
+ * {@link #onResume()} to populate the search box.
+ */
+ private String mVoiceSearchQuery;
+
+ /**
+ * @param tab the TAB_INDEX_* constant in {@link ListsFragment}
+ * @return A intent that will open the DialtactsActivity into the specified tab. The intent for
+ * each tab will be unique.
+ */
+ public static Intent getShowTabIntent(Context context, int tab) {
+ Intent intent = new Intent(context, DialtactsActivity.class);
+ intent.setAction(ACTION_SHOW_TAB);
+ intent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, tab);
+ intent.setData(
+ new Uri.Builder()
+ .scheme("intent")
+ .authority(context.getPackageName())
+ .appendPath(TAG)
+ .appendQueryParameter(DialtactsActivity.EXTRA_SHOW_TAB, String.valueOf(tab))
+ .build());
+
+ return intent;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Trace.beginSection(TAG + " onCreate");
+ super.onCreate(savedInstanceState);
+
+ mFirstLaunch = true;
+
+ final Resources resources = getResources();
+ mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height_large);
+
+ Trace.beginSection(TAG + " setContentView");
+ setContentView(R.layout.dialtacts_activity);
+ Trace.endSection();
+ getWindow().setBackgroundDrawable(null);
+
+ Trace.beginSection(TAG + " setup Views");
+ final ActionBar actionBar = getActionBarSafely();
+ actionBar.setCustomView(R.layout.search_edittext);
+ actionBar.setDisplayShowCustomEnabled(true);
+ actionBar.setBackgroundDrawable(null);
+
+ SearchEditTextLayout searchEditTextLayout =
+ (SearchEditTextLayout) actionBar.getCustomView().findViewById(R.id.search_view_container);
+ searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener);
+
+ mActionBarController = new ActionBarController(this, searchEditTextLayout);
+
+ mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view);
+ mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
+ mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button);
+ searchEditTextLayout
+ .findViewById(R.id.search_magnifying_glass)
+ .setOnClickListener(mSearchViewOnClickListener);
+ searchEditTextLayout
+ .findViewById(R.id.search_box_start_search)
+ .setOnClickListener(mSearchViewOnClickListener);
+ searchEditTextLayout.setOnClickListener(mSearchViewOnClickListener);
+ searchEditTextLayout.setCallback(
+ new SearchEditTextLayout.Callback() {
+ @Override
+ public void onBackButtonClicked() {
+ onBackPressed();
+ }
+
+ @Override
+ public void onSearchViewClicked() {
+ // Hide FAB, as the keyboard is shown.
+ mFloatingActionButtonController.scaleOut();
+ }
+ });
+
+ mIsLandscape =
+ getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
+ mPreviouslySelectedTabIndex = ListsFragment.TAB_INDEX_SPEED_DIAL;
+ final View floatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
+ ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
+ floatingActionButton.setOnClickListener(this);
+ mFloatingActionButtonController =
+ new FloatingActionButtonController(
+ this, floatingActionButtonContainer, floatingActionButton);
+
+ ImageButton optionsMenuButton =
+ (ImageButton) searchEditTextLayout.findViewById(R.id.dialtacts_options_menu_button);
+ optionsMenuButton.setOnClickListener(this);
+ mOverflowMenu = buildOptionsMenu(optionsMenuButton);
+ optionsMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener());
+
+ // Add the favorites fragment but only if savedInstanceState is null. Otherwise the
+ // fragment manager is responsible for recreating it.
+ if (savedInstanceState == null) {
+ getFragmentManager()
+ .beginTransaction()
+ .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
+ .commit();
+ } else {
+ mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
+ mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI);
+ mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI);
+ mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH);
+ mWasConfigurationChange = savedInstanceState.getBoolean(KEY_WAS_CONFIGURATION_CHANGE);
+ mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN);
+ mActionBarController.restoreInstanceState(savedInstanceState);
+ }
+
+ final boolean isLayoutRtl = ViewUtil.isRtl();
+ if (mIsLandscape) {
+ mSlideIn =
+ AnimationUtils.loadAnimation(
+ this, isLayoutRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
+ mSlideOut =
+ AnimationUtils.loadAnimation(
+ this, isLayoutRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
+ } else {
+ mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
+ mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
+ }
+
+ mSlideIn.setInterpolator(AnimUtils.EASE_IN);
+ mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
+
+ mSlideIn.setAnimationListener(mSlideInListener);
+ mSlideOut.setAnimationListener(mSlideOutListener);
+
+ mParentLayout = (CoordinatorLayout) findViewById(R.id.dialtacts_mainlayout);
+ mParentLayout.setOnDragListener(new LayoutOnDragListener());
+ floatingActionButtonContainer
+ .getViewTreeObserver()
+ .addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ final ViewTreeObserver observer =
+ floatingActionButtonContainer.getViewTreeObserver();
+ if (!observer.isAlive()) {
+ return;
+ }
+ observer.removeOnGlobalLayoutListener(this);
+ int screenWidth = mParentLayout.getWidth();
+ mFloatingActionButtonController.setScreenWidth(screenWidth);
+ mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
+ }
+ });
+
+ Trace.endSection();
+
+ Trace.beginSection(TAG + " initialize smart dialing");
+ mDialerDatabaseHelper = Database.get(this).getDatabaseHelper(this);
+ SmartDialPrefix.initializeNanpSettings(this);
+ Trace.endSection();
+
+ mP13nLogger = P13nLogging.get(getApplicationContext());
+ mP13nRanker = P13nRanking.get(getApplicationContext());
+ Trace.endSection();
+ }
+
+ @NonNull
+ private ActionBar getActionBarSafely() {
+ return Assert.isNotNull(getSupportActionBar());
+ }
+
+ @Override
+ protected void onResume() {
+ Trace.beginSection(TAG + " onResume");
+ super.onResume();
+
+ mStateSaved = false;
+ if (mFirstLaunch) {
+ displayFragment(getIntent());
+ } else if (!phoneIsInUse() && mInCallDialpadUp) {
+ hideDialpadFragment(false, true);
+ mInCallDialpadUp = false;
+ } else if (mShowDialpadOnResume) {
+ showDialpadFragment(false);
+ mShowDialpadOnResume = false;
+ }
+
+ // If there was a voice query result returned in the {@link #onActivityResult} callback, it
+ // will have been stashed in mVoiceSearchQuery since the search results fragment cannot be
+ // shown until onResume has completed. Active the search UI and set the search term now.
+ if (!TextUtils.isEmpty(mVoiceSearchQuery)) {
+ mActionBarController.onSearchBoxTapped();
+ mSearchView.setText(mVoiceSearchQuery);
+ mVoiceSearchQuery = null;
+ }
+
+ mFirstLaunch = false;
+
+ if (mIsRestarting) {
+ // This is only called when the activity goes from resumed -> paused -> resumed, so it
+ // will not cause an extra view to be sent out on rotation
+ if (mIsDialpadShown) {
+ Logger.get(this).logScreenView(ScreenEvent.Type.DIALPAD, this);
+ }
+ mIsRestarting = false;
+ }
+
+ prepareVoiceSearchButton();
+ if (!mWasConfigurationChange) {
+ mDialerDatabaseHelper.startSmartDialUpdateThread();
+ }
+ mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
+
+ if (Calls.CONTENT_TYPE.equals(getIntent().getType())) {
+ // Externally specified extras take precedence to EXTRA_SHOW_TAB, which is only
+ // used internally.
+ final Bundle extras = getIntent().getExtras();
+ if (extras != null && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) {
+ mListsFragment.showTab(ListsFragment.TAB_INDEX_VOICEMAIL);
+ } else {
+ mListsFragment.showTab(ListsFragment.TAB_INDEX_HISTORY);
+ }
+ } else if (getIntent().hasExtra(EXTRA_SHOW_TAB)) {
+ int index = getIntent().getIntExtra(EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_SPEED_DIAL);
+ if (index < mListsFragment.getTabCount()) {
+ // Hide dialpad since this is an explicit intent to show a specific tab, which is coming
+ // from missed call or voicemail notification.
+ hideDialpadFragment(false, false);
+ exitSearchUi();
+ mListsFragment.showTab(index);
+ }
+ }
+
+ if (getIntent().getBooleanExtra(EXTRA_CLEAR_NEW_VOICEMAILS, false)) {
+ CallLogNotificationsService.markNewVoicemailsAsOld(this);
+ }
+
+ setSearchBoxHint();
+
+ mP13nLogger.reset();
+ mP13nRanker.refresh(
+ new P13nRefreshCompleteListener() {
+ @Override
+ public void onP13nRefreshComplete() {
+ // TODO: make zero-query search results visible
+ }
+ });
+ Trace.endSection();
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ mIsRestarting = true;
+ }
+
+ @Override
+ protected void onPause() {
+ if (mClearSearchOnPause) {
+ hideDialpadAndSearchUi();
+ mClearSearchOnPause = false;
+ }
+ if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) {
+ commitDialpadFragmentHide();
+ }
+ super.onPause();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
+ outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
+ outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
+ outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
+ outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown);
+ outState.putBoolean(KEY_WAS_CONFIGURATION_CHANGE, isChangingConfigurations());
+ mActionBarController.saveInstanceState(outState);
+ mStateSaved = true;
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ if (fragment instanceof DialpadFragment) {
+ mDialpadFragment = (DialpadFragment) fragment;
+ if (!mIsDialpadShown && !mShowDialpadOnResume) {
+ final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.hide(mDialpadFragment);
+ transaction.commit();
+ }
+ } else if (fragment instanceof SmartDialSearchFragment) {
+ mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
+ mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this);
+ if (!TextUtils.isEmpty(mDialpadQuery)) {
+ mSmartDialSearchFragment.setAddToContactNumber(mDialpadQuery);
+ }
+ } else if (fragment instanceof SearchFragment) {
+ mRegularSearchFragment = (RegularSearchFragment) fragment;
+ mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this);
+ } else if (fragment instanceof ListsFragment) {
+ mListsFragment = (ListsFragment) fragment;
+ mListsFragment.addOnPageChangeListener(this);
+ }
+ if (fragment instanceof SearchFragment) {
+ final SearchFragment searchFragment = (SearchFragment) fragment;
+ searchFragment.setReranker(
+ new CursorReranker() {
+ @Override
+ @MainThread
+ public Cursor rerankCursor(Cursor data) {
+ Assert.isMainThread();
+ return mP13nRanker.rankCursor(data, PhoneQuery.PHONE_NUMBER);
+ }
+ });
+ searchFragment.addOnLoadFinishedListener(
+ new OnLoadFinishedListener() {
+ @Override
+ public void onLoadFinished() {
+ mP13nLogger.onSearchQuery(
+ searchFragment.getQueryString(),
+ (PhoneNumberListAdapter) searchFragment.getAdapter());
+ }
+ });
+ }
+ }
+
+ protected void handleMenuSettings() {
+ final Intent intent = new Intent(this, DialerSettingsActivity.class);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onClick(View view) {
+ int resId = view.getId();
+ if (resId == R.id.floating_action_button) {
+ if (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_ALL_CONTACTS
+ && !mInRegularSearch
+ && !mInDialpadSearch) {
+ DialerUtils.startActivityWithErrorToast(
+ this, IntentUtil.getNewContactIntent(), R.string.add_contact_not_available);
+ Logger.get(this).logImpression(DialerImpression.Type.NEW_CONTACT_FAB);
+ } else if (!mIsDialpadShown) {
+ mInCallDialpadUp = false;
+ showDialpadFragment(true);
+ }
+ } else if (resId == R.id.voice_search_button) {
+ try {
+ startActivityForResult(
+ new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
+ ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(
+ DialtactsActivity.this, R.string.voice_search_not_available, Toast.LENGTH_SHORT)
+ .show();
+ }
+ } else if (resId == R.id.dialtacts_options_menu_button) {
+ mOverflowMenu.show();
+ } else {
+ Assert.fail("Unexpected onClick event from " + view);
+ }
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (!isSafeToCommitTransactions()) {
+ return true;
+ }
+
+ int resId = item.getItemId();
+ if (item.getItemId() == R.id.menu_delete_all) {
+ ClearCallLogDialog.show(getFragmentManager());
+ return true;
+ } else if (resId == R.id.menu_clear_frequents) {
+ ClearFrequentsDialog.show(getFragmentManager());
+ Logger.get(this).logScreenView(ScreenEvent.Type.CLEAR_FREQUENTS, this);
+ return true;
+ } else if (resId == R.id.menu_call_settings) {
+ handleMenuSettings();
+ Logger.get(this).logScreenView(ScreenEvent.Type.SETTINGS, this);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) {
+ if (resultCode == RESULT_OK) {
+ final ArrayList<String> matches =
+ data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
+ if (matches.size() > 0) {
+ mVoiceSearchQuery = matches.get(0);
+ } else {
+ LogUtil.i("DialtactsActivity.onActivityResult", "voice search - nothing heard");
+ }
+ } else {
+ LogUtil.e("DialtactsActivity.onActivityResult", "voice search failed: " + resultCode);
+ }
+ } else if (requestCode == ACTIVITY_REQUEST_CODE_CALL_COMPOSE) {
+ if (resultCode != RESULT_OK) {
+ LogUtil.i(
+ "DialtactsActivity.onActivityResult",
+ "returned from call composer, error occurred (resultCode=" + resultCode + ")");
+ String message =
+ getString(R.string.call_composer_connection_failed, getString(R.string.share_and_call));
+ Snackbar.make(mParentLayout, message, Snackbar.LENGTH_LONG).show();
+ } else {
+ LogUtil.i("DialtactsActivity.onActivityResult", "returned from call composer, no error");
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ /**
+ * Update the number of unread voicemails (potentially other tabs) displayed next to the tab icon.
+ */
+ public void updateTabUnreadCounts() {
+ mListsFragment.updateTabUnreadCounts();
+ }
+
+ /**
+ * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual
+ * updates are handled by a callback which is invoked after the dialpad fragment is shown.
+ *
+ * @see #onDialpadShown
+ */
+ private void showDialpadFragment(boolean animate) {
+ if (mIsDialpadShown || mStateSaved) {
+ return;
+ }
+ mIsDialpadShown = true;
+
+ mListsFragment.setUserVisibleHint(false);
+
+ final FragmentTransaction ft = getFragmentManager().beginTransaction();
+ if (mDialpadFragment == null) {
+ mDialpadFragment = new DialpadFragment();
+ ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT);
+ } else {
+ ft.show(mDialpadFragment);
+ }
+
+ mDialpadFragment.setAnimate(animate);
+ Logger.get(this).logScreenView(ScreenEvent.Type.DIALPAD, this);
+ ft.commit();
+
+ if (animate) {
+ mFloatingActionButtonController.scaleOut();
+ } else {
+ mFloatingActionButtonController.setVisible(false);
+ maybeEnterSearchUi();
+ }
+ mActionBarController.onDialpadUp();
+
+ Assert.isNotNull(mListsFragment.getView()).animate().alpha(0).withLayer();
+
+ //adjust the title, so the user will know where we're at when the activity start/resumes.
+ setTitle(R.string.launcherDialpadActivityLabel);
+ }
+
+ /** Callback from child DialpadFragment when the dialpad is shown. */
+ public void onDialpadShown() {
+ Assert.isNotNull(mDialpadFragment);
+ if (mDialpadFragment.getAnimate()) {
+ Assert.isNotNull(mDialpadFragment.getView()).startAnimation(mSlideIn);
+ } else {
+ mDialpadFragment.setYFraction(0);
+ }
+
+ updateSearchFragmentPosition();
+ }
+
+ /**
+ * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in a
+ * callback after the hide animation ends.
+ *
+ * @see #commitDialpadFragmentHide
+ */
+ public void hideDialpadFragment(boolean animate, boolean clearDialpad) {
+ if (mDialpadFragment == null || mDialpadFragment.getView() == null) {
+ return;
+ }
+ if (clearDialpad) {
+ // Temporarily disable accessibility when we clear the dialpad, since it should be
+ // invisible and should not announce anything.
+ mDialpadFragment
+ .getDigitsWidget()
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mDialpadFragment.clearDialpad();
+ mDialpadFragment
+ .getDigitsWidget()
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
+ if (!mIsDialpadShown) {
+ return;
+ }
+ mIsDialpadShown = false;
+ mDialpadFragment.setAnimate(animate);
+ mListsFragment.setUserVisibleHint(true);
+ mListsFragment.sendScreenViewForCurrentPosition();
+
+ updateSearchFragmentPosition();
+
+ mFloatingActionButtonController.align(getFabAlignment(), animate);
+ if (animate) {
+ mDialpadFragment.getView().startAnimation(mSlideOut);
+ } else {
+ commitDialpadFragmentHide();
+ }
+
+ mActionBarController.onDialpadDown();
+
+ if (isInSearchUi()) {
+ if (TextUtils.isEmpty(mSearchQuery)) {
+ exitSearchUi();
+ }
+ }
+ //reset the title to normal.
+ setTitle(R.string.launcherActivityLabel);
+ }
+
+ /** Finishes hiding the dialpad fragment after any animations are completed. */
+ private void commitDialpadFragmentHide() {
+ if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) {
+ final FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.hide(mDialpadFragment);
+ ft.commit();
+ }
+ mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
+ }
+
+ private void updateSearchFragmentPosition() {
+ SearchFragment fragment = null;
+ if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
+ fragment = mSmartDialSearchFragment;
+ } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
+ fragment = mRegularSearchFragment;
+ }
+ if (fragment != null && fragment.isVisible()) {
+ fragment.updatePosition(true /* animate */);
+ }
+ }
+
+ @Override
+ public boolean isInSearchUi() {
+ return mInDialpadSearch || mInRegularSearch;
+ }
+
+ @Override
+ public boolean hasSearchQuery() {
+ return !TextUtils.isEmpty(mSearchQuery);
+ }
+
+ @Override
+ public boolean shouldShowActionBar() {
+ return mListsFragment.shouldShowActionBar();
+ }
+
+ private void setNotInSearchUi() {
+ mInDialpadSearch = false;
+ mInRegularSearch = false;
+ }
+
+ private void hideDialpadAndSearchUi() {
+ if (mIsDialpadShown) {
+ hideDialpadFragment(false, true);
+ } else {
+ exitSearchUi();
+ }
+ }
+
+ private void prepareVoiceSearchButton() {
+ final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ if (canIntentBeHandled(voiceIntent)) {
+ mVoiceSearchButton.setVisibility(View.VISIBLE);
+ mVoiceSearchButton.setOnClickListener(this);
+ } else {
+ mVoiceSearchButton.setVisibility(View.GONE);
+ }
+ }
+
+ public boolean isNearbyPlacesSearchEnabled() {
+ return false;
+ }
+
+ protected int getSearchBoxHint() {
+ return R.string.dialer_hint_find_contact;
+ }
+
+ /** Sets the hint text for the contacts search box */
+ private void setSearchBoxHint() {
+ SearchEditTextLayout searchEditTextLayout =
+ (SearchEditTextLayout)
+ getActionBarSafely().getCustomView().findViewById(R.id.search_view_container);
+ ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search))
+ .setHint(getSearchBoxHint());
+ }
+
+ protected OptionsPopupMenu buildOptionsMenu(View invoker) {
+ final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker);
+ popupMenu.inflate(R.menu.dialtacts_options);
+ popupMenu.setOnMenuItemClickListener(this);
+ return popupMenu;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mPendingSearchViewQuery != null) {
+ mSearchView.setText(mPendingSearchViewQuery);
+ mPendingSearchViewQuery = null;
+ }
+ if (mActionBarController != null) {
+ mActionBarController.restoreActionBarOffset();
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the intent is due to hitting the green send key (hardware call button:
+ * KEYCODE_CALL) while in a call.
+ *
+ * @param intent the intent that launched this activity
+ * @return true if the intent is due to hitting the green send key while in a call
+ */
+ private boolean isSendKeyWhileInCall(Intent intent) {
+ // If there is a call in progress and the user launched the dialer by hitting the call
+ // button, go straight to the in-call screen.
+ final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction());
+
+ // When KEYCODE_CALL event is handled it dispatches an intent with the ACTION_CALL_BUTTON.
+ // Besides of checking the intent action, we must check if the phone is really during a
+ // call in order to decide whether to ignore the event or continue to display the activity.
+ if (callKey && phoneIsInUse()) {
+ TelecomUtil.showInCallScreen(this, false);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the current tab based on the intent's request type
+ *
+ * @param intent Intent that contains information about which tab should be selected
+ */
+ private void displayFragment(Intent intent) {
+ // If we got here by hitting send and we're in call forward along to the in-call activity
+ if (isSendKeyWhileInCall(intent)) {
+ finish();
+ return;
+ }
+
+ final boolean showDialpadChooser =
+ !ACTION_SHOW_TAB.equals(intent.getAction())
+ && phoneIsInUse()
+ && !DialpadFragment.isAddCallMode(intent);
+ if (showDialpadChooser || (intent.getData() != null && isDialIntent(intent))) {
+ showDialpadFragment(false);
+ mDialpadFragment.setStartedFromNewIntent(true);
+ if (showDialpadChooser && !mDialpadFragment.isVisible()) {
+ mInCallDialpadUp = true;
+ }
+ }
+ }
+
+ @Override
+ public void onNewIntent(Intent newIntent) {
+ setIntent(newIntent);
+
+ mStateSaved = false;
+ displayFragment(newIntent);
+
+ invalidateOptionsMenu();
+ }
+
+ /** Returns true if the given intent contains a phone number to populate the dialer with */
+ private boolean isDialIntent(Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
+ return true;
+ }
+ if (Intent.ACTION_VIEW.equals(action)) {
+ final Uri data = intent.getData();
+ if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Shows the search fragment */
+ private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) {
+ if (mStateSaved || getFragmentManager().isDestroyed()) {
+ // Weird race condition where fragment is doing work after the activity is destroyed
+ // due to talkback being on (b/10209937). Just return since we can't do any
+ // constructive here.
+ return;
+ }
+
+ if (DEBUG) {
+ LogUtil.v("DialtactsActivity.enterSearchUi", "smart dial " + smartDialSearch);
+ }
+
+ final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ if (mInDialpadSearch && mSmartDialSearchFragment != null) {
+ transaction.remove(mSmartDialSearchFragment);
+ } else if (mInRegularSearch && mRegularSearchFragment != null) {
+ transaction.remove(mRegularSearchFragment);
+ }
+
+ final String tag;
+ if (smartDialSearch) {
+ tag = TAG_SMARTDIAL_SEARCH_FRAGMENT;
+ } else {
+ tag = TAG_REGULAR_SEARCH_FRAGMENT;
+ }
+ mInDialpadSearch = smartDialSearch;
+ mInRegularSearch = !smartDialSearch;
+
+ mFloatingActionButtonController.scaleOut();
+
+ SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
+ if (animate) {
+ transaction.setCustomAnimations(android.R.animator.fade_in, 0);
+ } else {
+ transaction.setTransition(FragmentTransaction.TRANSIT_NONE);
+ }
+ if (fragment == null) {
+ if (smartDialSearch) {
+ fragment = new SmartDialSearchFragment();
+ } else {
+ fragment = Bindings.getLegacy(this).newRegularSearchFragment();
+ fragment.setOnTouchListener(
+ new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // Show the FAB when the user touches the lists fragment and the soft
+ // keyboard is hidden.
+ hideDialpadFragment(true, false);
+ showFabInSearchUi();
+ v.performClick();
+ return false;
+ }
+ });
+ }
+ transaction.add(R.id.dialtacts_frame, fragment, tag);
+ } else {
+ transaction.show(fragment);
+ }
+ // DialtactsActivity will provide the options menu
+ fragment.setHasOptionsMenu(false);
+ fragment.setShowEmptyListForNullQuery(true);
+ if (!smartDialSearch) {
+ fragment.setQueryString(query);
+ }
+ transaction.commit();
+
+ if (animate) {
+ Assert.isNotNull(mListsFragment.getView()).animate().alpha(0).withLayer();
+ }
+ mListsFragment.setUserVisibleHint(false);
+
+ if (smartDialSearch) {
+ Logger.get(this).logScreenView(ScreenEvent.Type.SMART_DIAL_SEARCH, this);
+ } else {
+ Logger.get(this).logScreenView(ScreenEvent.Type.REGULAR_SEARCH, this);
+ }
+ }
+
+ /** Hides the search fragment */
+ private void exitSearchUi() {
+ // See related bug in enterSearchUI();
+ if (getFragmentManager().isDestroyed() || mStateSaved) {
+ return;
+ }
+
+ mSearchView.setText(null);
+
+ if (mDialpadFragment != null) {
+ mDialpadFragment.clearDialpad();
+ }
+
+ setNotInSearchUi();
+
+ // Restore the FAB for the lists fragment.
+ if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) {
+ mFloatingActionButtonController.setVisible(false);
+ }
+ mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
+ onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */);
+ onPageSelected(mListsFragment.getCurrentTabIndex());
+
+ final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ if (mSmartDialSearchFragment != null) {
+ transaction.remove(mSmartDialSearchFragment);
+ }
+ if (mRegularSearchFragment != null) {
+ transaction.remove(mRegularSearchFragment);
+ }
+ transaction.commit();
+
+ Assert.isNotNull(mListsFragment.getView()).animate().alpha(1).withLayer();
+
+ if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
+ // If the dialpad fragment wasn't previously visible, then send a screen view because
+ // we are exiting regular search. Otherwise, the screen view will be sent by
+ // {@link #hideDialpadFragment}.
+ mListsFragment.sendScreenViewForCurrentPosition();
+ mListsFragment.setUserVisibleHint(true);
+ }
+
+ mActionBarController.onSearchUiExited();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mStateSaved) {
+ return;
+ }
+ if (mIsDialpadShown) {
+ if (TextUtils.isEmpty(mSearchQuery)
+ || (mSmartDialSearchFragment != null
+ && mSmartDialSearchFragment.isVisible()
+ && mSmartDialSearchFragment.getAdapter().getCount() == 0)) {
+ exitSearchUi();
+ }
+ hideDialpadFragment(true, false);
+ } else if (isInSearchUi()) {
+ exitSearchUi();
+ DialerUtils.hideInputMethod(mParentLayout);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ private void maybeEnterSearchUi() {
+ if (!isInSearchUi()) {
+ enterSearchUi(true /* isSmartDial */, mSearchQuery, false);
+ }
+ }
+
+ /** @return True if the search UI was exited, false otherwise */
+ private boolean maybeExitSearchUi() {
+ if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) {
+ exitSearchUi();
+ DialerUtils.hideInputMethod(mParentLayout);
+ return true;
+ }
+ return false;
+ }
+
+ private void showFabInSearchUi() {
+ mFloatingActionButtonController.changeIcon(
+ getResources().getDrawable(R.drawable.fab_ic_dial, null),
+ getResources().getString(R.string.action_menu_dialpad_button));
+ mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
+ mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
+ }
+
+ @Override
+ public void onDialpadQueryChanged(String query) {
+ mDialpadQuery = query;
+ if (mSmartDialSearchFragment != null) {
+ mSmartDialSearchFragment.setAddToContactNumber(query);
+ }
+ final String normalizedQuery =
+ SmartDialNameMatcher.normalizeNumber(query, SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
+
+ if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
+ if (DEBUG) {
+ LogUtil.v("DialtactsActivity.onDialpadQueryChanged", "new query: " + query);
+ }
+ if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
+ // This callback can happen if the dialpad fragment is recreated because of
+ // activity destruction. In that case, don't update the search view because
+ // that would bring the user back to the search fragment regardless of the
+ // previous state of the application. Instead, just return here and let the
+ // fragment manager correctly figure out whatever fragment was last displayed.
+ if (!TextUtils.isEmpty(normalizedQuery)) {
+ mPendingSearchViewQuery = normalizedQuery;
+ }
+ return;
+ }
+ mSearchView.setText(normalizedQuery);
+ }
+
+ try {
+ if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
+ mDialpadFragment.process_quote_emergency_unquote(normalizedQuery);
+ }
+ } catch (Exception ignored) {
+ // Skip any exceptions for this piece of code
+ }
+ }
+
+ @Override
+ public boolean onDialpadSpacerTouchWithEmptyQuery() {
+ if (mInDialpadSearch
+ && mSmartDialSearchFragment != null
+ && !mSmartDialSearchFragment.isShowingPermissionRequest()) {
+ hideDialpadFragment(true /* animate */, true /* clearDialpad */);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onListFragmentScrollStateChange(int scrollState) {
+ if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ hideDialpadFragment(true, false);
+ DialerUtils.hideInputMethod(mParentLayout);
+ }
+ }
+
+ @Override
+ public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ // TODO: No-op for now. This should eventually show/hide the actionBar based on
+ // interactions with the ListsFragments.
+ }
+
+ private boolean phoneIsInUse() {
+ return TelecomUtil.isInCall(this);
+ }
+
+ private boolean canIntentBeHandled(Intent intent) {
+ final PackageManager packageManager = getPackageManager();
+ final List<ResolveInfo> resolveInfo =
+ packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ return resolveInfo != null && resolveInfo.size() > 0;
+ }
+
+ /** Called when the user has long-pressed a contact tile to start a drag operation. */
+ @Override
+ public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
+ mListsFragment.showRemoveView(true);
+ }
+
+ @Override
+ public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {}
+
+ /** Called when the user has released a contact tile after long-pressing it. */
+ @Override
+ public void onDragFinished(int x, int y) {
+ mListsFragment.showRemoveView(false);
+ }
+
+ @Override
+ public void onDroppedOnRemove() {}
+
+ /**
+ * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer once it has
+ * been attached to the activity.
+ */
+ @Override
+ public void setDragDropController(DragDropController dragController) {
+ mDragDropController = dragController;
+ mListsFragment.getRemoveView().setDragDropController(dragController);
+ }
+
+ /** Implemented to satisfy {@link SpeedDialFragment.HostInterface} */
+ @Override
+ public void showAllContactsTab() {
+ if (mListsFragment != null) {
+ mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS);
+ }
+ }
+
+ /** Implemented to satisfy {@link CallLogFragment.HostInterface} */
+ @Override
+ public void showDialpad() {
+ showDialpadFragment(true);
+ }
+
+ @Override
+ public void enableFloatingButton(boolean enabled) {
+ LogUtil.d("DialtactsActivity.enableFloatingButton", "enable: %b", enabled);
+ // Floating button shouldn't be enabled when dialpad is shown.
+ if (!isDialpadShown() || !enabled) {
+ mFloatingActionButtonController.setVisible(enabled);
+ }
+ }
+
+ @Override
+ public void onPickDataUri(
+ Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
+ mClearSearchOnPause = true;
+ PhoneNumberInteraction.startInteractionForPhoneCall(
+ DialtactsActivity.this, dataUri, isVideoCall, callSpecificAppData);
+ }
+
+ @Override
+ public void onPickPhoneNumber(
+ String phoneNumber, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
+ if (phoneNumber == null) {
+ // Invalid phone number, but let the call go through so that InCallUI can show
+ // an error message.
+ phoneNumber = "";
+ }
+
+ Intent intent =
+ new CallIntentBuilder(phoneNumber, callSpecificAppData).setIsVideoCall(isVideoCall).build();
+
+ DialerUtils.startActivityWithErrorToast(this, intent);
+ mClearSearchOnPause = true;
+ }
+
+ @Override
+ public void onHomeInActionBarSelected() {
+ exitSearchUi();
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ int tabIndex = mListsFragment.getCurrentTabIndex();
+
+ // Scroll the button from center to end when moving from the Speed Dial to Call History tab.
+ // In RTL, scroll when the current tab is Call History instead, since the order of the tabs
+ // is reversed and the ViewPager returns the left tab position during scroll.
+ boolean isRtl = ViewUtil.isRtl();
+ if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) {
+ mFloatingActionButtonController.onPageScrolled(positionOffset);
+ } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_HISTORY && !mIsLandscape) {
+ mFloatingActionButtonController.onPageScrolled(1 - positionOffset);
+ } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) {
+ mFloatingActionButtonController.onPageScrolled(1);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ updateMissedCalls();
+ int tabIndex = mListsFragment.getCurrentTabIndex();
+ mPreviouslySelectedTabIndex = tabIndex;
+ mFloatingActionButtonController.setVisible(true);
+ if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS
+ && !mInRegularSearch
+ && !mInDialpadSearch) {
+ mFloatingActionButtonController.changeIcon(
+ getResources().getDrawable(R.drawable.ic_person_add_24dp, null),
+ getResources().getString(R.string.search_shortcut_create_new_contact));
+ } else {
+ mFloatingActionButtonController.changeIcon(
+ getResources().getDrawable(R.drawable.fab_ic_dial, null),
+ getResources().getString(R.string.action_menu_dialpad_button));
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {}
+
+ @Override
+ public boolean isActionBarShowing() {
+ return mActionBarController.isActionBarShowing();
+ }
+
+ @Override
+ public ActionBarController getActionBarController() {
+ return mActionBarController;
+ }
+
+ @Override
+ public boolean isDialpadShown() {
+ return mIsDialpadShown;
+ }
+
+ @Override
+ public int getDialpadHeight() {
+ if (mDialpadFragment != null) {
+ return mDialpadFragment.getDialpadHeight();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getActionBarHideOffset() {
+ return getActionBarSafely().getHideOffset();
+ }
+
+ @Override
+ public void setActionBarHideOffset(int offset) {
+ getActionBarSafely().setHideOffset(offset);
+ }
+
+ @Override
+ public int getActionBarHeight() {
+ return mActionBarHeight;
+ }
+
+ private int getFabAlignment() {
+ if (!mIsLandscape
+ && !isInSearchUi()
+ && mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) {
+ return FloatingActionButtonController.ALIGN_MIDDLE;
+ }
+ return FloatingActionButtonController.ALIGN_END;
+ }
+
+ private void updateMissedCalls() {
+ if (mPreviouslySelectedTabIndex == ListsFragment.TAB_INDEX_HISTORY) {
+ mListsFragment.markMissedCallsAsReadAndRemoveNotifications();
+ }
+ }
+
+ @Override
+ public void onDisambigDialogDismissed() {
+ // Don't do anything; the app will remain open with favorites tiles displayed.
+ }
+
+ @Override
+ public void interactionError(@InteractionErrorCode int interactionErrorCode) {
+ switch (interactionErrorCode) {
+ case InteractionErrorCode.USER_LEAVING_ACTIVITY:
+ // This is expected to happen if the user exits the activity before the interaction occurs.
+ return;
+ case InteractionErrorCode.CONTACT_NOT_FOUND:
+ case InteractionErrorCode.CONTACT_HAS_NO_NUMBER:
+ case InteractionErrorCode.OTHER_ERROR:
+ default:
+ // All other error codes are unexpected. For example, it should be impossible to start an
+ // interaction with an invalid contact from the Dialtacts activity.
+ Assert.fail("PhoneNumberInteraction error: " + interactionErrorCode);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, String[] permissions, int[] grantResults) {
+ // This should never happen; it should be impossible to start an interaction without the
+ // contacts permission from the Dialtacts activity.
+ Assert.fail(
+ String.format(
+ Locale.US,
+ "Permissions requested unexpectedly: %d/%s/%s",
+ requestCode,
+ Arrays.toString(permissions),
+ Arrays.toString(grantResults)));
+ }
+
+ protected class OptionsPopupMenu extends PopupMenu {
+
+ public OptionsPopupMenu(Context context, View anchor) {
+ super(context, anchor, Gravity.END);
+ }
+
+ @Override
+ public void show() {
+ final boolean hasContactsPermission =
+ PermissionsUtil.hasContactsPermissions(DialtactsActivity.this);
+ final Menu menu = getMenu();
+ final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
+ clearFrequents.setVisible(
+ mListsFragment != null
+ && mListsFragment.getSpeedDialFragment() != null
+ && mListsFragment.getSpeedDialFragment().hasFrequents()
+ && hasContactsPermission);
+
+ menu.findItem(R.id.menu_delete_all)
+ .setVisible(PermissionsUtil.hasPhonePermissions(DialtactsActivity.this));
+ super.show();
+ }
+ }
+
+ /**
+ * Listener that listens to drag events and sends their x and y coordinates to a {@link
+ * DragDropController}.
+ */
+ private class LayoutOnDragListener implements OnDragListener {
+
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
+ mDragDropController.handleDragHovered(v, (int) event.getX(), (int) event.getY());
+ }
+ return true;
+ }
+ }
+}