Merge change 25294 into eclair
* changes:
Adjust to new ContactHeaderWidget api.
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/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index a1ffff4..e39a7fc 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -432,7 +432,10 @@
mMode = MODE_PICK_POSTAL;
} else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type)) {
mMode = MODE_LEGACY_PICK_POSTAL;
+ } else if (People.CONTENT_ITEM_TYPE.equals(type)) {
+ mMode = MODE_LEGACY_PICK_OR_CREATE_PERSON;
}
+
} else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
mMode = MODE_INSERT_OR_EDIT_CONTACT;
} else if (Intent.ACTION_SEARCH.equals(action)) {
@@ -1046,6 +1049,10 @@
startActivity(intent);
finish();
} else if (id != -1) {
+ // Subtract one if we have Create Contact at the top
+ if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
+ position--;
+ }
final Uri uri = getSelectedUri(position);
if ((mMode & MODE_MASK_PICKER) == 0) {
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
@@ -1062,10 +1069,7 @@
|| mMode == MODE_LEGACY_PICK_PERSON
|| mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
if (mShortcutAction != null) {
- // Subtract one if we have Create Contact at the top
- Cursor c = (Cursor) mAdapter.getItem(position
- - (mMode == MODE_PICK_OR_CREATE_CONTACT
- || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON ? 1:0));
+ Cursor c = (Cursor) mAdapter.getItem(position);
returnPickerResult(c, c.getString(SUMMARY_NAME_COLUMN_INDEX), uri, id);
} else {
returnPickerResult(null, null, uri, id);
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index 725c65b..c0b7634 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -16,6 +16,7 @@
package com.android.contacts;
+import android.accounts.Account;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
@@ -44,6 +45,15 @@
import android.text.Spanned;
import android.text.style.RelativeSizeSpan;
import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
import java.io.File;
import java.io.FileInputStream;
@@ -139,17 +149,21 @@
// For reading multiple files.
private List<VCardFile> mVCardFileList;
private List<String> mErrorFileNameList;
+
+ final private Account mAccount;
- public VCardReadThread(String canonicalPath) {
+ public VCardReadThread(String canonicalPath, Account account) {
mCanonicalPath = canonicalPath;
mVCardFileList = null;
+ mAccount = account;
init();
}
- public VCardReadThread(List<VCardFile> vcardFileList) {
+ public VCardReadThread(List<VCardFile> vcardFileList, Account account) {
mCanonicalPath = null;
mVCardFileList = vcardFileList;
mErrorFileNameList = new ArrayList<String>();
+ mAccount = account;
init();
}
@@ -223,7 +237,8 @@
mProgressDialog.setIndeterminate(false);
mProgressDialog.setMax(counter.getCount());
String charset = detector.getEstimatedCharset();
- doActuallyReadOneVCard(mCanonicalPath, charset, true, detector, null);
+ doActuallyReadOneVCard(mCanonicalPath, null, charset, true, detector,
+ mErrorFileNameList);
} else { // Read multiple files.
mProgressDialog.setProgressNumberFormat(
getString(R.string.reading_vcard_files));
@@ -246,7 +261,7 @@
// Assume that VCardSourceDetector was able to detect the source.
}
String charset = detector.getEstimatedCharset();
- doActuallyReadOneVCard(canonicalPath,
+ doActuallyReadOneVCard(canonicalPath, mAccount,
charset, false, detector, mErrorFileNameList);
mProgressDialog.incrementProgressBy(1);
}
@@ -278,7 +293,7 @@
}
}
- private boolean doActuallyReadOneVCard(String canonicalPath,
+ private boolean doActuallyReadOneVCard(String canonicalPath, Account account,
String charset, boolean showEntryParseProgress,
VCardSourceDetector detector, List<String> errorFileNameList) {
final Context context = ImportVCardActivity.this;
@@ -287,10 +302,10 @@
int vcardType = VCardConfig.getVCardTypeFromString(
context.getString(R.string.config_import_vcard_type));
if (charset != null) {
- builder = new VCardDataBuilder(charset, charset, false, vcardType);
+ builder = new VCardDataBuilder(charset, charset, false, vcardType, mAccount);
} else {
charset = VCardConfig.DEFAULT_CHARSET;
- builder = new VCardDataBuilder(null, null, false, vcardType);
+ builder = new VCardDataBuilder(null, null, false, vcardType, mAccount);
}
builder.addEntryHandler(new EntryCommitter(mResolver));
if (showEntryParseProgress) {
@@ -395,24 +410,26 @@
public static final int IMPORT_ALL = 2;
public static final int IMPORT_TYPE_SIZE = 3;
- private List<VCardFile> mVCardFileList;
+ final private List<VCardFile> mVCardFileList;
+ final private Account mAccount;
private int mCurrentIndex;
- public ImportTypeSelectedListener(List<VCardFile> vcardFileList) {
+ public ImportTypeSelectedListener(List<VCardFile> vcardFileList, Account account) {
mVCardFileList = vcardFileList;
+ mAccount = account;
}
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
switch (mCurrentIndex) {
case IMPORT_ALL:
- importMultipleVCardFromSDCard(mVCardFileList);
+ importMultipleVCardFromSDCard(mVCardFileList, mAccount);
break;
case IMPORT_MULTIPLE:
- showVCardFileSelectDialog(mVCardFileList, true);
+ showVCardFileSelectDialog(mVCardFileList, mAccount, true);
break;
default:
- showVCardFileSelectDialog(mVCardFileList, false);
+ showVCardFileSelectDialog(mVCardFileList, mAccount, false);
}
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
finish();
@@ -422,16 +439,43 @@
}
}
+ private class AccountSelectedListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+
+ final private List<Account> mAccountList;
+ final private List<VCardFile> mVCardFileList;
+
+ public AccountSelectedListener(List<Account> accountList, List<VCardFile> vcardFileList) {
+ if (accountList == null || accountList.size() == 0) {
+ Log.e(LOG_TAG, "The size of Account list is 0");
+ }
+ mAccountList = accountList;
+ mVCardFileList = vcardFileList;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ startVCardSelectAndImport(mVCardFileList, mAccountList.get(which));
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ }
private class VCardSelectedListener implements
DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
- private List<VCardFile> mVCardFileList;
+ final private List<VCardFile> mVCardFileList;
+ final private Account mAccount;
private int mCurrentIndex;
private Set<Integer> mSelectedIndexSet;
public VCardSelectedListener(
- List<VCardFile> vcardFileList, boolean multipleSelect) {
+ List<VCardFile> vcardFileList,
+ Account account,
+ boolean multipleSelect) {
mVCardFileList = vcardFileList;
+ mAccount = account;
mCurrentIndex = 0;
if (multipleSelect) {
mSelectedIndexSet = new HashSet<Integer>();
@@ -449,9 +493,10 @@
selectedVCardFileList.add(mVCardFileList.get(i));
}
}
- importMultipleVCardFromSDCard(selectedVCardFileList);
+ importMultipleVCardFromSDCard(selectedVCardFileList, mAccount);
} else {
- importOneVCardFromSDCard(mVCardFileList.get(mCurrentIndex).getCanonicalPath());
+ importOneVCardFromSDCard(mVCardFileList.get(mCurrentIndex).getCanonicalPath(),
+ mAccount);
}
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
finish();
@@ -488,7 +533,7 @@
private File mRootDirectory;
// null when search operation is canceled.
- private List<VCardFile> mVCardFiles;
+ private List<VCardFile> mVCardFileList;
// To avoid recursive link.
private Set<String> mCheckedPaths;
@@ -502,7 +547,7 @@
mGotIOException = false;
mRootDirectory = sdcardDirectory;
mCheckedPaths = new HashSet<String>();
- mVCardFiles = new Vector<VCardFile>();
+ mVCardFileList = new Vector<VCardFile>();
PowerManager powerManager = (PowerManager)ImportVCardActivity.this.getSystemService(
Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(
@@ -524,7 +569,7 @@
}
if (mCanceled) {
- mVCardFiles = null;
+ mVCardFileList = null;
}
mProgressDialog.dismiss();
@@ -548,9 +593,10 @@
} else if (mCanceled) {
finish();
} else {
+ // TODO: too many nest. Clean up this code...
mHandler.post(new Runnable() {
public void run() {
- int size = mVCardFiles.size();
+ int size = mVCardFileList.size();
final Context context = ImportVCardActivity.this;
if (size == 0) {
String message = (getString(R.string.scanning_sdcard_failed_message,
@@ -564,17 +610,59 @@
.setPositiveButton(android.R.string.ok, mCancelListener);
builder.show();
return;
- } else if (context.getResources().getBoolean(
- R.bool.config_import_all_vcard_from_sdcard_automatically)) {
- importMultipleVCardFromSDCard(mVCardFiles);
- } else if (size == 1) {
- importOneVCardFromSDCard(mVCardFiles.get(0).getCanonicalPath());
- } else if (context.getResources().getBoolean(
- R.bool.config_allow_users_select_all_vcard_import)) {
- showSelectImportTypeDialog(mVCardFiles);
} else {
- // Let a user to select one vCard file.
- showVCardFileSelectDialog(mVCardFiles, false);
+ final Sources sources = Sources.getInstance(context);
+ final List<Account> accountList = sources.getAccounts(true);
+ if (accountList == null || accountList.size() == 0) {
+ startVCardSelectAndImport(mVCardFileList, null);
+ } else {
+ // A lot of codes are copied from EditContactActivity.
+ // TODO: Can we share the logic?
+
+ // Wrap our context to inflate list items using correct theme
+ final Context dialogContext = new ContextThemeWrapper(
+ context, android.R.style.Theme_Light);
+ final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final ArrayAdapter<Account> accountAdapter =
+ new ArrayAdapter<Account>(context,
+ android.R.layout.simple_list_item_2, accountList) {
+ @Override
+ public View getView(int position, View convertView,
+ ViewGroup parent) {
+ if (convertView == null) {
+ convertView = dialogInflater.inflate(
+ android.R.layout.simple_list_item_2,
+ parent, false);
+ }
+
+ // TODO: show icon along with title
+ final TextView text1 =
+ (TextView)convertView.findViewById(android.R.id.text1);
+ final TextView text2 =
+ (TextView)convertView.findViewById(android.R.id.text2);
+
+ final Account account = this.getItem(position);
+ final ContactsSource source =
+ sources.getInflatedSource(account.type,
+ ContactsSource.LEVEL_SUMMARY);
+
+ text1.setText(source.getDisplayLabel(context));
+ text2.setText(account.name);
+
+ return convertView;
+ }
+ };
+
+ AccountSelectedListener listener =
+ new AccountSelectedListener(accountList, mVCardFileList);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.dialog_new_contact_account)
+ .setSingleChoiceItems(accountAdapter, 0, listener)
+ .setOnCancelListener(listener);
+ builder.show();
+ }
}
}
});
@@ -605,7 +693,7 @@
String fileName = file.getName();
VCardFile vcardFile = new VCardFile(
fileName, canonicalPath, file.lastModified());
- mVCardFiles.add(vcardFile);
+ mVCardFileList.add(vcardFile);
}
}
}
@@ -621,22 +709,40 @@
}
}
+ private void startVCardSelectAndImport(final List<VCardFile> vcardFileList,
+ final Account account) {
+ final Context context = ImportVCardActivity.this;
+ final int size = vcardFileList.size();
+ if (context.getResources().getBoolean(
+ R.bool.config_import_all_vcard_from_sdcard_automatically)) {
+ importMultipleVCardFromSDCard(vcardFileList, account);
+ } else if (size == 1) {
+ importOneVCardFromSDCard(vcardFileList.get(0).getCanonicalPath(), account);
+ } else if (context.getResources().getBoolean(
+ R.bool.config_allow_users_select_all_vcard_import)) {
+ showSelectImportTypeDialog(vcardFileList, account);
+ } else {
+ // Let a user to select one vCard file.
+ showVCardFileSelectDialog(vcardFileList, account, false);
+ }
+ }
- private void importOneVCardFromSDCard(final String canonicalPath) {
- VCardReadThread thread = new VCardReadThread(canonicalPath);
+ private void importOneVCardFromSDCard(final String canonicalPath, Account account) {
+ VCardReadThread thread = new VCardReadThread(canonicalPath, account);
showReadingVCardDialog(thread);
thread.start();
}
- private void importMultipleVCardFromSDCard(final List<VCardFile> vcardFileList) {
- VCardReadThread thread = new VCardReadThread(vcardFileList);
+ private void importMultipleVCardFromSDCard(final List<VCardFile> vcardFileList,
+ final Account account) {
+ VCardReadThread thread = new VCardReadThread(vcardFileList, account);
showReadingVCardDialog(thread);
thread.start();
}
- private void showSelectImportTypeDialog(List<VCardFile> vcardFileList) {
+ private void showSelectImportTypeDialog(List<VCardFile> vcardFileList, Account account) {
DialogInterface.OnClickListener listener =
- new ImportTypeSelectedListener(vcardFileList);
+ new ImportTypeSelectedListener(vcardFileList, account);
AlertDialog.Builder builder =
new AlertDialog.Builder(ImportVCardActivity.this)
.setTitle(R.string.select_vcard_title)
@@ -657,10 +763,10 @@
}
private void showVCardFileSelectDialog(
- List<VCardFile> vcardFileList, boolean multipleSelect) {
+ List<VCardFile> vcardFileList, Account account, boolean multipleSelect) {
int size = vcardFileList.size();
VCardSelectedListener listener =
- new VCardSelectedListener(vcardFileList, multipleSelect);
+ new VCardSelectedListener(vcardFileList, account, multipleSelect);
AlertDialog.Builder builder =
new AlertDialog.Builder(this)
.setTitle(R.string.select_vcard_title)
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index b941b38..003e641 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -278,7 +278,7 @@
final EditType type = iterator.next();
final int count = typeCount.get(type.rawValue);
- if (count == exactValue) {
+ if (exactValue == type.rawValue) {
// Found exact value match
return type;
}
@@ -468,13 +468,34 @@
{
// Im
- // TODO: handle decodeImProtocol for legacy reasons
final DataKind kind = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
+ fixupLegacyImType(extras);
parseExtras(state, kind, extras, Insert.IM_PROTOCOL, Insert.IM_HANDLE, Im.DATA);
}
}
/**
+ * Attempt to parse legacy {@link Insert#IM_PROTOCOL} values, replacing them
+ * with updated values.
+ */
+ private static void fixupLegacyImType(Bundle bundle) {
+ final String encodedString = bundle.getString(Insert.IM_PROTOCOL);
+ if (encodedString == null) return;
+
+ try {
+ final Object protocol = android.provider.Contacts.ContactMethods
+ .decodeImProtocol(encodedString);
+ if (protocol instanceof Integer) {
+ bundle.putInt(Insert.IM_PROTOCOL, (Integer)protocol);
+ } else {
+ bundle.putString(Insert.IM_PROTOCOL, (String)protocol);
+ }
+ } catch (IllegalArgumentException e) {
+ // Ignore exception when legacy parser fails
+ }
+ }
+
+ /**
* Parse a specific entry from the given {@link Bundle} and insert into the
* given {@link EntityDelta}. Silently skips the insert when missing value
* or no valid {@link EditType} found.
@@ -506,8 +527,8 @@
if (editType != null && editType.customColumn != null) {
// Write down label when custom type picked
- final CharSequence customType = extras.getCharSequence(typeExtra);
- child.put(editType.customColumn, customType.toString());
+ final String customType = extras.getString(typeExtra);
+ child.put(editType.customColumn, customType);
}
}
}
diff --git a/src/com/android/contacts/model/EntitySet.java b/src/com/android/contacts/model/EntitySet.java
index b9a71e7..18afd2b 100644
--- a/src/com/android/contacts/model/EntitySet.java
+++ b/src/com/android/contacts/model/EntitySet.java
@@ -16,6 +16,7 @@
package com.android.contacts.model;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.google.android.collect.Lists;
import android.content.ContentProviderOperation;
@@ -173,18 +174,20 @@
* Find {@link RawContacts#_ID} of the requested {@link EntityDelta}.
*/
public long getRawContactId(int index) {
- if (index >=0 && index < this.size()) {
+ if (index >= 0 && index < this.size()) {
final EntityDelta delta = this.get(index);
- return delta.getValues().getAsLong(RawContacts._ID);
- } else {
- return 0;
+ final ValuesDelta values = delta.getValues();
+ if (values.isVisible()) {
+ return values.getAsLong(RawContacts._ID);
+ }
}
+ return 0;
}
/**
* Find index of given {@link RawContacts#_ID} when present.
*/
- public int indexOfRawContactId(long rawContactId) {
+ public int indexOfRawContactId(Long rawContactId) {
final int size = this.size();
for (int i = 0; i < size; i++) {
final long currentId = getRawContactId(i);
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;
}
}
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 2354a7f..27ed008 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -61,6 +61,7 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts.Data;
import android.util.Log;
+import android.util.SparseArray;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -101,6 +102,8 @@
private EntitySet mState;
+ private ArrayList<Dialog> mManagedDialogs = Lists.newArrayList();
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -129,11 +132,14 @@
findViewById(R.id.btn_done).setOnClickListener(this);
findViewById(R.id.btn_discard).setOnClickListener(this);
- if (Intent.ACTION_EDIT.equals(action) && icicle == null) {
+ // Handle initial actions only when existing state missing
+ final boolean hasIncomingState = icicle != null && icicle.containsKey(KEY_EDIT_STATE);
+
+ if (Intent.ACTION_EDIT.equals(action) && !hasIncomingState) {
// Read initial state from database
new QueryEntitiesTask(this).execute(intent);
- } else if (Intent.ACTION_INSERT.equals(action) && icicle == null) {
+ } else if (Intent.ACTION_INSERT.equals(action) && !hasIncomingState) {
// Trigger dialog to pick account type
doAddAction();
}
@@ -180,7 +186,7 @@
// Handle any incoming values that should be inserted
final Bundle extras = intent.getExtras();
- final boolean hasExtras = extras.size() > 0;
+ final boolean hasExtras = extras != null && extras.size() > 0;
final boolean hasState = target.mState.size() > 0;
if (hasExtras && hasState) {
// Find source defining the first RawContact found
@@ -224,7 +230,7 @@
bindHeader();
if (hasValidState()) {
- final long selectedId = savedInstanceState.getLong(KEY_SELECTED_RAW_CONTACT);
+ final Long selectedId = savedInstanceState.getLong(KEY_SELECTED_RAW_CONTACT);
setSelectedRawContactId(selectedId);
}
@@ -232,20 +238,68 @@
super.onRestoreInstanceState(savedInstanceState);
}
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ for (Dialog dialog : mManagedDialogs) {
+ if (dialog.isShowing()) {
+ dialog.dismiss();
+ }
+ }
+ }
+
+ /**
+ * Start managing this {@link Dialog} along with the {@link Activity}.
+ */
+ private void startManagingDialog(Dialog dialog) {
+ synchronized (mManagedDialogs) {
+ mManagedDialogs.add(dialog);
+ }
+ }
+
+ /**
+ * Show this {@link Dialog} and manage with the {@link Activity}.
+ */
+ private void showAndManageDialog(Dialog dialog) {
+ startManagingDialog(dialog);
+ dialog.show();
+ }
+
/**
* Return the {@link RawContacts#_ID} of the currently selected tab.
*/
- protected long getSelectedRawContactId() {
- final int index = mTabWidget.getCurrentTab();
- return mState.getRawContactId(index);
+ protected Long getSelectedRawContactId() {
+ final int tabIndex = mTabWidget.getCurrentTab();
+ return this.mTabRawContacts.get(tabIndex);
+ }
+
+ /**
+ * Return the {@link EntityDelta} for the currently selected tab.
+ */
+ protected EntityDelta getSelectedEntityDelta() {
+ final Long rawContactId = getSelectedRawContactId();
+ final int stateIndex = mState.indexOfRawContactId(rawContactId);
+ return mState.get(stateIndex);
}
/**
* Set the selected tab based on the given {@link RawContacts#_ID}.
*/
- protected void setSelectedRawContactId(long rawContactId) {
- final int index = mState.indexOfRawContactId(rawContactId);
- mTabWidget.setCurrentTab(index);
+ protected void setSelectedRawContactId(Long rawContactId) {
+ int tabIndex = 0;
+
+ // Find index of requested contact
+ final int size = mTabRawContacts.size();
+ for (int i = 0; i < size; i++) {
+ if (mTabRawContacts.valueAt(i) == rawContactId) {
+ tabIndex = i;
+ break;
+ }
+ }
+
+ mTabWidget.setCurrentTab(tabIndex);
+ this.onTabSelectionChanged(tabIndex, false);
}
/**
@@ -259,6 +313,13 @@
/**
+ * Map from {@link #mTabWidget} indexes to {@link RawContacts#_ID}, usually
+ * used when mapping to {@link #mState}.
+ */
+ private SparseArray<Long> mTabRawContacts = new SparseArray<Long>();
+
+
+ /**
* Rebuild tabs to match our underlying {@link #mState} object, usually
* called once we've parsed {@link Entity} data or have inserted a new
* {@link RawContacts}.
@@ -267,24 +328,34 @@
if (!hasValidState()) return;
final Sources sources = Sources.getInstance(this);
- int selectedTab = 0;
+ final Long selectedRawContactId = this.getSelectedRawContactId();
+ // Remove any existing tabs and rebuild any visible
mTabWidget.removeAllTabs();
+ mTabRawContacts.clear();
for (EntityDelta entity : mState) {
- ValuesDelta values = entity.getValues();
+ final ValuesDelta values = entity.getValues();
+ if (!values.isVisible()) continue;
+
final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
final Long rawContactId = values.getAsLong(RawContacts._ID);
final ContactsSource source = sources.getInflatedSource(accountType,
ContactsSource.LEVEL_CONSTRAINTS);
+ final int tabIndex = mTabWidget.getTabCount();
final View tabView = ContactsUtils.createTabIndicatorView(
mTabWidget.getTabParent(), source);
mTabWidget.addTab(tabView);
+ mTabRawContacts.put(tabIndex, rawContactId);
}
- if (mState.size() > 0) {
- mTabWidget.setCurrentTab(selectedTab);
- this.onTabSelectionChanged(selectedTab, false);
+ final boolean hasActiveTabs = mTabWidget.getTabCount() > 0;
+ if (hasActiveTabs) {
+ // Focus on last selected contact
+ this.setSelectedRawContactId(selectedRawContactId);
+ } else {
+ // Nothing remains to edit, save and bail entirely
+ this.doSaveAction();
}
// Show editor now that we've loaded state
@@ -317,10 +388,9 @@
/** {@inheritDoc} */
public void onTabSelectionChanged(int tabIndex, boolean clicked) {
if (!hasValidState()) return;
- if (tabIndex < 0 || tabIndex >= mState.size()) return;
// Find entity and source for selected tab
- final EntityDelta entity = mState.get(tabIndex);
+ final EntityDelta entity = this.getSelectedEntityDelta();
final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
final Sources sources = Sources.getInstance(this);
@@ -334,13 +404,13 @@
/** {@inheritDoc} */
public void onDisplayNameLongClick(View view) {
if (!hasValidState()) return;
- this.createNameDialog().show();
+ showAndManageDialog(createNameDialog());
}
/** {@inheritDoc} */
public void onPhotoLongClick(View view) {
if (!hasValidState()) return;
- this.createPhotoDialog().show();
+ showAndManageDialog(createPhotoDialog());
}
@@ -535,11 +605,10 @@
if (!hasValidState()) return false;
// Pass back last-selected contact
- final int selectedTab = mTabWidget.getCurrentTab();
- final long rawContactId = mState.getRawContactId(selectedTab);
- if (rawContactId != -1) {
+ final Long rawContactId = this.getSelectedRawContactId();
+ if (rawContactId != null) {
final Intent intent = new Intent();
- intent.putExtra(ViewContactActivity.RAW_CONTACT_ID_EXTRA, rawContactId);
+ intent.putExtra(ViewContactActivity.RAW_CONTACT_ID_EXTRA, (long)rawContactId);
setResult(RESULT_OK, intent);
}
@@ -586,7 +655,7 @@
private boolean doDeleteAction() {
if (!hasValidState()) return false;
- this.createDeleteDialog().show();
+ showAndManageDialog(createDeleteDialog());
return true;
}
@@ -745,7 +814,7 @@
@Override
protected void onPostExecute(EditContactActivity target, AlertDialog.Builder result) {
- result.create().show();
+ target.showAndManageDialog(result.create());
}
}
@@ -759,11 +828,9 @@
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Mark the currently selected contact for deletion
- final int index = mTabWidget.getCurrentTab();
- final EntityDelta delta = mState.get(index);
+ final EntityDelta delta = getSelectedEntityDelta();
delta.markDeleted();
- // TODO: trigger task to update tabs (doesnt need to be background)
bindTabs();
bindHeader();
}