blob: 3c9b870dd45ee123bf850d75dbe6ec98e3af0f23 [file] [log] [blame]
/*
* 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;
import android.app.ActionBar;
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.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.speech.RecognizerIntent;
import android.support.v4.view.ViewPager;
import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
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.View.OnTouchListener;
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.FrameLayout;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.Toast;
import com.android.contacts.common.CallUtil;
import com.android.contacts.common.activity.TransactionSafeActivity;
import com.android.contacts.common.dialog.ClearFrequentsDialog;
import com.android.contacts.common.interactions.ImportExportDialogFragment;
import com.android.contacts.common.interactions.TouchPointManager;
import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
import com.android.contacts.common.widget.FloatingActionButtonController;
import com.android.contacts.commonbind.analytics.AnalyticsUtil;
import com.android.dialer.calllog.CallLogActivity;
import com.android.dialer.database.DialerDatabaseHelper;
import com.android.dialer.dialpad.DialpadFragment;
import com.android.dialer.dialpad.SmartDialNameMatcher;
import com.android.dialer.dialpad.SmartDialPrefix;
import com.android.dialer.interactions.PhoneNumberInteraction;
import com.android.dialer.list.DragDropController;
import com.android.dialer.list.ListsFragment;
import com.android.dialer.list.OnDragDropListener;
import com.android.dialer.list.OnListFragmentScrolledListener;
import com.android.dialer.list.PhoneFavoriteSquareTileView;
import com.android.dialer.list.RegularSearchFragment;
import com.android.dialer.list.SearchFragment;
import com.android.dialer.list.SmartDialSearchFragment;
import com.android.dialer.list.SpeedDialFragment;
import com.android.dialer.settings.DialerSettingsActivity;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.widget.ActionBarController;
import com.android.dialer.widget.SearchEditTextLayout;
import com.android.dialer.widget.SearchEditTextLayout.OnBackButtonClickedListener;
import com.android.dialerbind.DatabaseHelperManager;
import com.android.phone.common.animation.AnimUtils;
import com.android.phone.common.animation.AnimationListenerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* The dialer tab's title is 'phone', a more common name (see strings.xml).
*/
public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener,
DialpadFragment.OnDialpadQueryChangedListener,
OnListFragmentScrolledListener,
ListsFragment.HostInterface,
SpeedDialFragment.HostInterface,
SearchFragment.HostInterface,
OnDragDropListener,
OnPhoneNumberPickerActionListener,
PopupMenu.OnMenuItemClickListener,
ViewPager.OnPageChangeListener,
ActionBarController.ActivityUi {
private static final String TAG = "DialtactsActivity";
public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences";
/** @see #getCallOrigin() */
private static final String CALL_ORIGIN_DIALTACTS =
"com.android.dialer.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_IS_DIALPAD_SHOWN = "is_dialpad_shown";
private static final String TAG_DIALPAD_FRAGMENT = "dialpad";
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;
private FrameLayout mParentLayout;
/**
* Fragment containing the dialpad that slides into view
*/
protected DialpadFragment mDialpadFragment;
/**
* 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;
/**
* Listener for after slide out animation completes on dialer fragment.
*/
AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
@Override
public void onAnimationEnd(Animation animation) {
commitDialpadFragmentHide();
}
};
/**
* Fragment containing the speed dial list, recents 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;
/**
* The position of the currently selected tab in the attached {@link ListsFragment}.
*/
private int mCurrentTabPosition = 0;
/**
* 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 DialerDatabaseHelper mDialerDatabaseHelper;
private DragDropController mDragDropController;
private ActionBarController mActionBarController;
private FloatingActionButtonController mFloatingActionButtonController;
private int mActionBarHeight;
private class OptionsPopupMenu extends PopupMenu {
public OptionsPopupMenu(Context context, View anchor) {
super(context, anchor, Gravity.END);
}
@Override
public void show() {
final Menu menu = getMenu();
final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
clearFrequents.setVisible(mListsFragment != null &&
mListsFragment.getSpeedDialFragment() != null &&
mListsFragment.getSpeedDialFragment().hasFrequents());
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;
}
}
/**
* 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) {
Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText);
Log.d(TAG, "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);
}
}
if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
} else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
}
}
@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());
}
}
};
/**
* If the search term is empty and the user closes the soft keyboard, close the search UI.
*/
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 &&
TextUtils.isEmpty(mSearchView.getText().toString())) {
maybeExitSearchUi();
}
return false;
}
};
@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) {
super.onCreate(savedInstanceState);
mFirstLaunch = true;
final Resources resources = getResources();
mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height_large);
setContentView(R.layout.dialtacts_activity);
getWindow().setBackgroundDrawable(null);
final ActionBar actionBar = getActionBar();
actionBar.setCustomView(R.layout.search_edittext);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setBackgroundDrawable(null);
mActionBarController = new ActionBarController(this,
(SearchEditTextLayout) actionBar.getCustomView());
SearchEditTextLayout searchEditTextLayout =
(SearchEditTextLayout) actionBar.getCustomView();
searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener);
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.setOnBackButtonClickedListener(new OnBackButtonClickedListener() {
@Override
public void onBackButtonClicked() {
onBackPressed();
}
});
mIsLandscape = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
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(searchEditTextLayout);
optionsMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener());
// Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState
// is null. Otherwise the fragment manager takes care of recreating these fragments.
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
.add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_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);
mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN);
mActionBarController.restoreInstanceState(savedInstanceState);
}
final boolean isLayoutRtl = DialerUtils.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);
mSlideOut.setAnimationListener(mSlideOutListener);
mParentLayout = (FrameLayout) 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);
updateFloatingActionButtonControllerAlignment(false /* animate */);
}
});
setupActivityOverlay();
mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this);
SmartDialPrefix.initializeNanpSettings(this);
}
private void setupActivityOverlay() {
final View activityOverlay = findViewById(R.id.activity_overlay);
activityOverlay.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (!mIsDialpadShown) {
maybeExitSearchUi();
}
return false;
}
});
}
@Override
protected void 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;
}
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) {
AnalyticsUtil.sendScreenView(mDialpadFragment, this);
}
mIsRestarting = false;
}
prepareVoiceSearchButton();
mDialerDatabaseHelper.startSmartDialUpdateThread();
updateFloatingActionButtonControllerAlignment(false /* animate */);
}
@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);
mActionBarController.saveInstanceState(outState);
mStateSaved = true;
}
@Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof DialpadFragment) {
mDialpadFragment = (DialpadFragment) fragment;
if (!mShowDialpadOnResume) {
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.hide(mDialpadFragment);
transaction.commit();
}
} else if (fragment instanceof SmartDialSearchFragment) {
mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this);
} else if (fragment instanceof SearchFragment) {
mRegularSearchFragment = (RegularSearchFragment) fragment;
mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this);
} else if (fragment instanceof ListsFragment) {
mListsFragment = (ListsFragment) fragment;
mListsFragment.addOnPageChangeListener(this);
}
}
protected void handleMenuSettings() {
final Intent intent = new Intent(this, DialerSettingsActivity.class);
startActivity(intent);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.floating_action_button:
if (!mIsDialpadShown) {
mInCallDialpadUp = false;
showDialpadFragment(true);
}
break;
case 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();
}
break;
case R.id.dialtacts_options_menu_button:
mOverflowMenu.show();
break;
default: {
Log.wtf(TAG, "Unexpected onClick event from " + view);
break;
}
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_history:
showCallHistory();
break;
case R.id.menu_add_contact:
try {
startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
} catch (ActivityNotFoundException e) {
Toast toast = Toast.makeText(this,
R.string.add_contact_not_available,
Toast.LENGTH_SHORT);
toast.show();
}
break;
case R.id.menu_import_export:
// We hard-code the "contactsAreAvailable" argument because doing it properly would
// involve querying a {@link ProviderStatusLoader}, which we don't want to do right
// now in Dialtacts for (potential) performance reasons. Compare with how it is
// done in {@link PeopleActivity}.
ImportExportDialogFragment.show(getFragmentManager(), true,
DialtactsActivity.class);
return true;
case R.id.menu_clear_frequents:
ClearFrequentsDialog.show(getFragmentManager());
return true;
case R.id.menu_call_settings:
handleMenuSettings();
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) {
final String match = matches.get(0);
mSearchView.setText(match);
} else {
Log.e(TAG, "Voice search - nothing heard");
}
} else {
Log.e(TAG, "Voice search failed");
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* 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;
mDialpadFragment.setAnimate(animate);
mListsFragment.setUserVisibleHint(false);
AnalyticsUtil.sendScreenView(mDialpadFragment);
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.show(mDialpadFragment);
ft.commit();
if (animate) {
mFloatingActionButtonController.scaleOut();
} else {
mFloatingActionButtonController.setVisible(false);
}
mActionBarController.onDialpadUp();
if (!isInSearchUi()) {
enterSearchUi(true /* isSmartDial */, mSearchQuery);
}
}
/**
* Callback from child DialpadFragment when the dialpad is shown.
*/
public void onDialpadShown() {
if (mDialpadFragment.getAnimate()) {
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) {
return;
}
if (clearDialpad) {
mDialpadFragment.clearDialpad();
}
if (!mIsDialpadShown) {
return;
}
mIsDialpadShown = false;
mDialpadFragment.setAnimate(animate);
mListsFragment.setUserVisibleHint(true);
mListsFragment.sendScreenViewForCurrentPosition();
updateSearchFragmentPosition();
updateFloatingActionButtonControllerAlignment(animate);
if (animate) {
mDialpadFragment.getView().startAnimation(mSlideOut);
} else {
commitDialpadFragmentHide();
}
mActionBarController.onDialpadDown();
if (isInSearchUi()) {
if (TextUtils.isEmpty(mSearchQuery)) {
exitSearchUi();
}
}
}
/**
* Finishes hiding the dialpad fragment after any animations are completed.
*/
private void commitDialpadFragmentHide() {
if (!mStateSaved && !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);
}
}
private OptionsPopupMenu buildOptionsMenu(View invoker) {
final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker);
popupMenu.inflate(R.menu.dialtacts_options);
final Menu menu = popupMenu.getMenu();
popupMenu.setOnMenuItemClickListener(this);
return popupMenu;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mPendingSearchViewQuery != null) {
mSearchView.setText(mPendingSearchViewQuery);
mPendingSearchViewQuery = 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());
if (callKey) {
getTelecomManager().showInCallScreen(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;
}
if (mDialpadFragment != null) {
final boolean phoneIsInUse = phoneIsInUse();
if (phoneIsInUse || (intent.getData() != null && isDialIntent(intent))) {
mDialpadFragment.setStartedFromNewIntent(true);
if (phoneIsInUse && !mDialpadFragment.isVisible()) {
mInCallDialpadUp = true;
}
showDialpadFragment(false);
}
}
}
@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;
}
/**
* Returns an appropriate call origin for this Activity. May return null when no call origin
* should be used (e.g. when some 3rd party application launched the screen. Call origin is
* for remembering the tab in which the user made a phone call, so the external app's DIAL
* request should not be counted.)
*/
public String getCallOrigin() {
return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null;
}
/**
* Shows the search fragment
*/
private void enterSearchUi(boolean smartDialSearch, String query) {
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) {
Log.d(TAG, "Entering search UI - 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;
SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
transaction.setCustomAnimations(android.R.animator.fade_in, 0);
if (fragment == null) {
if (smartDialSearch) {
fragment = new SmartDialSearchFragment();
} else {
fragment = new RegularSearchFragment();
}
transaction.add(R.id.dialtacts_frame, fragment, tag);
} else {
transaction.show(fragment);
}
// DialtactsActivity will provide the options menu
fragment.setHasOptionsMenu(false);
fragment.setShowEmptyListForNullQuery(true);
fragment.setQueryString(query, false /* delaySelection */);
transaction.commit();
mListsFragment.getView().animate().alpha(0).withLayer();
mListsFragment.setUserVisibleHint(false);
}
/**
* Hides the search fragment
*/
private void exitSearchUi() {
// See related bug in enterSearchUI();
if (getFragmentManager().isDestroyed() || mStateSaved) {
return;
}
mSearchView.setText(null);
mDialpadFragment.clearDialpad();
setNotInSearchUi();
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
if (mSmartDialSearchFragment != null) {
transaction.remove(mSmartDialSearchFragment);
}
if (mRegularSearchFragment != null) {
transaction.remove(mRegularSearchFragment);
}
transaction.commit();
mListsFragment.getView().animate().alpha(1).withLayer();
if (!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();
}
}
/**
* @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;
}
@Override
public void onDialpadQueryChanged(String 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) {
Log.d(TAG, "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);
}
}
@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 getTelecomManager().isInCall();
}
public static Intent getAddNumberToContactIntent(CharSequence text) {
final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Intents.Insert.PHONE, text);
intent.setType(Contacts.CONTENT_ITEM_TYPE);
return intent;
}
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;
}
@Override
public void showCallHistory() {
// Use explicit CallLogActivity intent instead of ACTION_VIEW +
// CONTENT_TYPE, so that we always open our call log from our dialer
final Intent intent = new Intent(this, CallLogActivity.class);
startActivity(intent);
}
/**
* 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) {
if (mListsFragment.isPaneOpen()) {
mActionBarController.setAlpha(ListsFragment.REMOVE_VIEW_SHOWN_ALPHA);
}
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) {
if (mListsFragment.isPaneOpen()) {
mActionBarController.setAlpha(ListsFragment.REMOVE_VIEW_HIDDEN_ALPHA);
}
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);
}
@Override
public void onPickPhoneNumberAction(Uri dataUri) {
// Specify call-origin so that users will see the previous tab instead of
// CallLog screen (search UI will be automatically exited).
PhoneNumberInteraction.startInteractionForPhoneCall(
DialtactsActivity.this, dataUri, getCallOrigin());
mClearSearchOnPause = true;
}
@Override
public void onCallNumberDirectly(String phoneNumber) {
onCallNumberDirectly(phoneNumber, false /* isVideoCall */);
}
@Override
public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall) {
Intent intent = isVideoCall ?
CallUtil.getVideoCallIntent(phoneNumber, getCallOrigin()) :
CallUtil.getCallIntent(phoneNumber, getCallOrigin());
DialerUtils.startActivityWithErrorToast(this, intent);
mClearSearchOnPause = true;
}
@Override
public void onShortcutIntentCreated(Intent intent) {
Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
}
@Override
public void onHomeInActionBarSelected() {
exitSearchUi();
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
position = mListsFragment.getRtlPosition(position);
// Only scroll the button when the first tab is selected. The button should scroll from
// the middle to right position only on the transition from the first tab to the second
// tab.
// If the app is in RTL mode, we need to check against the second tab, rather than the
// first. This is because if we are scrolling between the first and second tabs, the
// viewpager will report that the starting tab position is 1 rather than 0, due to the
// reversal of the order of the tabs.
final boolean isLayoutRtl = DialerUtils.isRtl();
final boolean shouldScrollButton = position == (isLayoutRtl
? ListsFragment.TAB_INDEX_RECENTS : ListsFragment.TAB_INDEX_SPEED_DIAL);
if (shouldScrollButton && !mIsLandscape) {
mFloatingActionButtonController.onPageScrolled(
isLayoutRtl ? 1 - positionOffset : positionOffset);
} else if (position != ListsFragment.TAB_INDEX_SPEED_DIAL) {
mFloatingActionButtonController.onPageScrolled(1);
}
}
@Override
public void onPageSelected(int position) {
position = mListsFragment.getRtlPosition(position);
mCurrentTabPosition = position;
}
@Override
public void onPageScrollStateChanged(int state) {
}
private TelephonyManager getTelephonyManager() {
return (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
}
private TelecomManager getTelecomManager() {
return (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
}
@Override
public boolean isActionBarShowing() {
return mActionBarController.isActionBarShowing();
}
@Override
public ActionBarController getActionBarController() {
return mActionBarController;
}
public boolean isDialpadShown() {
return mIsDialpadShown;
}
@Override
public int getActionBarHideOffset() {
return getActionBar().getHideOffset();
}
@Override
public void setActionBarHideOffset(int offset) {
getActionBar().setHideOffset(offset);
}
@Override
public int getActionBarHeight() {
return mActionBarHeight;
}
/**
* Updates controller based on currently known information.
*
* @param animate Whether or not to animate the transition.
*/
private void updateFloatingActionButtonControllerAlignment(boolean animate) {
int align = (!mIsLandscape && mCurrentTabPosition == ListsFragment.TAB_INDEX_SPEED_DIAL) ?
FloatingActionButtonController.ALIGN_MIDDLE :
FloatingActionButtonController.ALIGN_END;
mFloatingActionButtonController.align(align, 0 /* offsetX */, 0 /* offsetY */, animate);
}
}