Initial cut of "Split Aggregate" UI.
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index 9dc898f..3b80da5 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -16,25 +16,20 @@
package com.android.contacts;
-import static com.android.contacts.ContactEntryAdapter.AGGREGATE_DISPLAY_NAME_COLUMN;
import static com.android.contacts.ContactEntryAdapter.AGGREGATE_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.AGGREGATE_STARRED_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_ID_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_PACKAGE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_MIMETYPE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_IS_PRIMARY_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_IS_SUPER_PRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_1_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_2_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_3_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_4_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_5_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_6_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_7_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_8_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_9_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_10_COLUMN;
+import static com.android.contacts.ContactEntryAdapter.DATA_CONTACT_ID_COLUMN;
+import static com.android.contacts.ContactEntryAdapter.DATA_ID_COLUMN;
+import static com.android.contacts.ContactEntryAdapter.DATA_IS_SUPER_PRIMARY_COLUMN;
+import static com.android.contacts.ContactEntryAdapter.DATA_MIMETYPE_COLUMN;
+import com.android.contacts.SplitAggregateView.OnContactSelectedListener;
import com.android.internal.telephony.ITelephony;
import android.app.AlertDialog;
@@ -47,13 +42,13 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.DialogInterface.OnClickListener;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -63,14 +58,14 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Aggregates;
-import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Data;
-import android.provider.Im;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
+import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -85,7 +80,6 @@
import android.widget.Toast;
import java.util.ArrayList;
-import java.util.List;
/**
* Displays the details of a specific contact.
@@ -103,6 +97,12 @@
public static final int MENU_ITEM_DELETE = 1;
public static final int MENU_ITEM_MAKE_DEFAULT = 2;
public static final int MENU_ITEM_SHOW_BARCODE = 3;
+ public static final int MENU_ITEM_SPLIT_AGGREGATE = 4;
+
+ private static final String[] AGGREGATION_EXCEPTIONS_PROJECTION =
+ new String[] { AggregationExceptions._ID};
+
+ private static final int AGGREGATION_EXCEPTIONS_COL_ID = 0;
private Uri mUri;
private Uri mAggDataUri;
@@ -110,6 +110,11 @@
private ViewAdapter mAdapter;
private int mNumPhoneNumbers = 0;
+ /**
+ * A list of distinct contact IDs included in the current aggregate.
+ */
+ private ArrayList<Long> mContactIds = new ArrayList<Long>();
+
/* package */ ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
/* package */ ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
/* package */ ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
@@ -300,7 +305,8 @@
.setAlphabeticShortcut('e');
menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
.setIcon(android.R.drawable.ic_menu_delete);
-
+ menu.add(0, MENU_ITEM_SPLIT_AGGREGATE, 0, R.string.menu_splitAggregate)
+ .setIcon(android.R.drawable.ic_menu_share);
return true;
}
@@ -317,6 +323,9 @@
} else {
menu.removeItem(MENU_ITEM_SHOW_BARCODE);
}
+
+ boolean isAggregate = mContactIds.size() > 1;
+ menu.findItem(MENU_ITEM_SPLIT_AGGREGATE).setEnabled(isAggregate);
return true;
}
@@ -373,6 +382,11 @@
return true;
}
+ case MENU_ITEM_SPLIT_AGGREGATE: {
+ showSplitAggregateDialog();
+ return true;
+ }
+
// TODO(emillar) Bring this back.
/*case MENU_ITEM_SHOW_BARCODE:
if (mCursor.moveToFirst()) {
@@ -423,32 +437,131 @@
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_MAKE_DEFAULT: {
- AdapterView.AdapterContextMenuInfo info;
- try {
- info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
- } catch (ClassCastException e) {
- Log.e(TAG, "bad menuInfo", e);
- break;
+ if (makeItemDefault(item)) {
+ return true;
}
-
- ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position,
- SHOW_SEPARATORS);
-
- // Update the primary values in the data record.
- ContentValues values = new ContentValues(1);
- values.put(Data.IS_PRIMARY, 1);
- values.put(Data.IS_SUPER_PRIMARY, 1);
-
- Log.i(TAG, mUri.toString());
- getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
- values, null, null);
- dataChanged();
- return true;
+ break;
}
}
+
return super.onContextItemSelected(item);
}
+ private boolean makeItemDefault(MenuItem item) {
+ ViewEntry entry = getViewEntryForMenuItem(item);
+ if (entry == null) {
+ return false;
+ }
+
+ // Update the primary values in the data record.
+ ContentValues values = new ContentValues(2);
+ values.put(Data.IS_PRIMARY, 1);
+ values.put(Data.IS_SUPER_PRIMARY, 1);
+
+ Log.i(TAG, mUri.toString());
+ getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
+ values, null, null);
+ dataChanged();
+ return true;
+ }
+
+ /**
+ * Shows a dialog that contains a list of all constituent contacts in this aggregate.
+ * The user picks a contact to be split into its own aggregate or clicks Cancel.
+ */
+ private void showSplitAggregateDialog() {
+
+ // Wrap this dialog in a specific theme so that list items have correct text color.
+ final ContextThemeWrapper dialogContext =
+ new ContextThemeWrapper(this, android.R.style.Theme_Light);
+ AlertDialog.Builder builder =
+ new AlertDialog.Builder(dialogContext);
+ builder.setTitle(getString(R.string.splitAggregate_title));
+
+ final SplitAggregateView view = new SplitAggregateView(dialogContext, mUri);
+ builder.setView(view);
+
+ builder.setInverseBackgroundForced(true);
+ builder.setCancelable(true);
+ builder.setNegativeButton(android.R.string.cancel,
+ new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ final AlertDialog dialog = builder.create();
+
+ view.setOnContactSelectedListener(new OnContactSelectedListener() {
+ public void onContactSelected(long contactId) {
+ dialog.dismiss();
+ splitContact(contactId);
+ }
+ });
+
+ dialog.show();
+ }
+
+ /**
+ * Given an ID of a constituent contact, splits it off into a separate aggregate.
+ */
+ protected void splitContact(long contactToSplit) {
+ long contactToKeep;
+
+ // We are guaranteed to have at least two items in the mContactIds array, because
+ // otherwise the "Split" menu item would be disabled.
+ if (mContactIds.get(0) != contactToSplit) {
+ contactToKeep = mContactIds.get(0);
+ } else {
+ contactToKeep = mContactIds.get(1);
+ }
+
+ ContentValues values = new ContentValues(3);
+ values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_NEVER_MATCH);
+
+ boolean updated = false;
+
+ // First see if there is an existing exception for this pair of contacts
+ Cursor c = mResolver.query(AggregationExceptions.CONTENT_URI,
+ AGGREGATION_EXCEPTIONS_PROJECTION,
+ "(" + AggregationExceptions.CONTACT_ID1 + "=" + contactToSplit
+ + " AND " + AggregationExceptions.CONTACT_ID2 + "=" + contactToKeep
+ + ") OR (" + AggregationExceptions.CONTACT_ID1 + "=" + contactToKeep
+ + " AND " + AggregationExceptions.CONTACT_ID2 + "=" + contactToSplit + ")",
+ null, null);
+
+ try {
+ if (c.moveToFirst()) {
+ long exceptionId = c.getLong(AGGREGATION_EXCEPTIONS_COL_ID);
+ mResolver.update(ContentUris.withAppendedId(AggregationExceptions.CONTENT_URI,
+ exceptionId), values, null, null);
+ updated = true;
+ }
+ } finally {
+ c.close();
+ }
+
+ // Otherwise, insert a new exception
+ if (!updated) {
+ values.put(AggregationExceptions.CONTACT_ID1, contactToSplit);
+ values.put(AggregationExceptions.CONTACT_ID2, contactToKeep);
+ mResolver.insert(AggregationExceptions.CONTENT_URI, values);
+ }
+
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private ViewEntry getViewEntryForMenuItem(MenuItem item) {
+ AdapterView.AdapterContextMenuInfo info;
+ try {
+ info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+ } catch (ClassCastException e) {
+ Log.e(TAG, "bad menuInfo", e);
+ return null;
+ }
+
+ return ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
+ }
+
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
@@ -535,6 +648,8 @@
mSections.get(i).clear();
}
+ mContactIds.clear();
+
// Build up method entries
if (mUri != null) {
Bitmap photoBitmap = null;
@@ -548,6 +663,10 @@
entry.id = id;
entry.uri = uri;
entry.mimetype = mimetype;
+ entry.contactId = aggCursor.getLong(DATA_CONTACT_ID_COLUMN);
+ if (!mContactIds.contains(entry.contactId)) {
+ mContactIds.add(entry.contactId);
+ }
if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
|| mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)