Support horizontal swipe in Phone app
- introduce ViewPager for swipe support
- let DialpadFragment call resolveIntent() by itself
on onCreateView()
- remove ContactsFragment (a.k.a previous "3rd" tab)
- remove resolveIntent() call in onNewIntent()
-- This was introduced to fix bug: 4724464 but we cannot
do same thing anymore, as we cannot assume views in the
Fragment is ready at that moment.
- remove some codes coping with old tab behavior.
-- After having the ViewPager, we cannot simply assume
Fragment is a replacement of previous tab activities.
Fragment creation is delayed by ViewPager, so some of
old codes causes NPE.
Bug: 4974874
Change-Id: I3b2f95b0d34e89bb9665b2014048a634d29e54ce
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
index 445a332..14fb137 100644
--- a/res/layout/dialtacts_activity.xml
+++ b/res/layout/dialtacts_activity.xml
@@ -4,9 +4,9 @@
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.
@@ -18,27 +18,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <!-- Dialer --> -->
- <fragment
- class="com.android.contacts.dialpad.DialpadFragment"
- android:id="@+id/dialpad_fragment"
+ <android.support.v4.view.ViewPager
+ android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <!-- Call Log -->
- <fragment
- class="com.android.contacts.calllog.CallLogFragment"
- android:id="@+id/call_log_fragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- <!-- Favorites -->
- <fragment
- android:id="@+id/favorites_fragment"
- class="com.android.contacts.list.StrequentContactListFragment"
- android:layout_height="match_parent"
- android:layout_width="match_parent" />
-
<!-- For phone search UI -->
<fragment
android:id="@+id/phone_number_picker_fragment"
diff --git a/src/com/android/contacts/activities/DialpadActivity.java b/src/com/android/contacts/activities/DialpadActivity.java
index bb122df..cfe17f3 100644
--- a/src/com/android/contacts/activities/DialpadActivity.java
+++ b/src/com/android/contacts/activities/DialpadActivity.java
@@ -30,7 +30,6 @@
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.Menu;
import android.view.ViewConfiguration;
import android.view.Window;
import android.view.inputmethod.InputMethodManager;
@@ -60,33 +59,12 @@
mFragment = (DialpadFragment) getFragmentManager().findFragmentById(
R.id.dialpad_fragment);
-
- // Manually run the onRestoreInstanceState() sequence here, but only if
- // our intent does *not* have the DialtactsActivity.EXTRA_IGNORE_STATE
- // set (see the references to EXTRA_IGNORE_STATE in DialtactsActivity).
- // TODO: Find a cleaner way of doing this.
- if (!mFragment.resolveIntent() && (icicle != null)) {
- super.onRestoreInstanceState(icicle);
- }
- }
-
- @Override
- protected void onRestoreInstanceState(Bundle icicle) {
- // Do nothing, state is restored in onCreate() if needed
}
@Override
protected void onNewIntent(Intent newIntent) {
setIntent(newIntent);
- mFragment.resolveIntent();
- }
-
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
-
- // Pass this lifecycle event down to the fragment
- mFragment.onPostCreate();
+ mFragment.resolveIntent(newIntent);
}
public DialpadFragment getFragment() {
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index a051dc3..9d1634d 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -21,12 +21,6 @@
import com.android.contacts.dialpad.DialpadFragment;
import com.android.contacts.interactions.ImportExportDialogFragment;
import com.android.contacts.interactions.PhoneNumberInteraction;
-import com.android.contacts.list.ContactListFilter;
-import com.android.contacts.list.ContactsIntentResolver;
-import com.android.contacts.list.ContactsRequest;
-import com.android.contacts.list.DefaultContactBrowseListFragment;
-import com.android.contacts.list.DirectoryListLoader;
-import com.android.contacts.list.OnContactBrowserActionListener;
import com.android.contacts.list.OnPhoneNumberPickerActionListener;
import com.android.contacts.list.PhoneNumberPickerFragment;
import com.android.contacts.list.StrequentContactListFragment;
@@ -53,6 +47,9 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents.UI;
import android.provider.Settings;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
@@ -75,14 +72,13 @@
public class DialtactsActivity extends Activity {
private static final String TAG = "DialtactsActivity";
+ /** Used both by {@link ActionBar} and {@link ViewPagerAdapter} */
private static final int TAB_INDEX_DIALER = 0;
private static final int TAB_INDEX_CALL_LOG = 1;
private static final int TAB_INDEX_FAVORITES = 2;
private static final int TAB_INDEX_COUNT = 3;
- public static final String EXTRA_IGNORE_STATE = "ignore-state";
-
/** Name of the dialtacts shared preferences */
static final String PREFS_DIALTACTS = "dialtacts";
static final boolean PREF_FAVORITES_AS_CONTACTS_DEFAULT = false;
@@ -91,11 +87,62 @@
private static final String PREF_LAST_MANUALLY_SELECTED_TAB = "last_manually_selected_tab";
private static final int PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT = TAB_INDEX_DIALER;
+ /**
+ * Listener interface for Fragments accommodated in {@link ViewPager} enabling them to know
+ * when it becomes visible or invisible inside the ViewPager.
+ */
+ public interface ViewPagerVisibilityListener {
+ public void onVisibilityChange(boolean visible);
+ }
+
+ public class ViewPagerAdapter extends FragmentPagerAdapter {
+ public ViewPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ switch (position) {
+ case TAB_INDEX_DIALER:
+ return mDialpadFragment;
+ case TAB_INDEX_CALL_LOG:
+ return mCallLogFragment;
+ case TAB_INDEX_FAVORITES:
+ return mStrequentFragment;
+ }
+ throw new IllegalStateException("No fragment at position " + position);
+ }
+
+ @Override
+ public int getCount() {
+ return TAB_INDEX_COUNT;
+ }
+ }
+
+ private class PageChangeListener implements OnPageChangeListener {
+ @Override
+ public void onPageScrolled(
+ int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ final ActionBar actionBar = getActionBar();
+ actionBar.selectTab(actionBar.getTabAt(position));
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+ }
+
private String mFilterText;
private Uri mDialUri;
+
+ /** Enables horizontal swipe between Fragments. */
+ private ViewPager mViewPager;
private DialpadFragment mDialpadFragment;
private CallLogFragment mCallLogFragment;
- private DefaultContactBrowseListFragment mContactsFragment;
private StrequentContactListFragment mStrequentFragment;
/**
@@ -186,32 +233,30 @@
setContentView(R.layout.dialtacts_activity);
- final FragmentManager fragmentManager = getFragmentManager();
- mDialpadFragment = (DialpadFragment) fragmentManager
- .findFragmentById(R.id.dialpad_fragment);
+ // Instantiate Fragments which ViewPager will accommodate. At this point they aren't
+ // attached to any Activity, so no Views inside them will be ready yet.
+ mDialpadFragment = new DialpadFragment();
mDialpadFragment.setListener(new DialpadFragment.Listener() {
@Override
public void onSearchButtonPressed() {
enterSearchUi();
}
});
- mCallLogFragment = (CallLogFragment) fragmentManager
- .findFragmentById(R.id.call_log_fragment);
- mContactsFragment = (DefaultContactBrowseListFragment) fragmentManager
- .findFragmentById(R.id.contacts_fragment);
- mStrequentFragment = (StrequentContactListFragment) fragmentManager
- .findFragmentById(R.id.favorites_fragment);
- mPhoneNumberPickerFragment = (PhoneNumberPickerFragment) fragmentManager
+ mCallLogFragment = new CallLogFragment();
+ mStrequentFragment = new StrequentContactListFragment();
+
+ mViewPager = (ViewPager) findViewById(R.id.pager);
+ mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
+ mViewPager.setOnPageChangeListener(new PageChangeListener());
+
+ // This Fragment is _not_ maintained by ViewPager.
+ mPhoneNumberPickerFragment = (PhoneNumberPickerFragment) getFragmentManager()
.findFragmentById(R.id.phone_number_picker_fragment);
mPhoneNumberPickerFragment.setOnPhoneNumberPickerActionListener(
mPhoneNumberPickerActionListener);
mPhoneNumberPickerFragment.setHighlightSearchPrefix(true);
- // Hide all tabs (the current tab will later be reshown once a tab is selected)
- final FragmentTransaction transaction = fragmentManager.beginTransaction();
- transaction.hide(mDialpadFragment);
- transaction.hide(mCallLogFragment);
- transaction.hide(mStrequentFragment);
+ final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.hide(mPhoneNumberPickerFragment);
transaction.commit();
@@ -272,7 +317,6 @@
tab.setTabListener(new TabChangeListener(mDialpadFragment));
tab.setIcon(R.drawable.ic_tab_dialer);
getActionBar().addTab(tab);
- mDialpadFragment.resolveIntent();
}
private void setupCallLog() {
@@ -283,32 +327,6 @@
getActionBar().addTab(tab);
}
- private void setupContacts() {
- final Tab tab = getActionBar().newTab();
- tab.setText(""); // R.string.contactsIconLabel
- tab.setIcon(R.drawable.ic_tab_contacts);
- tab.setTabListener(new TabChangeListener(mContactsFragment));
- getActionBar().addTab(tab);
-
- // TODO: We should not artificially create Intents and put them into the Fragment.
- // It would be nicer to directly pass in the UI constant
- Intent intent = new Intent(UI.LIST_ALL_CONTACTS_ACTION);
- intent.setClass(this, PeopleActivity.class);
-
- ContactsIntentResolver resolver = new ContactsIntentResolver(this);
- ContactsRequest request = resolver.resolveIntent(intent);
- final ContactListFilter filter = ContactListFilter.createFilterWithType(
- ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
- mContactsFragment.setFilter(filter, false);
- mContactsFragment.setSearchMode(request.isSearchMode());
- mContactsFragment.setQueryString(request.getQueryString(), false);
- mContactsFragment.setContactsRequest(request);
- mContactsFragment.setDirectorySearchMode(request.isDirectorySearchEnabled()
- ? DirectoryListLoader.SEARCH_MODE_DEFAULT
- : DirectoryListLoader.SEARCH_MODE_NONE);
- mContactsFragment.setOnContactListActionListener(mListFragmentListener);
- }
-
private void setupFavorites() {
final Tab tab = getActionBar().newTab();
tab.setText(""); // R.string.contactsFavoritesLabel
@@ -356,28 +374,23 @@
return;
}
- // Tell the children activities that they should ignore any possible saved
- // state and instead reload their state from the parent's intent
- intent.putExtra(EXTRA_IGNORE_STATE, true);
-
// Remember the old manually selected tab index so that it can be restored if it is
// overwritten by one of the programmatic tab selections
final int savedTabIndex = mLastManuallySelectedTab;
- if (DialpadFragment.phoneIsInUse()) {
- getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_DIALER));
+ final int tabIndex;
+ if (DialpadFragment.phoneIsInUse() || isDialIntent(intent)) {
+ tabIndex = TAB_INDEX_DIALER;
} else if (recentCallsRequest) {
- getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_CALL_LOG));
+ tabIndex = TAB_INDEX_CALL_LOG;
} else {
- getActionBar().selectTab(getActionBar().getTabAt(mLastManuallySelectedTab));
+ tabIndex = mLastManuallySelectedTab;
}
+ mViewPager.setCurrentItem(tabIndex);
+ getActionBar().selectTab(getActionBar().getTabAt(tabIndex));
// Restore to the previous manual selection
mLastManuallySelectedTab = savedTabIndex;
-
- // Tell the children activities that they should honor their saved states
- // instead of the state from the parent's intent
- intent.putExtra(EXTRA_IGNORE_STATE, false);
}
@Override
@@ -391,8 +404,9 @@
} else if (isDialIntent(newIntent)) {
setupDialUri(newIntent);
}
- // Fill in a phone number again.
- mDialpadFragment.resolveIntent();
+ if (mPhoneNumberPickerFragment.isVisible()) {
+ exitSearchUi();
+ }
}
/** Returns true if the given intent contains a phone number to populate the dialer with */
@@ -484,14 +498,6 @@
}
}
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
-
- // Pass this lifecycle event down to the fragment
- mDialpadFragment.onPostCreate();
- }
-
/**
* Tab change listener that is instantiated once for each tab. Handles showing/hiding tabs
* and remembers manual tab selections
@@ -505,12 +511,19 @@
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
- ft.hide(mFragment);
+ if (mFragment instanceof ViewPagerVisibilityListener) {
+ ((ViewPagerVisibilityListener) mFragment).onVisibilityChange(false);
+ }
}
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
- ft.show(mFragment);
+ if (mFragment instanceof ViewPagerVisibilityListener) {
+ ((ViewPagerVisibilityListener) mFragment).onVisibilityChange(true);
+ }
+ if (mViewPager.getCurrentItem() != tab.getPosition()) {
+ mViewPager.setCurrentItem(tab.getPosition(), false /* smoothScroll */);
+ }
ft.hide(mPhoneNumberPickerFragment);
// During the call, we don't remember the tab position.
@@ -527,56 +540,6 @@
}
}
- private OnContactBrowserActionListener mListFragmentListener =
- new OnContactBrowserActionListener() {
- @Override
- public void onViewContactAction(Uri contactLookupUri) {
- startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri));
- }
-
- @Override
- public void onSmsContactAction(Uri contactUri) {
- }
-
- @Override
- public void onSelectionChange() {
- }
-
- @Override
- public void onRemoveFromFavoritesAction(Uri contactUri) {
- }
-
- @Override
- public void onInvalidSelection() {
- }
-
- @Override
- public void onFinishAction() {
- }
-
- @Override
- public void onEditContactAction(Uri contactLookupUri) {
- }
-
- @Override
- public void onDeleteContactAction(Uri contactUri) {
- }
-
- @Override
- public void onCreateNewContactAction() {
- }
-
- @Override
- public void onCallContactAction(Uri contactUri) {
- PhoneNumberInteraction.startInteractionForPhoneCall(
- DialtactsActivity.this, contactUri);
- }
-
- @Override
- public void onAddToFavoritesAction(Uri contactUri) {
- }
- };
-
private StrequentContactListFragment.Listener mStrequentListener =
new StrequentContactListFragment.Listener() {
@Override
@@ -702,10 +665,8 @@
// Show the search fragment and hide everything else.
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.show(mPhoneNumberPickerFragment);
- transaction.hide(mDialpadFragment);
- transaction.hide(mCallLogFragment);
- transaction.hide(mStrequentFragment);
transaction.commit();
+ mViewPager.setVisibility(View.GONE);
mInSearchUi = true;
}
@@ -728,6 +689,12 @@
actionBar.setDisplayShowCustomEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.hide(mPhoneNumberPickerFragment);
+ transaction.commit();
+
+ mViewPager.setVisibility(View.VISIBLE);
+
// Request to update option menu.
invalidateOptionsMenu();
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 31373cc..29434a0 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -21,6 +21,7 @@
import com.android.contacts.ContactPhotoManager;
import com.android.contacts.ContactsUtils;
import com.android.contacts.R;
+import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
import com.android.contacts.util.ExpirableCache;
import com.android.internal.telephony.CallerInfo;
import com.google.common.annotations.VisibleForTesting;
@@ -75,7 +76,7 @@
* Displays a list of call log entries.
*/
public class CallLogFragment extends ListFragment
- implements View.OnCreateContextMenuListener {
+ implements View.OnCreateContextMenuListener, ViewPagerVisibilityListener {
private static final String TAG = "CallLogFragment";
/**
@@ -144,6 +145,9 @@
private String mCurrentCountryIso;
private boolean mScrollToTop;
+ private MenuItem mDeleteAllCallLogMenuItem;
+ private boolean mShowMenu;
+
public static final class ContactInfo {
public long personId;
public String name;
@@ -902,8 +906,14 @@
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- menu.add(0, OptionsMenuItems.DELETE_ALL, 0, R.string.recentCalls_deleteAll).setIcon(
- android.R.drawable.ic_menu_close_clear_cancel);
+ mDeleteAllCallLogMenuItem = menu.add(0, OptionsMenuItems.DELETE_ALL,
+ 0, R.string.recentCalls_deleteAll)
+ .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ mDeleteAllCallLogMenuItem.setVisible(mShowMenu);
}
@Override
@@ -1135,4 +1145,9 @@
public String getVoiceMailNumber() {
return mVoiceMailNumber;
}
+
+ @Override
+ public void onVisibilityChange(boolean visible) {
+ mShowMenu = visible;
+ }
}
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 2097a1d..70dd4b1 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -20,6 +20,7 @@
import com.android.contacts.R;
import com.android.contacts.SpecialCharSequenceMgr;
import com.android.contacts.activities.DialtactsActivity;
+import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
import com.android.internal.telephony.ITelephony;
import com.android.phone.CallLogAsync;
import com.android.phone.HapticFeedback;
@@ -73,7 +74,8 @@
public class DialpadFragment extends Fragment
implements View.OnClickListener,
View.OnLongClickListener, View.OnKeyListener,
- AdapterView.OnItemClickListener, TextWatcher {
+ AdapterView.OnItemClickListener, TextWatcher,
+ ViewPagerVisibilityListener {
private static final String TAG = "DialpadFragment";
private static final String EMPTY_NUMBER = "";
@@ -107,7 +109,8 @@
private View mDialButton;
private ListView mDialpadChooser;
private DialpadChooserAdapter mDialpadChooserAdapter;
- //Member variables for dialpad options
+
+ // Member variables for dialpad options
private MenuItem m2SecPauseMenuItem;
private MenuItem mWaitMenuItem;
private MenuItem mCallSettingsItem;
@@ -116,6 +119,8 @@
private static final int MENU_WAIT = 3;
private static final int MENU_CALL_SETTINGS = 4;
+ private boolean mShowMenu;
+
private boolean mHasVoicemail = false;
// Last number dialed, retrieved asynchronously from the call DB
@@ -231,6 +236,7 @@
mDigits.setKeyListener(DialerKeyListener.getInstance());
mDigits.setOnClickListener(this);
mDigits.setOnKeyListener(this);
+ mDigits.addTextChangedListener(this);
maybeAddNumberFormatting();
@@ -278,6 +284,8 @@
mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser);
mDialpadChooser.setOnItemClickListener(this);
+ resolveIntent(getActivity().getIntent());
+
return fragmentView;
}
@@ -301,19 +309,9 @@
* any possible saved state, and instead reset our state based on the parent's
* intent.
*/
- public boolean resolveIntent() {
+ public boolean resolveIntent(Intent intent) {
boolean ignoreState = false;
- // Find the proper intent
- final Intent intent;
- if (getActivity().isChild()) {
- intent = getActivity().getParent().getIntent();
- ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
- } else {
- intent = getActivity().getIntent();
- }
- // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
-
// by default we are not adding a call.
mIsAddCallMode = false;
@@ -420,16 +418,6 @@
fragmentView.findViewById(R.id.pound).setOnClickListener(this);
}
- // Do some stuff that needs to happen only once, but which we
- // can't do directly from onCreate().
- public void onPostCreate() {
- // This can't be done in onCreate(), since the auto-restoring of the digits
- // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
- // is called. This method will be called every time the activity is created, and
- // will always happen after onRestoreSavedInstanceState().
- mDigits.addTextChangedListener(this);
- }
-
@Override
public void onResume() {
super.onResume();
@@ -462,13 +450,13 @@
}
}
- Activity parent = getActivity().getParent();
+ Activity parent = getActivity();
// See if we were invoked with a DIAL intent. If we were, fill in the appropriate
// digits in the dialer field.
- if (parent != null && parent instanceof DialtactsActivity) {
+ if (parent instanceof DialtactsActivity) {
Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
if (dialUri != null) {
- resolveIntent();
+ resolveIntent(parent.getIntent());
}
}
@@ -543,7 +531,14 @@
return;
}
- // We show "Call Settings" menu every time
+ if (!mShowMenu) {
+ mCallSettingsItem.setVisible(false);
+ mAddToContactMenuItem.setVisible(false);
+ m2SecPauseMenuItem.setVisible(false);
+ mWaitMenuItem.setVisible(false);
+ return;
+ }
+
mCallSettingsItem.setVisible(true);
Intent settingsIntent = new Intent(Intent.ACTION_MAIN);
settingsIntent.setClassName("com.android.phone", "com.android.phone.CallFeaturesSetting");
@@ -1237,4 +1232,9 @@
public void setListener(Listener listener) {
mListener = listener;
}
+
+ @Override
+ public void onVisibilityChange(boolean visible) {
+ mShowMenu = visible;
+ }
}