Adding support for a full-contact (entities) query.
Change-Id: Ib1cdd998dcc4f60124dbc37a42fd61ee0f6802fd
diff --git a/src/com/android/exchange/adapter/GalParser.java b/src/com/android/exchange/adapter/GalParser.java
index fffad70..94c8cdf 100644
--- a/src/com/android/exchange/adapter/GalParser.java
+++ b/src/com/android/exchange/adapter/GalParser.java
@@ -17,6 +17,7 @@
import com.android.exchange.EasSyncService;
import com.android.exchange.provider.GalResult;
+import com.android.exchange.provider.GalResult.GalData;
import java.io.IOException;
import java.io.InputStream;
@@ -52,22 +53,54 @@
return mGalResult.total > 0;
}
- public void parseProperties(GalResult galResult) throws IOException {
- String displayName = null;
- String email = null;
- while (nextTag(Tags.SEARCH_STORE) != END) {
- if (tag == Tags.GAL_DISPLAY_NAME) {
- displayName = getValue();
- } else if (tag == Tags.GAL_EMAIL_ADDRESS) {
- email = getValue();
- } else {
- skipTag();
- }
- }
- if (displayName != null && email != null) {
- galResult.addGalData(0, displayName, email);
- }
- }
+ public void parseProperties(GalResult galResult) throws IOException {
+ GalData galData = new GalData();
+ while (nextTag(Tags.SEARCH_STORE) != END) {
+ switch(tag) {
+ // Display name and email address use both legacy and new code for galData
+ case Tags.GAL_DISPLAY_NAME:
+ String displayName = getValue();
+ galData.put(GalData.DISPLAY_NAME, displayName);
+ galData.displayName = displayName;
+ break;
+ case Tags.GAL_EMAIL_ADDRESS:
+ String emailAddress = getValue();
+ galData.put(GalData.EMAIL_ADDRESS, emailAddress);
+ galData.emailAddress = emailAddress;
+ break;
+ case Tags.GAL_PHONE:
+ galData.put(GalData.WORK_PHONE, getValue());
+ break;
+ case Tags.GAL_OFFICE:
+ galData.put(GalData.OFFICE, getValue());
+ break;
+ case Tags.GAL_TITLE:
+ galData.put(GalData.TITLE, getValue());
+ break;
+ case Tags.GAL_COMPANY:
+ galData.put(GalData.COMPANY, getValue());
+ break;
+ case Tags.GAL_ALIAS:
+ galData.put(GalData.ALIAS, getValue());
+ break;
+ case Tags.GAL_FIRST_NAME:
+ galData.put(GalData.FIRST_NAME, getValue());
+ break;
+ case Tags.GAL_LAST_NAME:
+ galData.put(GalData.LAST_NAME, getValue());
+ break;
+ case Tags.GAL_HOME_PHONE:
+ galData.put(GalData.HOME_PHONE, getValue());
+ break;
+ case Tags.GAL_MOBILE_PHONE:
+ galData.put(GalData.MOBILE_PHONE, getValue());
+ break;
+ default:
+ skipTag();
+ }
+ }
+ galResult.addGalData(galData);
+ }
public void parseResult(GalResult galResult) throws IOException {
while (nextTag(Tags.SEARCH_STORE) != END) {
diff --git a/src/com/android/exchange/provider/ExchangeDirectoryProvider.java b/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
index 5d6121f..6d68522 100644
--- a/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
+++ b/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
@@ -16,6 +16,7 @@
package com.android.exchange.provider;
+import com.android.email.mail.PackedString;
import com.android.email.provider.EmailContent.Account;
import com.android.exchange.EasSyncService;
import com.android.exchange.SyncManager;
@@ -29,8 +30,16 @@
import android.net.Uri;
import android.os.Binder;
import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.Data;
import android.provider.ContactsContract.RawContacts;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+import java.util.List;
/**
* ExchangeDirectoryProvider provides real-time data from the Exchange server; at the moment, it is
@@ -39,13 +48,21 @@
public class ExchangeDirectoryProvider extends ContentProvider {
public static final String EXCHANGE_GAL_AUTHORITY = "com.android.exchange.directory.provider";
+ private static final int DEFAULT_CONTACT_ID = 1;
+
private static final int GAL_BASE = 0;
private static final int GAL_FILTER = GAL_BASE;
+ private static final int GAL_CONTACT = GAL_BASE + 1;
+ private static final int GAL_CONTACT_WITH_ID = GAL_BASE + 2;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/filter/*", GAL_FILTER);
+ sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/lookup/*/entities",
+ GAL_CONTACT);
+ sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/lookup/*/#/entities",
+ GAL_CONTACT_WITH_ID);
}
@Override
@@ -53,6 +70,96 @@
return true;
}
+ static class GalProjection {
+ final int size;
+ final HashMap<String, Integer> columnMap = new HashMap<String, Integer>();
+
+ GalProjection(String[] projection) {
+ size = projection.length;
+ for (int i = 0; i < projection.length; i++) {
+ columnMap.put(projection[i], i);
+ }
+ }
+ }
+
+ static class GalContactRow {
+ private final GalProjection mProjection;
+ private Object[] row;
+ static long dataId = 1;
+
+ GalContactRow(GalProjection projection, long contactId, String lookupKey,
+ String accountName, String displayName) {
+ this.mProjection = projection;
+ row = new Object[projection.size];
+
+ put(Contacts.Entity.CONTACT_ID, contactId);
+
+ // We only have one raw contact per aggregate, so they can have the same ID
+ put(Contacts.Entity.RAW_CONTACT_ID, contactId);
+ put(Contacts.Entity.DATA_ID, dataId++);
+
+ put(Contacts.DISPLAY_NAME, displayName);
+
+ // TODO alternative display name
+ put(Contacts.DISPLAY_NAME_ALTERNATIVE, displayName);
+
+ put(RawContacts.ACCOUNT_TYPE, com.android.email.Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
+ put(RawContacts.ACCOUNT_NAME, accountName);
+ put(RawContacts.RAW_CONTACT_IS_READ_ONLY, 1);
+ put(Data.IS_READ_ONLY, 1);
+ }
+
+ Object[] getRow () {
+ return row;
+ }
+
+ void put(String columnName, Object value) {
+ Integer integer = mProjection.columnMap.get(columnName);
+ if (integer != null) {
+ row[integer] = value;
+ } else {
+ System.out.println("Unsupported column: " + columnName);
+ }
+ }
+
+ static void addEmailAddress(MatrixCursor cursor, GalProjection galProjection,
+ long contactId, String lookupKey, String accountName, String displayName,
+ String address) {
+ if (!TextUtils.isEmpty(address)) {
+ GalContactRow r = new GalContactRow(
+ galProjection, contactId, lookupKey, accountName, displayName);
+ r.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ r.put(Email.TYPE, Email.TYPE_WORK);
+ r.put(Email.ADDRESS, address);
+ cursor.addRow(r.getRow());
+ }
+ }
+
+ static void addPhoneRow(MatrixCursor cursor, GalProjection projection, long contactId,
+ String lookupKey, String accountName, String displayName, int type, String number) {
+ if (!TextUtils.isEmpty(number)) {
+ GalContactRow r = new GalContactRow(
+ projection, contactId, lookupKey, accountName, displayName);
+ r.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ r.put(Phone.TYPE, type);
+ r.put(Phone.NUMBER, number);
+ cursor.addRow(r.getRow());
+ }
+ }
+
+ public static void addNameRow(MatrixCursor cursor, GalProjection galProjection,
+ long contactId, String lookupKey, String accountName, String displayName,
+ String firstName, String lastName) {
+ GalContactRow r = new GalContactRow(
+ galProjection, contactId, lookupKey, accountName, displayName);
+ r.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+ r.put(StructuredName.GIVEN_NAME, firstName);
+ r.put(StructuredName.FAMILY_NAME, lastName);
+ r.put(StructuredName.DISPLAY_NAME, displayName);
+ cursor.addRow(r.getRow());
+ }
+ }
+
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
@@ -67,8 +174,14 @@
}
int match = sURIMatcher.match(uri);
+ MatrixCursor cursor;
+ Object[] row;
+ PackedString ps;
+ List<String> pathSegments = uri.getPathSegments();
+ String lookupKey;
+
switch (match) {
- case GAL_FILTER:
+ case GAL_FILTER: {
String filter = uri.getLastPathSegment();
// We should have at least two characters before doing a GAL search
if (filter == null || filter.length() < 2) {
@@ -86,6 +199,31 @@
Binder.restoreCallingIdentity(callingId);
}
break;
+ }
+
+ case GAL_CONTACT:
+ case GAL_CONTACT_WITH_ID: {
+ GalProjection galProjection = new GalProjection(projection);
+ cursor = new MatrixCursor(projection);
+ // Handle the decomposition of the key into rows suitable for CP2
+ lookupKey = pathSegments.get(2);
+ long contactId = (match == GAL_CONTACT_WITH_ID)
+ ? Long.parseLong(pathSegments.get(3))
+ : DEFAULT_CONTACT_ID;
+ ps = new PackedString(lookupKey);
+ String displayName = ps.get(GalData.DISPLAY_NAME);
+ GalContactRow.addEmailAddress(cursor, galProjection, contactId, lookupKey,
+ accountName, displayName, ps.get(GalData.EMAIL_ADDRESS));
+ GalContactRow.addPhoneRow(cursor, galProjection, contactId, accountName,
+ displayName, displayName, Phone.TYPE_HOME, ps.get(GalData.HOME_PHONE));
+ GalContactRow.addPhoneRow(cursor, galProjection, contactId, accountName,
+ displayName, displayName, Phone.TYPE_WORK, ps.get(GalData.WORK_PHONE));
+ GalContactRow.addPhoneRow(cursor, galProjection, contactId, accountName,
+ displayName, displayName, Phone.TYPE_MOBILE, ps.get(GalData.MOBILE_PHONE));
+ GalContactRow.addNameRow(cursor, galProjection, contactId, accountName, displayName,
+ ps.get(GalData.FIRST_NAME), ps.get(GalData.LAST_NAME), displayName);
+ return cursor;
+ }
}
return null;
@@ -93,8 +231,10 @@
/*package*/ Cursor buildGalResultCursor(String[] projection, GalResult galResult) {
int displayNameIndex = -1;
+ int alternateDisplayNameIndex = -1;;
int emailIndex = -1;
- boolean alternateDisplayName = false;
+ int idIndex = -1;
+ int lookupIndex = -1;
for (int i = 0; i < projection.length; i++) {
String column = projection[i];
@@ -102,13 +242,14 @@
Contacts.DISPLAY_NAME_PRIMARY.equals(column)) {
displayNameIndex = i;
} else if (Contacts.DISPLAY_NAME_ALTERNATIVE.equals(column)) {
- displayNameIndex = i;
- alternateDisplayName = true;
-
+ alternateDisplayNameIndex = i;
} else if (CommonDataKinds.Email.ADDRESS.equals(column)) {
emailIndex = i;
+ } else if (Contacts._ID.equals(column)) {
+ idIndex = i;
+ } else if (Contacts.LOOKUP_KEY.equals(column)) {
+ lookupIndex = i;
}
- // TODO other fields
}
Object[] row = new Object[projection.length];
@@ -120,12 +261,43 @@
int count = galResult.galData.size();
for (int i = 0; i < count; i++) {
GalData galDataRow = galResult.galData.get(i);
+ String firstName = galDataRow.get(GalData.FIRST_NAME);
+ String lastName = galDataRow.get(GalData.LAST_NAME);
+ String displayName = galDataRow.get(GalData.DISPLAY_NAME);
+ // If we don't have a display name, try to create one using first and last name
+ if (displayName == null) {
+ if (firstName != null && lastName != null) {
+ displayName = firstName + " " + lastName;
+ } else if (firstName != null) {
+ displayName = firstName;
+ } else if (lastName != null) {
+ displayName = lastName;
+ }
+ }
+ galDataRow.put(GalData.DISPLAY_NAME, displayName);
+
if (displayNameIndex != -1) {
- row[displayNameIndex] = galDataRow.displayName;
- // TODO Handle alternate display name here
+ row[displayNameIndex] = displayName;
+ }
+ if (alternateDisplayNameIndex != -1) {
+ // Try to create an alternate display name, using first and last name
+ // TODO: Check with Contacts team to make sure we're using this properly
+ if (firstName != null && lastName != null) {
+ row[alternateDisplayNameIndex] = lastName + " " + firstName;
+ } else {
+ row[alternateDisplayNameIndex] = displayName;
+ }
}
if (emailIndex != -1) {
- row[emailIndex] = galDataRow.emailAddress;
+ row[emailIndex] = galDataRow.get(GalData.EMAIL_ADDRESS);
+ }
+ if (idIndex != -1) {
+ row[idIndex] = i + 1; // Let's be 1 based
+ }
+ if (lookupIndex != -1) {
+ // We use the packed string as our lookup key; it contains ALL of the gal data
+ // We do this because we are not able to provide a stable id to ContactsProvider
+ row[lookupIndex] = Uri.encode(galDataRow.toPackedString());
}
cursor.addRow(row);
}
diff --git a/src/com/android/exchange/provider/GalResult.java b/src/com/android/exchange/provider/GalResult.java
index 3700622..e7f2477 100644
--- a/src/com/android/exchange/provider/GalResult.java
+++ b/src/com/android/exchange/provider/GalResult.java
@@ -15,6 +15,8 @@
package com.android.exchange.provider;
+import com.android.email.mail.PackedString;
+
import java.util.ArrayList;
/**
@@ -29,19 +31,64 @@
public GalResult() {
}
+ /**
+ * Legacy method for email address autocomplete
+ */
public void addGalData(long id, String displayName, String emailAddress) {
galData.add(new GalData(id, displayName, emailAddress));
}
- public static class GalData {
- final long _id;
- final String displayName;
- final String emailAddress;
+ public void addGalData(GalData data) {
+ galData.add(data);
+ }
+ public static class GalData {
+ // PackedString constants for GalData
+ public static final String ID = "_id";
+ public static final String DISPLAY_NAME = "displayName";
+ public static final String EMAIL_ADDRESS = "emailAddress";
+ public static final String WORK_PHONE = "workPhone";
+ public static final String HOME_PHONE = "homePhone";
+ public static final String MOBILE_PHONE = "mobilePhone";
+ public static final String FIRST_NAME = "firstName";
+ public static final String LAST_NAME = "lastName";
+ public static final String COMPANY = "company";
+ public static final String TITLE = "title";
+ public static final String OFFICE = "office";
+ public static final String ALIAS = "alias";
+ // The Builder we use to construct the PackedString
+ PackedString.Builder builder = new PackedString.Builder();
+
+ // The following three fields are for legacy email autocomplete
+ public long _id = 0;
+ public String displayName;
+ public String emailAddress;
+
+ /**
+ * Legacy constructor for email address autocomplete
+ */
private GalData(long id, String _displayName, String _emailAddress) {
+ put(ID, Long.toString(id));
_id = id;
+ put(DISPLAY_NAME, _displayName);
displayName = _displayName;
+ put(EMAIL_ADDRESS, _emailAddress);
emailAddress = _emailAddress;
}
+
+ public GalData() {
+ }
+
+ public String get(String field) {
+ return builder.get(field);
+ }
+
+ public void put(String field, String value) {
+ builder.put(field, value);
+ }
+
+ public String toPackedString() {
+ return builder.toString();
+ }
}
}
diff --git a/tests/src/com/android/exchange/provider/ExchangeDirectoryProviderTests.java b/tests/src/com/android/exchange/provider/ExchangeDirectoryProviderTests.java
index a623aed..e1dc223 100644
--- a/tests/src/com/android/exchange/provider/ExchangeDirectoryProviderTests.java
+++ b/tests/src/com/android/exchange/provider/ExchangeDirectoryProviderTests.java
@@ -16,6 +16,7 @@
package com.android.exchange.provider;
+import com.android.email.mail.PackedString;
import com.android.exchange.provider.GalResult.GalData;
import android.database.Cursor;
@@ -32,18 +33,19 @@
// Create a test projection; we should only get back values for display name and email address
private static final String[] GAL_RESULT_PROJECTION =
- new String[] {Contacts.DISPLAY_NAME, CommonDataKinds.Email.ADDRESS, Contacts.CONTENT_TYPE};
- private static final int GAL_RESULT_DISPLAY_NAME_COLUMN = 0;
- private static final int GAL_RESULT_EMAIL_ADDRESS_COLUMN = 1;
- private static final int GAL_RESULT_CONTENT_TYPE_COLUMN = 2;
+ new String[] {Contacts.DISPLAY_NAME, CommonDataKinds.Email.ADDRESS, Contacts.CONTENT_TYPE,
+ Contacts.LOOKUP_KEY};
+ private static final int GAL_RESULT_COLUMN_DISPLAY_NAME = 0;
+ private static final int GAL_RESULT_COLUMN_EMAIL_ADDRESS = 1;
+ private static final int GAL_RESULT_COLUMN_CONTENT_TYPE = 2;
+ private static final int GAL_RESULT_COLUMN_LOOKUP_KEY = 3;
- public void testBuildGalResultCursor() {
+ public void testBuildSimpleGalResultCursor() {
GalResult result = new GalResult();
result.addGalData(1, "Alice Aardvark", "alice@aardvark.com");
result.addGalData(2, "Bob Badger", "bob@badger.com");
result.addGalData(3, "Clark Cougar", "clark@cougar.com");
result.addGalData(4, "Dan Dolphin", "dan@dolphin.com");
-
// Make sure our returned cursor has the expected contents
ExchangeDirectoryProvider provider = new ExchangeDirectoryProvider();
Cursor c = provider.buildGalResultCursor(GAL_RESULT_PROJECTION, result);
@@ -53,9 +55,67 @@
for (int i = 0; i < 4; i++) {
GalData data = result.galData.get(i);
assertTrue(c.moveToNext());
- assertEquals(data.displayName, c.getString(GAL_RESULT_DISPLAY_NAME_COLUMN));
- assertEquals(data.emailAddress, c.getString(GAL_RESULT_EMAIL_ADDRESS_COLUMN));
- assertNull(c.getString(GAL_RESULT_CONTENT_TYPE_COLUMN));
+ assertEquals(data.displayName, c.getString(GAL_RESULT_COLUMN_DISPLAY_NAME));
+ assertEquals(data.emailAddress, c.getString(GAL_RESULT_COLUMN_EMAIL_ADDRESS));
+ assertNull(c.getString(GAL_RESULT_COLUMN_CONTENT_TYPE));
+ }
+ }
+
+ private static final String[][] DISPLAY_NAME_TEST_FIELDS = {
+ {"Alice", "Aardvark", "Another Name"},
+ {"Alice", "Aardvark", null},
+ {"Alice", null, null},
+ {null, "Aardvark", null},
+ {null, null, null}
+ };
+ private static final int TEST_FIELD_FIRST_NAME = 0;
+ private static final int TEST_FIELD_LAST_NAME = 1;
+ private static final int TEST_FIELD_DISPLAY_NAME = 2;
+ private static final String[] EXPECTED_DISPLAY_NAMES = new String[] {"Another Name",
+ "Alice Aardvark", "Alice", "Aardvark", null};
+
+ private GalResult getTestDisplayNameResult() {
+ GalResult result = new GalResult();
+ for (int i = 0; i < DISPLAY_NAME_TEST_FIELDS.length; i++) {
+ GalData galData = new GalData();
+ String[] names = DISPLAY_NAME_TEST_FIELDS[i];
+ galData.put(GalData.FIRST_NAME, names[TEST_FIELD_FIRST_NAME]);
+ galData.put(GalData.LAST_NAME, names[TEST_FIELD_LAST_NAME]);
+ galData.put(GalData.DISPLAY_NAME, names[TEST_FIELD_DISPLAY_NAME]);
+ result.addGalData(galData);
+ }
+ return result;
+ }
+
+ public void testDisplayNameLogic() {
+ GalResult result = getTestDisplayNameResult();
+ // Make sure our returned cursor has the expected contents
+ ExchangeDirectoryProvider provider = new ExchangeDirectoryProvider();
+ Cursor c = provider.buildGalResultCursor(GAL_RESULT_PROJECTION, result);
+ assertNotNull(c);
+ assertEquals(MatrixCursor.class, c.getClass());
+ assertEquals(DISPLAY_NAME_TEST_FIELDS.length, c.getCount());
+ for (int i = 0; i < EXPECTED_DISPLAY_NAMES.length; i++) {
+ assertTrue(c.moveToNext());
+ assertEquals(EXPECTED_DISPLAY_NAMES[i], c.getString(GAL_RESULT_COLUMN_DISPLAY_NAME));
+ }
+ }
+
+ public void testLookupKeyLogic() {
+ GalResult result = getTestDisplayNameResult();
+ // Make sure our returned cursor has the expected contents
+ ExchangeDirectoryProvider provider = new ExchangeDirectoryProvider();
+ Cursor c = provider.buildGalResultCursor(GAL_RESULT_PROJECTION, result);
+ assertNotNull(c);
+ assertEquals(MatrixCursor.class, c.getClass());
+ assertEquals(DISPLAY_NAME_TEST_FIELDS.length, c.getCount());
+ for (int i = 0; i < EXPECTED_DISPLAY_NAMES.length; i++) {
+ assertTrue(c.moveToNext());
+ PackedString ps = new PackedString(c.getString(GAL_RESULT_COLUMN_LOOKUP_KEY));
+ String[] testFields = DISPLAY_NAME_TEST_FIELDS[i];
+ assertEquals(testFields[TEST_FIELD_FIRST_NAME], ps.get(GalData.FIRST_NAME));
+ assertEquals(testFields[TEST_FIELD_LAST_NAME], ps.get(GalData.LAST_NAME));
+ assertEquals(EXPECTED_DISPLAY_NAMES[i], ps.get(GalData.DISPLAY_NAME));
}
}
}