432 lines
18 KiB
Java
432 lines
18 KiB
Java
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.exchange.provider;
|
|
|
|
import com.android.email.R;
|
|
import com.android.email.Utility;
|
|
import com.android.email.VendorPolicyLoader;
|
|
import com.android.email.provider.EmailContent;
|
|
import com.android.email.provider.EmailContent.Account;
|
|
import com.android.email.provider.EmailContent.AccountColumns;
|
|
import com.android.emailcommon.mail.PackedString;
|
|
import com.android.exchange.EasSyncService;
|
|
import com.android.exchange.provider.GalResult.GalData;
|
|
|
|
import android.accounts.AccountManager;
|
|
import android.content.ContentProvider;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.UriMatcher;
|
|
import android.database.Cursor;
|
|
import android.database.MatrixCursor;
|
|
import android.net.Uri;
|
|
import android.os.Binder;
|
|
import android.provider.ContactsContract;
|
|
import android.provider.ContactsContract.CommonDataKinds;
|
|
import android.provider.ContactsContract.Contacts;
|
|
import android.provider.ContactsContract.Directory;
|
|
import android.provider.ContactsContract.RawContacts;
|
|
import android.provider.ContactsContract.CommonDataKinds.Email;
|
|
import android.provider.ContactsContract.CommonDataKinds.Phone;
|
|
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
|
|
import android.provider.ContactsContract.Contacts.Data;
|
|
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
|
|
* used solely to provide GAL (Global Address Lookup) service to email address adapters
|
|
*/
|
|
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 DEFAULT_LOOKUP_LIMIT = 20;
|
|
|
|
private static final int GAL_BASE = 0;
|
|
private static final int GAL_DIRECTORIES = GAL_BASE;
|
|
private static final int GAL_FILTER = GAL_BASE + 1;
|
|
private static final int GAL_CONTACT = GAL_BASE + 2;
|
|
private static final int GAL_CONTACT_WITH_ID = GAL_BASE + 3;
|
|
private static final int GAL_EMAIL_FILTER = GAL_BASE + 4;
|
|
|
|
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
|
/*package*/ final HashMap<String, Long> mAccountIdMap = new HashMap<String, Long>();
|
|
|
|
static {
|
|
sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "directories", GAL_DIRECTORIES);
|
|
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);
|
|
sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "data/emails/filter/*", GAL_EMAIL_FILTER);
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreate() {
|
|
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());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find the record id of an Account, given its name (email address)
|
|
* @param accountName the name of the account
|
|
* @return the record id of the Account, or -1 if not found
|
|
*/
|
|
/*package*/ long getAccountIdByName(Context context, String accountName) {
|
|
Long accountId = mAccountIdMap.get(accountName);
|
|
if (accountId == null) {
|
|
accountId = Utility.getFirstRowLong(context, Account.CONTENT_URI,
|
|
EmailContent.ID_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?",
|
|
new String[] {accountName}, null, EmailContent.ID_PROJECTION_COLUMN , -1L);
|
|
if (accountId != -1) {
|
|
mAccountIdMap.put(accountName, accountId);
|
|
}
|
|
}
|
|
return accountId;
|
|
}
|
|
|
|
@Override
|
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
|
String sortOrder) {
|
|
int match = sURIMatcher.match(uri);
|
|
MatrixCursor cursor;
|
|
Object[] row;
|
|
PackedString ps;
|
|
String lookupKey;
|
|
|
|
switch (match) {
|
|
case GAL_DIRECTORIES: {
|
|
// Assuming that GAL can be used with all exchange accounts
|
|
android.accounts.Account[] accounts = AccountManager.get(getContext())
|
|
.getAccountsByType(com.android.email.Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
|
|
cursor = new MatrixCursor(projection);
|
|
if (accounts != null) {
|
|
for (android.accounts.Account account : accounts) {
|
|
row = new Object[projection.length];
|
|
|
|
for (int i = 0; i < projection.length; i++) {
|
|
String column = projection[i];
|
|
if (column.equals(Directory.ACCOUNT_NAME)) {
|
|
row[i] = account.name;
|
|
} else if (column.equals(Directory.ACCOUNT_TYPE)) {
|
|
row[i] = account.type;
|
|
} else if (column.equals(Directory.TYPE_RESOURCE_ID)) {
|
|
if (VendorPolicyLoader.getInstance(getContext())
|
|
.useAlternateExchangeStrings()) {
|
|
row[i] = R.string.exchange_name_alternate;
|
|
} else {
|
|
row[i] = R.string.exchange_name;
|
|
}
|
|
} else if (column.equals(Directory.DISPLAY_NAME)) {
|
|
// If the account name is an email address, extract
|
|
// the domain name and use it as the directory display name
|
|
final String accountName = account.name;
|
|
int atIndex = accountName.indexOf('@');
|
|
if (atIndex != -1 && atIndex < accountName.length() - 2) {
|
|
final char firstLetter = Character.toUpperCase(
|
|
accountName.charAt(atIndex + 1));
|
|
row[i] = firstLetter + accountName.substring(atIndex + 2);
|
|
} else {
|
|
row[i] = account.name;
|
|
}
|
|
} else if (column.equals(Directory.EXPORT_SUPPORT)) {
|
|
row[i] = Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY;
|
|
} else if (column.equals(Directory.SHORTCUT_SUPPORT)) {
|
|
row[i] = Directory.SHORTCUT_SUPPORT_NONE;
|
|
}
|
|
}
|
|
cursor.addRow(row);
|
|
}
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
case GAL_FILTER:
|
|
case GAL_EMAIL_FILTER: {
|
|
String filter = uri.getLastPathSegment();
|
|
// We should have at least two characters before doing a GAL search
|
|
if (filter == null || filter.length() < 2) {
|
|
return null;
|
|
}
|
|
|
|
String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
|
|
if (accountName == null) {
|
|
return null;
|
|
}
|
|
|
|
// Enforce a limit on the number of lookup responses
|
|
String limitString = uri.getQueryParameter(ContactsContract.LIMIT_PARAM_KEY);
|
|
int limit = DEFAULT_LOOKUP_LIMIT;
|
|
if (limitString != null) {
|
|
try {
|
|
limit = Integer.parseInt(limitString);
|
|
} catch (NumberFormatException e) {
|
|
limit = 0;
|
|
}
|
|
if (limit <= 0) {
|
|
throw new IllegalArgumentException("Limit not valid: " + limitString);
|
|
}
|
|
}
|
|
|
|
long callingId = Binder.clearCallingIdentity();
|
|
try {
|
|
// Find the account id to pass along to EasSyncService
|
|
long accountId = getAccountIdByName(getContext(), accountName);
|
|
if (accountId == -1) {
|
|
// The account was deleted?
|
|
return null;
|
|
}
|
|
|
|
// Get results from the Exchange account
|
|
GalResult galResult = EasSyncService.searchGal(getContext(), accountId,
|
|
filter, limit);
|
|
if (galResult != null) {
|
|
return buildGalResultCursor(projection, galResult);
|
|
}
|
|
} finally {
|
|
Binder.restoreCallingIdentity(callingId);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GAL_CONTACT:
|
|
case GAL_CONTACT_WITH_ID: {
|
|
String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
|
|
if (accountName == null) {
|
|
return null;
|
|
}
|
|
|
|
GalProjection galProjection = new GalProjection(projection);
|
|
cursor = new MatrixCursor(projection);
|
|
// Handle the decomposition of the key into rows suitable for CP2
|
|
List<String> pathSegments = uri.getPathSegments();
|
|
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;
|
|
}
|
|
|
|
/*package*/ Cursor buildGalResultCursor(String[] projection, GalResult galResult) {
|
|
int displayNameIndex = -1;
|
|
int alternateDisplayNameIndex = -1;;
|
|
int emailIndex = -1;
|
|
int idIndex = -1;
|
|
int lookupIndex = -1;
|
|
|
|
for (int i = 0; i < projection.length; i++) {
|
|
String column = projection[i];
|
|
if (Contacts.DISPLAY_NAME.equals(column) ||
|
|
Contacts.DISPLAY_NAME_PRIMARY.equals(column)) {
|
|
displayNameIndex = i;
|
|
} else if (Contacts.DISPLAY_NAME_ALTERNATIVE.equals(column)) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
Object[] row = new Object[projection.length];
|
|
|
|
/*
|
|
* ContactsProvider will ensure that every request has a non-null projection.
|
|
*/
|
|
MatrixCursor cursor = new MatrixCursor(projection);
|
|
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] = 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.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);
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
@Override
|
|
public String getType(Uri uri) {
|
|
int match = sURIMatcher.match(uri);
|
|
switch (match) {
|
|
case GAL_FILTER:
|
|
return Contacts.CONTENT_ITEM_TYPE;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public Uri insert(Uri uri, ContentValues values) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
}
|