Automatically selecting first found contact.
Change-Id: I232f37d1b5256c315d514a2c8dee9e9eeca5dcb7
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index dbdb1f0..89cace9 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -48,12 +48,9 @@
private static final String EXTRA_KEY_QUERY = "navBar.query";
private static final String KEY_MODE_DEFAULT = "mode_default";
- private static final String KEY_MODE_SEARCH = "mode_search";
private boolean mSearchMode;
private String mQueryString;
- private Bundle mSavedStateForSearchMode;
- private Bundle mSavedStateForDefaultMode;
private View mNavigationBar;
private TextView mSearchLabel;
@@ -76,8 +73,6 @@
if (savedState != null) {
mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
mQueryString = savedState.getString(EXTRA_KEY_QUERY);
- mSavedStateForDefaultMode = savedState.getParcelable(KEY_MODE_DEFAULT);
- mSavedStateForSearchMode = savedState.getParcelable(KEY_MODE_SEARCH);
} else {
mSearchMode = request.isSearchMode();
mQueryString = request.getQueryString();
@@ -113,9 +108,21 @@
@Override
public void onFocusChange(View v, boolean hasFocus) {
- if (v == mSearchView) {
- setSearchMode(hasFocus);
+ if (v != mSearchView) {
+ return;
}
+
+ // When we switch search mode on/off, the activity may need to change
+ // fragments, which may lead to focus temporarily leaving the search
+ // view or coming back to it, which could lead to an infinite loop.
+ // Postponing the change breaks that loop.
+ mNavigationBar.post(new Runnable() {
+
+ @Override
+ public void run() {
+ setSearchMode(mSearchView.hasFocus());
+ }
+ });
}
public boolean isSearchMode() {
@@ -195,31 +202,9 @@
return false;
}
- public Bundle getSavedStateForSearchMode() {
- return mSavedStateForSearchMode;
- }
-
- public void setSavedStateForSearchMode(Bundle state) {
- mSavedStateForSearchMode = state;
- }
-
- public Bundle getSavedStateForDefaultMode() {
- return mSavedStateForDefaultMode;
- }
-
- public void setSavedStateForDefaultMode(Bundle state) {
- mSavedStateForDefaultMode = state;
- }
-
public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
outState.putString(EXTRA_KEY_QUERY, mQueryString);
- if (mSavedStateForDefaultMode != null) {
- outState.putParcelable(KEY_MODE_DEFAULT, mSavedStateForDefaultMode);
- }
- if (mSavedStateForSearchMode != null) {
- outState.putParcelable(KEY_MODE_SEARCH, mSavedStateForSearchMode);
- }
}
@Override
diff --git a/src/com/android/contacts/activities/ContactBrowserActivity.java b/src/com/android/contacts/activities/ContactBrowserActivity.java
index b04d640..aa8b80d 100644
--- a/src/com/android/contacts/activities/ContactBrowserActivity.java
+++ b/src/com/android/contacts/activities/ContactBrowserActivity.java
@@ -51,6 +51,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
@@ -90,6 +91,23 @@
private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
+ /**
+ * The id for a delayed message that triggers automatic selection of the first
+ * found contact in search mode.
+ */
+ private static final int MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT = 1;
+
+ /**
+ * The delay that is used for automatically selecting the first found contact.
+ */
+ private static final int DELAY_AUTOSELECT_FIRST_FOUND_CONTACT_MILLIS = 500;
+
+ /**
+ * The minimum number of characters in the search query that is required
+ * before we automatically select the first found contact.
+ */
+ private static final int AUTOSELECT_FIRST_FOUND_CONTACT_MIN_QUERY_LENGTH = 2;
+
private static final String KEY_SEARCH_MODE = "searchMode";
private DialogManager mDialogManager = new DialogManager(this);
@@ -128,17 +146,26 @@
mContactListFilterController.addListener(this);
}
+ private Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT) {
+ selectFirstFoundContact();
+ }
+ }
+ };
+ }
+ return mHandler;
+ }
+
@Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof ContactBrowseListFragment) {
mListFragment = (ContactBrowseListFragment)fragment;
mListFragment.setOnContactListActionListener(new ContactBrowserActionListener());
- if (mListFragment instanceof DefaultContactBrowseListFragment
- && mContactListFilterController != null
- && mContactListFilterController.isLoaded()) {
- ((DefaultContactBrowseListFragment) mListFragment).setFilter(
- mContactListFilterController.getFilter());
- }
+ restoreListSelection();
} else if (fragment instanceof ContactDetailFragment) {
mDetailFragment = (ContactDetailFragment)fragment;
mDetailFragment.setListener(mDetailFragmentListener);
@@ -188,7 +215,6 @@
if (mHasActionBar) {
mActionBarAdapter = new ActionBarAdapter(this);
mActionBarAdapter.onCreate(savedState, mRequest, getActionBar());
- mActionBarAdapter.setListener(this);
mActionBarAdapter.setContactListFilterController(mContactListFilterController);
// TODO: request may ask for FREQUENT - set the filter accordingly
mAddContactImageView = new ImageView(this);
@@ -221,11 +247,9 @@
}
if (mHasActionBar) {
- if (mActionBarAdapter.isSearchMode()) {
- mActionBarAdapter.setSavedStateForSearchMode(null);
- mActionBarAdapter.setSearchMode(false);
- }
+ mActionBarAdapter.setSearchMode(false);
}
+
mListFragment.setSelectedContactUri(uri);
mListFragment.requestSelectionOnScreen(true);
if (mContactContentDisplayed) {
@@ -235,6 +259,22 @@
}
@Override
+ protected void onPause() {
+ if (mActionBarAdapter != null) {
+ mActionBarAdapter.setListener(null);
+ }
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mActionBarAdapter != null) {
+ mActionBarAdapter.setListener(this);
+ }
+ }
+
+ @Override
protected void onStart() {
if (mContactListFilterController != null) {
mContactListFilterController.startLoading();
@@ -284,28 +324,16 @@
mSearchMode = searchMode;
if (mSearchMode) {
mListFragment = createContactSearchFragment();
+ // When switching to the search mode, erase previous state of the search UI
+ mListFragment.saveSelectedUri(mPrefs);
} else {
mListFragment = createListFragment(ContactsRequest.ACTION_DEFAULT);
mListFragment.requestSelectionOnScreen(false);
}
}
- if (mHasActionBar) {
- if (mSearchMode) {
- Bundle savedState = mActionBarAdapter.getSavedStateForSearchMode();
- if (savedState != null) {
- mListFragment.restoreSavedState(savedState);
- mActionBarAdapter.setSavedStateForSearchMode(null);
- }
-
- mListFragment.setQueryString(mActionBarAdapter.getQueryString());
- } else {
- Bundle savedState = mActionBarAdapter.getSavedStateForDefaultMode();
- if (savedState != null) {
- mListFragment.restoreSavedState(savedState);
- mActionBarAdapter.setSavedStateForDefaultMode(null);
- }
- }
+ if (mHasActionBar && mSearchMode) {
+ mListFragment.setQueryString(mActionBarAdapter.getQueryString());
}
if (fromRequest) {
@@ -320,37 +348,26 @@
getFragmentManager().openTransaction()
.replace(R.id.list_container, mListFragment)
.commit();
+
+ if (mContactContentDisplayed) {
+ setupContactDetailFragment(mListFragment.getSelectedContactUri());
+ }
}
}
private void closeListFragment() {
if (mListFragment != null) {
mListFragment.setOnContactListActionListener(null);
-
- if (mHasActionBar) {
- Bundle state = new Bundle();
- mListFragment.onSaveInstanceState(state);
- if (mSearchMode) {
- mActionBarAdapter.setSavedStateForSearchMode(state);
- } else {
- mActionBarAdapter.setSavedStateForDefaultMode(state);
- }
- }
-
mListFragment = null;
}
}
@Override
public void onContactListFiltersLoaded() {
- if (mListFragment instanceof DefaultContactBrowseListFragment) {
- DefaultContactBrowseListFragment fragment =
- (DefaultContactBrowseListFragment) mListFragment;
- restoreListSelection(fragment);
+ restoreListSelection();
- // Filters have been loaded - now we can start loading the list itself
- fragment.startLoading();
- }
+ // Filters have been loaded - now we can start loading the list itself
+ mListFragment.startLoading();
}
@Override
@@ -359,25 +376,35 @@
// because the user has explicitly changed the filter.
mRequest.setContactUri(null);
- if (mListFragment instanceof DefaultContactBrowseListFragment) {
- DefaultContactBrowseListFragment fragment =
- (DefaultContactBrowseListFragment) mListFragment;
- restoreListSelection(fragment);
-
- fragment.reloadData();
- }
+ restoreListSelection();
+ mListFragment.reloadData();
}
- private void restoreListSelection(DefaultContactBrowseListFragment fragment) {
- fragment.setFilter(mContactListFilterController.getFilter());
- fragment.restoreSelectedUri(mPrefs);
- fragment.requestSelectionOnScreen(false);
+ /**
+ * Restores filter-specific persistent selection.
+ */
+ private void restoreListSelection() {
+ if (mListFragment instanceof DefaultContactBrowseListFragment
+ && mContactListFilterController != null
+ && mContactListFilterController.isLoaded()) {
+ DefaultContactBrowseListFragment fragment =
+ (DefaultContactBrowseListFragment) mListFragment;
+ fragment.setFilter(mContactListFilterController.getFilter());
+ fragment.restoreSelectedUri(mPrefs);
+ fragment.requestSelectionOnScreen(false);
+ }
+
if (mContactContentDisplayed) {
- setupContactDetailFragment(fragment.getSelectedContactUri());
+ setupContactDetailFragment(mListFragment.getSelectedContactUri());
}
}
private void showDefaultSelection() {
+ if (mSearchMode) {
+ selectFirstFoundContactAfterDelay();
+ return;
+ }
+
Uri requestedContactUri = mRequest.getContactUri();
if (requestedContactUri != null
&& mListFragment instanceof DefaultContactBrowseListFragment) {
@@ -385,8 +412,8 @@
// that the requested selection is unconditionally visible.
DefaultContactBrowseListFragment fragment =
(DefaultContactBrowseListFragment) mListFragment;
- ContactListFilter filter = new ContactListFilter(
- ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
+ ContactListFilter filter =
+ new ContactListFilter(ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
fragment.setFilter(filter);
fragment.setSelectedContactUri(requestedContactUri);
fragment.saveSelectedUri(mPrefs);
@@ -407,6 +434,39 @@
}
}
+ /**
+ * Automatically selects the first found contact in search mode. The selection
+ * is updated after a delay to allow the user to type without to much UI churn
+ * and to save bandwidth on directory queries.
+ */
+ public void selectFirstFoundContactAfterDelay() {
+ Handler handler = getHandler();
+ handler.removeMessages(MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT);
+ handler.sendEmptyMessageDelayed(MESSAGE_AUTOSELECT_FIRST_FOUND_CONTACT,
+ DELAY_AUTOSELECT_FIRST_FOUND_CONTACT_MILLIS);
+ }
+
+ /**
+ * Selects the first contact in the list in search mode.
+ */
+ protected void selectFirstFoundContact() {
+ if (!mSearchMode) {
+ return;
+ }
+
+ Uri selectedUri = null;
+ String queryString = mListFragment.getQueryString();
+ if (queryString != null
+ && queryString.length() >= AUTOSELECT_FIRST_FOUND_CONTACT_MIN_QUERY_LENGTH) {
+ selectedUri = mListFragment.getFirstContactUri();
+ }
+
+ mListFragment.setSelectedContactUri(selectedUri);
+ if (mContactContentDisplayed) {
+ setupContactDetailFragment(selectedUri);
+ }
+ }
+
@Override
public void onContactListFilterCustomizationRequest() {
startActivityForResult(new Intent(this, CustomContactListFilterActivity.class),
@@ -419,18 +479,14 @@
return;
}
- // Already showing? Nothing to do
- if (mDetailFragment != null) {
- mDetailFragment.loadUri(contactLookupUri);
- return;
+ if (mDetailFragment == null) {
+ mDetailFragment = new ContactDetailFragment();
+ getFragmentManager().openTransaction()
+ .replace(R.id.detail_container, mDetailFragment)
+ .commit();
}
- mDetailFragment = new ContactDetailFragment();
mDetailFragment.loadUri(contactLookupUri);
-
- getFragmentManager().openTransaction()
- .replace(R.id.detail_container, mDetailFragment)
- .commit();
}
/**
@@ -439,7 +495,6 @@
@Override
public void onAction() {
configureListFragment(false /* from request */);
- setupContactDetailFragment(mListFragment.getSelectedContactUri());
}
/**
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 5a81f6a..ac4a2a7 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -146,7 +146,7 @@
}
public void setSelectedContactUri(Uri uri) {
- if (mSelectedContactUri == null
+ if ((mSelectedContactUri == null && uri != null)
|| (mSelectedContactUri != null && !mSelectedContactUri.equals(uri))) {
mSelectedContactUri = uri;
@@ -204,21 +204,21 @@
}
@Override
- protected void onPartitionLoaded(int partitionIndex, Cursor data) {
- super.onPartitionLoaded(partitionIndex, data);
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ super.onLoadFinished(loader, data);
checkSelection();
}
private void checkSelection() {
- if (mSelectionVerified || isSearchMode()) {
+ if (mSelectionVerified) {
+ return;
+ }
+
+ if (isLoading()) {
return;
}
ContactListAdapter adapter = getAdapter();
- if (adapter.isLoading() || mLoadingLookupKey) {
- return;
- }
-
if (adapter.hasValidSelection()) {
mSelectionVerified = true;
requestSelectionOnScreenIfNeeded();
@@ -228,13 +228,18 @@
notifyInvalidSelection();
}
+ @Override
+ public boolean isLoading() {
+ return mLoadingLookupKey || super.isLoading();
+ }
+
public Uri getFirstContactUri() {
ContactListAdapter adapter = getAdapter();
return adapter.getFirstContactUri();
}
@Override
- protected void startLoading() {
+ public void startLoading() {
mSelectionVerified = false;
super.startLoading();
}
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index cf03026..41136d4 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -126,7 +126,7 @@
}
@Override
- public void clearPartitions() {
+ public void clearPartitions() {
int count = getPartitionCount();
for (int i = 0; i < count; i++) {
Partition partition = getPartition(i);
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index a4f4b72..fe80c3c 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -135,7 +135,12 @@
private int mProviderStatus = ProviderStatus.STATUS_NORMAL;
private boolean mForceLoad;
- private boolean mLoadDirectoryList;
+
+ private static final int STATUS_NOT_LOADED = 0;
+ private static final int STATUS_LOADING = 1;
+ private static final int STATUS_LOADED = 2;
+
+ private int mDirectoryListStatus = STATUS_NOT_LOADED;
/**
* Indicates whether we are doing the initial complete load of data (false) or
@@ -293,7 +298,7 @@
}
mForceLoad = false;
- mLoadDirectoryList = true;
+ mDirectoryListStatus = STATUS_NOT_LOADED;
mLoadPriorityDirectoriesOnly = true;
startLoading();
@@ -393,6 +398,7 @@
int loaderId = loader.getId();
if (loaderId == DIRECTORY_LOADER_ID) {
+ mDirectoryListStatus = STATUS_LOADED;
mAdapter.changeDirectories(data);
startLoading();
} else {
@@ -400,8 +406,8 @@
if (isSearchMode()) {
int directorySearchMode = getDirectorySearchMode();
if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {
- if (mLoadDirectoryList) {
- mLoadDirectoryList = false;
+ if (mDirectoryListStatus == STATUS_NOT_LOADED) {
+ mDirectoryListStatus = STATUS_LOADING;
getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
} else {
startLoading();
@@ -423,8 +429,23 @@
mAizy.setIndexer(mAdapter.getIndexer(), data.getCount());
}
- // TODO should probably only restore instance state after all directories are loaded
- completeRestoreInstanceState();
+ if (!isLoading()) {
+ completeRestoreInstanceState();
+ }
+ }
+
+ public boolean isLoading() {
+ if (mAdapter != null && mAdapter.isLoading()) {
+ return true;
+ }
+
+ if (isSearchMode() && getDirectorySearchMode() != DirectoryListLoader.SEARCH_MODE_NONE
+ && (mDirectoryListStatus == STATUS_NOT_LOADED
+ || mDirectoryListStatus == STATUS_LOADING)) {
+ return true;
+ }
+
+ return false;
}
@Override
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 7729e28..919c740 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -39,6 +39,7 @@
private static final String KEY_FILTER_ENABLED = "filterEnabled";
private static final String PERSISTENT_SELECTION_PREFIX = "defaultContactBrowserSelection";
+ private static final String KEY_SEARCH_MODE_CONTACT_URI_SUFFIX = "search";
private View mCounterHeaderView;
private View mSearchHeaderView;
@@ -190,10 +191,6 @@
@Override
public void saveSelectedUri(SharedPreferences preferences) {
- if (isSearchMode()) {
- return;
- }
-
Editor editor = preferences.edit();
Uri uri = getSelectedContactUri();
if (uri == null) {
@@ -206,10 +203,6 @@
@Override
public void restoreSelectedUri(SharedPreferences preferences) {
- if (isSearchMode()) {
- return;
- }
-
String selectedUri = preferences.getString(getPersistentSelectionKey(), null);
if (selectedUri == null) {
setSelectedContactUri(null);
@@ -219,7 +212,9 @@
}
private String getPersistentSelectionKey() {
- if (mFilter == null) {
+ if (isSearchMode()) {
+ return mPersistentSelectionPrefix + "-" + KEY_SEARCH_MODE_CONTACT_URI_SUFFIX;
+ } else if (mFilter == null) {
return mPersistentSelectionPrefix;
} else {
return mPersistentSelectionPrefix + "-" + mFilter.getId();
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index d5368dc..fc9e78d 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -112,6 +112,8 @@
private static final int LOADER_DETAILS = 1;
+ private static final String KEY_CONTACT_URI = "contactUri";
+
private Context mContext;
private View mView;
private Uri mLookupUri;
@@ -184,6 +186,20 @@
}
@Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mLookupUri = savedInstanceState.getParcelable(KEY_CONTACT_URI);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(KEY_CONTACT_URI, mLookupUri);
+ }
+
+ @Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mContext = activity;