Preliminary GAL/Contacts integration for EAS

Change-Id: I9997ac96f83f427c71caf12d591ba6069bedf935
This commit is contained in:
Marc Blank 2010-06-13 17:39:15 -07:00
parent 1a3a3a5aec
commit 5bd2faee5e
6 changed files with 273 additions and 5 deletions

View File

@ -339,12 +339,20 @@
<!--EXCHANGE-REMOVE-SECTION-START-->
<!-- In this release, GAL information is used locally only, so we used the same
strict permissions. -->
<!-- NOTE: ExchangeGalProvider will replace ExchangeProvider after integration with
the new GAL/contacts implementation -->
<provider
android:name="com.android.exchange.provider.ExchangeProvider"
android:authorities="com.android.exchange.provider"
android:multiprocess="true"
android:permission="com.android.email.permission.ACCESS_PROVIDER"
/>
<provider
android:name="com.android.exchange.provider.ExchangeGalProvider"
android:authorities="com.android.exchange.gal.provider"
android:readPermission="android.permission.READ_CONTACTS"
android:multiprocess="false"
/>
<!--EXCHANGE-REMOVE-SECTION-END-->
</application>

View File

@ -17,7 +17,10 @@
package com.android.email.service;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.VendorPolicyLoader;
import com.android.email.activity.setup.AccountSetupBasics;
import com.android.exchange.provider.ExchangeDirectoryProvider;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
@ -26,12 +29,14 @@ import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Calendar;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Directory;
/**
* A very basic authenticator service for EAS. At the moment, it has no UI hooks. When called
@ -45,8 +50,11 @@ public class EasAuthenticatorService extends Service {
public static final String OPTIONS_CALENDAR_SYNC_ENABLED = "calendar";
class EasAuthenticator extends AbstractAccountAuthenticator {
private Context mContext;
public EasAuthenticator(Context context) {
super(context);
mContext = context;
}
@Override
@ -83,6 +91,21 @@ public class EasAuthenticatorService extends Service {
ContentResolver.setIsSyncable(account, Calendar.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, Calendar.AUTHORITY, syncCalendar);
// Register our GAL provider
ContentValues values = new ContentValues();
values.put(Directory.DIRECTORY_AUTHORITY,
ExchangeDirectoryProvider.EXCHANGE_GAL_AUTHORITY);
values.put(Directory.ACCOUNT_NAME, account.name);
values.put(Directory.ACCOUNT_TYPE, account.type);
values.put(Directory.PACKAGE_NAME, mContext.getPackageName());
if (VendorPolicyLoader.getInstance(EasAuthenticatorService.this)
.useAlternateExchangeStrings()) {
values.put(Directory.TYPE_RESOURCE_ID, R.string.exchange_name_alternate);
} else {
values.put(Directory.TYPE_RESOURCE_ID, R.string.exchange_name);
}
values.put(Directory.DISPLAY_NAME, account.name);
values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY);
Bundle b = new Bundle();
b.putString(AccountManager.KEY_ACCOUNT_NAME, options.getString(OPTIONS_USERNAME));
b.putString(AccountManager.KEY_ACCOUNT_TYPE, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
@ -144,10 +167,7 @@ public class EasAuthenticatorService extends Service {
@Override
public IBinder onBind(Intent intent) {
// TODO Replace this with an appropriate constant in AccountManager, when it's created
String authenticatorIntent = "android.accounts.AccountAuthenticator";
if (authenticatorIntent.equals(intent.getAction())) {
if (AccountManager.ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())) {
return new EasAuthenticator(this).getIBinder();
} else {
return null;

View File

@ -788,7 +788,7 @@ public class EasSyncService extends AbstractSyncService {
* @param context caller's context
* @param accountId the account Id to search
* @param filter the characters entered so far
* @return a result record or null
* @return a result record or null for no data
*
* TODO: shorter timeout for interactive lookup
* TODO: make watchdog actually work (it doesn't understand our service w/Mailbox == 0)

View File

@ -420,6 +420,15 @@ public class SyncManager extends Service implements Runnable {
}
return null;
}
public Account getByName(String accountName) {
for (Account account : this) {
if (account.mEmailAddress.equalsIgnoreCase(accountName)) {
return account;
}
}
return null;
}
}
class AccountObserver extends ContentObserver {
@ -837,6 +846,17 @@ public class SyncManager extends Service implements Runnable {
return null;
}
static public Account getAccountByName(String accountName) {
SyncManager syncManager = INSTANCE;
if (syncManager != null) {
AccountList accountList = syncManager.mAccountList;
synchronized (accountList) {
return accountList.getByName(accountName);
}
}
return null;
}
static public String getEasAccountSelector() {
SyncManager syncManager = INSTANCE;
if (syncManager != null && syncManager.mAccountObserver != null) {

View File

@ -0,0 +1,159 @@
/*
* 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.provider.EmailContent.Account;
import com.android.exchange.EasSyncService;
import com.android.exchange.SyncManager;
import com.android.exchange.provider.GalResult.GalData;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Binder;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.RawContacts;
/**
* 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.gal.provider";
private static final int GAL_BASE = 0;
private static final int GAL_FILTER = GAL_BASE;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(EXCHANGE_GAL_AUTHORITY, "contacts/filter/*", GAL_FILTER);
}
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
if (accountName == null) {
return null;
}
Account account = SyncManager.getAccountByName(accountName);
if (account == null) {
return null;
}
int match = sURIMatcher.match(uri);
switch (match) {
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) {
return null;
}
long callingId = Binder.clearCallingIdentity();
try {
// Get results from the Exchange account
GalResult galResult = EasSyncService.searchGal(getContext(), account.mId,
filter);
if (galResult != null) {
return buildGalResultCursor(projection, galResult);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
break;
}
return null;
}
/*package*/ Cursor buildGalResultCursor(String[] projection, GalResult galResult) {
int displayNameIndex = -1;
int emailIndex = -1;
boolean alternateDisplayName = false;
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)) {
displayNameIndex = i;
alternateDisplayName = true;
} else if (CommonDataKinds.Email.ADDRESS.equals(column)) {
emailIndex = i;
}
// TODO other fields
}
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);
if (displayNameIndex != -1) {
row[displayNameIndex] = galDataRow.displayName;
// TODO Handle alternate display name here
}
if (emailIndex != -1) {
row[emailIndex] = galDataRow.emailAddress;
}
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();
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.exchange.provider.GalResult.GalData;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.test.AndroidTestCase;
/**
* You can run this entire test case with:
* runtest -c com.android.exchange.provider.ExchangeDirectoryProviderTests email
*/
public class ExchangeDirectoryProviderTests extends AndroidTestCase {
// 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;
public void testBuildGalResultCursor() {
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);
assertNotNull(c);
assertEquals(MatrixCursor.class, c.getClass());
assertEquals(4, c.getCount());
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));
}
}
}