Show unsynced groups clearly in separate item, don't annoy.
This change adds a new "More groups..." list item under the
expandable list for a data source when it has any unsynced
groups, making it more discoverable for users to bring back
unsynced groups. This fixes http://b/2084771
Also cleans up lingering issues so we show unsynced groups
from the correct accounts, and only prompt the user once
about removing "ungrouped." Fixes http://b/2114723
diff --git a/res/values/strings.xml b/res/values/strings.xml
index aeea931..c9d8603 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -934,7 +934,8 @@
<string name="dialog_new_contact_account">Create contact under account</string>
<string name="menu_sync_remove">Remove sync group</string>
- <string name="menu_sync_add">Add sync group</string>
+ <string name="dialog_sync_add">Add sync group</string>
+ <string name="display_more_groups">More groups\u2026</string>
<!-- List title for a special contacts group that covers all contacts that
aren't members of any other group. -->
diff --git a/src/com/android/contacts/ui/DisplayGroupsActivity.java b/src/com/android/contacts/ui/DisplayGroupsActivity.java
index c358609..ffe423f 100644
--- a/src/com/android/contacts/ui/DisplayGroupsActivity.java
+++ b/src/com/android/contacts/ui/DisplayGroupsActivity.java
@@ -71,6 +71,9 @@
private static final String TAG = "DisplayGroupsActivity";
private static final int UNGROUPED_ID = -2;
+ private static final int UNSYNCED_ID = -3;
+
+ private static final int FOOTER_ENTRY = -4;
public interface Prefs {
public static final String DISPLAY_ONLY_PHONES = "only_phones";
@@ -251,13 +254,7 @@
final ContentValues values = new ContentValues();
// TODO: heavy update, perhaps push to background query
- if (id != UNGROUPED_ID) {
- // Handle persisting for normal group
- values.put(Groups.GROUP_VISIBLE, checkbox.isChecked() ? 1 : 0);
-
- final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, id);
- final int count = resolver.update(groupUri, values, null, null);
- } else {
+ if (id == UNGROUPED_ID) {
// Handle persisting for ungrouped through Settings
values.put(Settings.UNGROUPED_VISIBLE, checkbox.isChecked() ? 1 : 0);
@@ -267,6 +264,15 @@
settings.getString(SettingsQuery.ACCOUNT_NAME),
settings.getString(SettingsQuery.ACCOUNT_TYPE)
});
+ } else if (id == UNSYNCED_ID) {
+ // Open context menu for bringing back unsynced
+ this.openContextMenu(v);
+ } else {
+ // Handle persisting for normal group
+ values.put(Groups.GROUP_VISIBLE, checkbox.isChecked() ? 1 : 0);
+
+ final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, id);
+ final int count = resolver.update(groupUri, values, null, null);
}
return true;
@@ -300,19 +306,23 @@
final String accountType = groupCursor.getString(SettingsQuery.ACCOUNT_TYPE);
final Account account = new Account(accountName, accountType);
- if (childPosition == -1) {
+ final boolean shouldSyncUngrouped = groupCursor.getInt(SettingsQuery.SHOULD_SYNC) != 0;
+ final boolean anyUnsynced = groupCursor.getInt(SettingsQuery.ANY_UNSYNCED) != 0;
+ final boolean lastChild = (childPosition == (mAdapter.getChildrenCount(groupPosition) - 1));
+
+ if (anyUnsynced && lastChild) {
// Show add dialog for this overall source
showAddSync(menu, groupCursor, account, syncMode);
- } else {
+ } else if (childPosition != -1) {
// Show remove dialog for this specific group
final Cursor childCursor = mAdapter.getChild(groupPosition, childPosition);
- showRemoveSync(menu, account, childCursor, syncMode);
+ showRemoveSync(menu, account, childCursor, syncMode, shouldSyncUngrouped);
}
}
protected void showRemoveSync(ContextMenu menu, final Account account, Cursor childCursor,
- final int syncMode) {
+ final int syncMode, final boolean shouldSyncUngrouped) {
final long groupId = childCursor.getLong(GroupsQuery._ID);
final CharSequence title = getGroupTitle(this, childCursor);
@@ -320,19 +330,20 @@
menu.add(R.string.menu_sync_remove).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
- handleRemoveSync(groupId, account, syncMode, title);
+ handleRemoveSync(groupId, account, syncMode, title, shouldSyncUngrouped);
return true;
}
});
}
protected void handleRemoveSync(final long groupId, final Account account, final int syncMode,
- CharSequence title) {
- if (syncMode == SYNC_MODE_EVERYTHING && groupId != UNGROUPED_ID) {
+ CharSequence title, boolean shouldSyncUngrouped) {
+ if (syncMode == SYNC_MODE_EVERYTHING && groupId != UNGROUPED_ID && shouldSyncUngrouped) {
// Warn before removing this group when it would cause ungrouped to stop syncing
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
final CharSequence removeMessage = this.getString(
R.string.display_warn_remove_ungrouped, title);
+ builder.setTitle(R.string.menu_sync_remove);
builder.setMessage(removeMessage);
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -348,8 +359,9 @@
}
}
- protected void showAddSync(ContextMenu menu, Cursor groupCursor, final Account account, final int syncMode) {
- menu.setHeaderTitle(R.string.menu_sync_add);
+ protected void showAddSync(ContextMenu menu, Cursor groupCursor, final Account account,
+ final int syncMode) {
+ menu.setHeaderTitle(R.string.dialog_sync_add);
// Create single "Ungrouped" item when not synced
final boolean ungroupedAvailable = groupCursor.getInt(SettingsQuery.SHOULD_SYNC) == 0;
@@ -366,7 +378,11 @@
// Create item for each available, unsynced group
final Cursor availableGroups = this.managedQuery(Groups.CONTENT_SUMMARY_URI,
- GroupsQuery.PROJECTION, Groups.SHOULD_SYNC + "=0", null);
+ GroupsQuery.PROJECTION, Groups.SHOULD_SYNC + "=0 AND " + Groups.ACCOUNT_NAME
+ + "=? AND " + Groups.ACCOUNT_TYPE + "=?", new String[] {
+ groupCursor.getString(SettingsQuery.ACCOUNT_NAME),
+ groupCursor.getString(SettingsQuery.ACCOUNT_TYPE)
+ }, null);
while (availableGroups.moveToNext()) {
// Create item this unsynced group
final long groupId = availableGroups.getLong(GroupsQuery._ID);
@@ -538,6 +554,98 @@
}
/**
+ * Special {@link Cursor} that shows zero or one items based on
+ * {@link Settings#ANY_UNSYNCED} value.
+ */
+ private static class FooterCursor extends AbstractCursor {
+ private Context mContext;
+ private Cursor mCursor;
+ private int mPosition;
+
+ public FooterCursor(Context context, Cursor cursor, int position) {
+ mContext = context;
+ mCursor = cursor;
+ mPosition = position;
+ }
+
+ @Override
+ public int getCount() {
+ assertParent();
+
+ final boolean anyUnsynced = mCursor.getInt(SettingsQuery.ANY_UNSYNCED) != 0;
+ return anyUnsynced ? 1 : 0;
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return GroupsQuery.PROJECTION;
+ }
+
+ protected void assertParent() {
+ mCursor.moveToPosition(mPosition);
+ }
+
+ @Override
+ public String getString(int column) {
+ assertParent();
+ switch(column) {
+ case GroupsQuery.ACCOUNT_NAME:
+ return mCursor.getString(SettingsQuery.ACCOUNT_NAME);
+ case GroupsQuery.ACCOUNT_TYPE:
+ return mCursor.getString(SettingsQuery.ACCOUNT_TYPE);
+ case GroupsQuery.TITLE:
+ return null;
+ case GroupsQuery.RES_PACKAGE:
+ return mContext.getPackageName();
+ case GroupsQuery.TITLE_RES:
+ return Integer.toString(UNSYNCED_ID);
+ }
+ throw new IllegalArgumentException("Requested column not available as string");
+ }
+
+ @Override
+ public short getShort(int column) {
+ throw new IllegalArgumentException("Requested column not available as short");
+ }
+
+ @Override
+ public int getInt(int column) {
+ assertParent();
+ switch(column) {
+ case GroupsQuery._ID:
+ return UNSYNCED_ID;
+ case GroupsQuery.TITLE_RES:
+ return R.string.display_more_groups;
+ case GroupsQuery.GROUP_VISIBLE:
+ case GroupsQuery.SUMMARY_COUNT:
+ case GroupsQuery.SUMMARY_WITH_PHONES:
+ return FOOTER_ENTRY;
+ }
+ throw new IllegalArgumentException("Requested column not available as int");
+ }
+
+ @Override
+ public long getLong(int column) {
+ return getInt(column);
+ }
+
+ @Override
+ public float getFloat(int column) {
+ throw new IllegalArgumentException("Requested column not available as float");
+ }
+
+ @Override
+ public double getDouble(int column) {
+ throw new IllegalArgumentException("Requested column not available as double");
+ }
+
+ @Override
+ public boolean isNull(int column) {
+ return getString(column) == null;
+ }
+ }
+
+ /**
* Adapter that shows all display groups as returned by a {@link Cursor}
* over {@link Groups#CONTENT_SUMMARY_URI}, along with their current visible
* status. Splits groups into sections based on {@link Account}.
@@ -600,13 +708,14 @@
final int position = groupCursor.getPosition();
final Cursor ungroupedCursor = new HeaderCursor(mContext, groupCursor, position);
+ final Cursor unsyncedCursor = new FooterCursor(mContext, groupCursor, position);
final ContentResolver resolver = mContext.getContentResolver();
final Cursor groupsCursor = resolver.query(Groups.CONTENT_SUMMARY_URI,
GroupsQuery.PROJECTION, selection, selectionArgs, null);
mActivity.startManagingCursor(groupsCursor);
- return new MergeCursor(new Cursor[] { ungroupedCursor, groupsCursor });
+ return new MergeCursor(new Cursor[] { ungroupedCursor, groupsCursor, unsyncedCursor });
}
@Override
@@ -634,6 +743,12 @@
text1.setText(title);
// text2.setText(descrip);
checkbox.setChecked((membersVisible == 1));
+
+ // Hide extra views when recycled as footer
+ final boolean footerView = membersVisible == FOOTER_ENTRY;
+// text2.setVisibility(footerView ? View.GONE : View.VISIBLE);
+ text2.setVisibility(View.GONE);
+ checkbox.setVisibility(footerView ? View.GONE : View.VISIBLE);
}
}
@@ -643,6 +758,7 @@
Settings.ACCOUNT_TYPE,
Settings.SHOULD_SYNC,
Settings.UNGROUPED_VISIBLE,
+ Settings.ANY_UNSYNCED,
// Settings.UNGROUPED_COUNT,
// Settings.UNGROUPED_WITH_PHONES,
};
@@ -651,8 +767,9 @@
final int ACCOUNT_TYPE = 1;
final int SHOULD_SYNC = 2;
final int UNGROUPED_VISIBLE = 3;
-// final int UNGROUPED_COUNT = 4;
-// final int UNGROUPED_WITH_PHONES = 5;
+ final int ANY_UNSYNCED = 4;
+// final int UNGROUPED_COUNT = 5;
+// final int UNGROUPED_WITH_PHONES = 6;
}
private interface GroupsQuery {
@@ -662,10 +779,10 @@
Groups.RES_PACKAGE,
Groups.TITLE_RES,
Groups.GROUP_VISIBLE,
-// Groups.SUMMARY_COUNT,
-// Groups.SUMMARY_WITH_PHONES,
Groups.ACCOUNT_NAME,
Groups.ACCOUNT_TYPE,
+// Groups.SUMMARY_COUNT,
+// Groups.SUMMARY_WITH_PHONES,
};
final int _ID = 0;
@@ -673,9 +790,9 @@
final int RES_PACKAGE = 2;
final int TITLE_RES = 3;
final int GROUP_VISIBLE = 4;
-// final int SUMMARY_COUNT = 5;
-// final int SUMMARY_WITH_PHONES = 6;
final int ACCOUNT_NAME = 5;
final int ACCOUNT_TYPE = 6;
+ final int SUMMARY_COUNT = 7;
+ final int SUMMARY_WITH_PHONES = 8;
}
}