diff --git a/AndroidManifest.xml b/AndroidManifest.xml index bdcd4c829..6d4ff986b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -339,12 +339,20 @@ + + diff --git a/src/com/android/email/service/EasAuthenticatorService.java b/src/com/android/email/service/EasAuthenticatorService.java index d8cf13caa..6608d4e4b 100644 --- a/src/com/android/email/service/EasAuthenticatorService.java +++ b/src/com/android/email/service/EasAuthenticatorService.java @@ -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; diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index f58604c50..aeeb90b71 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -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) diff --git a/src/com/android/exchange/SyncManager.java b/src/com/android/exchange/SyncManager.java index f2d6e57aa..890a99a5f 100644 --- a/src/com/android/exchange/SyncManager.java +++ b/src/com/android/exchange/SyncManager.java @@ -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) { diff --git a/src/com/android/exchange/provider/ExchangeDirectoryProvider.java b/src/com/android/exchange/provider/ExchangeDirectoryProvider.java new file mode 100644 index 000000000..fde34df75 --- /dev/null +++ b/src/com/android/exchange/provider/ExchangeDirectoryProvider.java @@ -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(); + } +} diff --git a/tests/src/com/android/exchange/provider/ExchangeDirectoryProviderTests.java b/tests/src/com/android/exchange/provider/ExchangeDirectoryProviderTests.java new file mode 100644 index 000000000..a623aed13 --- /dev/null +++ b/tests/src/com/android/exchange/provider/ExchangeDirectoryProviderTests.java @@ -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)); + } + } +}