From 17250429db16553b59d5df5e358f71406dd2b322 Mon Sep 17 00:00:00 2001 From: Andrew Stadler Date: Tue, 7 Jul 2009 09:39:11 -0700 Subject: [PATCH] Various EAS related changes related to accounts and services. * Renamed ISyncManager/ISyncManagerCallback to IEmailService/IEmailServiceCallback * Restored ExchangeTransportExample to its original state; created ExchangeStore to handle validation functionality instead; updated stores.xml to reflect these changes. * Add support for AccountManager in EAS code (this is necessary for the contacts and calendar providers to work with syncable data); created EasAuthenticatorService to as our authenticator, which required adding authenticator.xml and modifying the manifest to register our service with AccountManager metadata * Created EmailServiceProxy as a convenience for the UI in calling into the EAS service; created EmailServiceStatus class for status codes in callbacks. --- Android.mk | 5 +- AndroidManifest.xml | 10 + res/xml/authenticator.xml | 27 + res/xml/stores.xml | 4 +- .../email/activity/FolderMessageList.java | 12 +- .../email/activity/MessageListItem.java | 2 +- .../exchange/ExchangeTransportExample.java | 54 +- .../email/mail/store/ExchangeStore.java | 497 ++++++++++++++++++ .../service/EasAuthenticatorService.java | 105 ++++ .../email/service/EmailServiceProxy.java | 126 +++++ src/com/android/exchange/Eas.java | 4 +- src/com/android/exchange/EasSyncService.java | 2 +- .../android/exchange/EmailServiceStatus.java | 35 ++ .../{ISyncManager.aidl => IEmailService.aidl} | 19 +- ...llback.aidl => IEmailServiceCallback.aidl} | 4 +- .../exchange/InteractiveSyncService.java | 2 +- src/com/android/exchange/SyncManager.java | 42 +- .../exchange/adapter/EasFolderSyncParser.java | 1 + 18 files changed, 852 insertions(+), 99 deletions(-) create mode 100644 res/xml/authenticator.xml create mode 100644 src/com/android/email/mail/store/ExchangeStore.java create mode 100644 src/com/android/email/service/EasAuthenticatorService.java create mode 100644 src/com/android/email/service/EmailServiceProxy.java create mode 100644 src/com/android/exchange/EmailServiceStatus.java rename src/com/android/exchange/{ISyncManager.aidl => IEmailService.aidl} (74%) rename src/com/android/exchange/{ISyncManagerCallback.aidl => IEmailServiceCallback.aidl} (88%) diff --git a/Android.mk b/Android.mk index 0572f02da..277d0e5a3 100644 --- a/Android.mk +++ b/Android.mk @@ -17,8 +17,9 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_SRC_FILES += \ - src/com/android/exchange/ISyncManager.aidl \ - src/com/android/exchange/ISyncManagerCallback.aidl + src/com/android/exchange/IEmailService.aidl \ + src/com/android/exchange/IEmailServiceCallback.aidl + LOCAL_PACKAGE_NAME := Email diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 21d18ebad..a49bfbac8 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -178,6 +178,16 @@ android:enabled="true" > + + + + + + + + + + + + + + + diff --git a/res/xml/stores.xml b/res/xml/stores.xml index d362a8e4c..98ef3cede 100644 --- a/res/xml/stores.xml +++ b/res/xml/stores.xml @@ -35,6 +35,6 @@ - + diff --git a/src/com/android/email/activity/FolderMessageList.java b/src/com/android/email/activity/FolderMessageList.java index fed9b23f6..a1f1eb19e 100644 --- a/src/com/android/email/activity/FolderMessageList.java +++ b/src/com/android/email/activity/FolderMessageList.java @@ -33,6 +33,8 @@ import com.android.email.mail.Message.RecipientType; import com.android.email.mail.store.LocalStore; import com.android.email.mail.store.LocalStore.LocalMessage; import com.android.email.provider.EmailContent; +import com.android.email.provider.EmailContent.Mailbox; +import com.android.email.provider.EmailContent.MailboxColumns; import android.app.ExpandableListActivity; import android.app.NotificationManager; @@ -996,12 +998,14 @@ public class FolderMessageList extends ExpandableListActivity { // to match desired UI presentation (e.g. INBOX at the top) @Override protected Cursor doInBackground(Void... params) { + // Only show boxes with mail, and sort secondarily by name return FolderMessageList.this.managedQuery( - EmailContent.Mailbox.CONTENT_URI, - EmailContent.Mailbox.CONTENT_PROJECTION, - EmailContent.MailboxColumns.ACCOUNT_KEY + "=?", + Mailbox.CONTENT_URI, + Mailbox.CONTENT_PROJECTION, + MailboxColumns.ACCOUNT_KEY + "=? and " + + MailboxColumns.TYPE + '<' + Mailbox.TYPE_NOT_EMAIL, new String[] { String.valueOf(FolderMessageList.this.mAccountId) }, - EmailContent.MailboxColumns.TYPE); + MailboxColumns.TYPE + ',' + MailboxColumns.DISPLAY_NAME); } @Override diff --git a/src/com/android/email/activity/MessageListItem.java b/src/com/android/email/activity/MessageListItem.java index 179af60b3..25da63d95 100644 --- a/src/com/android/email/activity/MessageListItem.java +++ b/src/com/android/email/activity/MessageListItem.java @@ -82,7 +82,7 @@ public class MessageListItem extends RelativeLayout { int touchX = (int) event.getX(); if (!mCachedViewPositions) { - float paddingScale = mContext.getResources().getDisplayMetrics().density; + float paddingScale = getContext().getResources().getDisplayMetrics().density; int checkPadding = (int) ((CHECKMARK_PAD * paddingScale) + 0.5); int starPadding = (int) ((STAR_PAD * paddingScale) + 0.5); mCheckRight = findViewById(R.id.selected).getRight() + checkPadding; diff --git a/src/com/android/email/mail/exchange/ExchangeTransportExample.java b/src/com/android/email/mail/exchange/ExchangeTransportExample.java index a023f3c94..8320dc921 100644 --- a/src/com/android/email/mail/exchange/ExchangeTransportExample.java +++ b/src/com/android/email/mail/exchange/ExchangeTransportExample.java @@ -17,18 +17,9 @@ package com.android.email.mail.exchange; import com.android.email.Email; -import com.android.email.mail.AuthenticationFailedException; import com.android.email.mail.MessagingException; -import com.android.exchange.ISyncManager; -import com.android.exchange.SyncManager; -//import com.android.exchange.EasService; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.os.RemoteException; import android.text.TextUtils; import java.net.URI; @@ -44,6 +35,7 @@ public class ExchangeTransportExample { public static final String FOLDER_INBOX = Email.INBOX; + private static final String TAG = "ExchangeTransportExample"; private final Context mContext; private String mHost; @@ -115,52 +107,10 @@ public class ExchangeTransportExample { * @param uri the server/account to try and connect to * @throws MessagingException thrown if the connection, server, account are not useable */ - ISyncManager mSyncManagerService = null; - - private ServiceConnection mSyncManagerConnection = new ServiceConnection () { - public void onServiceConnected(ComponentName name, IBinder binder) { - mSyncManagerService = ISyncManager.Stub.asInterface(binder); - } - public void onServiceDisconnected(ComponentName name) { - mSyncManagerService = null; - } - }; - public void checkSettings(URI uri) throws MessagingException { setUri(uri); - boolean ssl = uri.getScheme().contains("ssl+"); - try { - mContext.bindService(new Intent(mContext, SyncManager.class), mSyncManagerConnection, Context.BIND_AUTO_CREATE); - // TODO Is this the right way of waiting for a connection?? - int count = 0; - while ((count++ < 10) && (mSyncManagerService == null)) { - Thread.sleep(500); - } - if (mSyncManagerService == null) { - throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION); - } - // The result of validate is a MessagingException error code - // We use NO_ERROR to indicate a validated account - int result = mSyncManagerService.validate("eas", mHost, mUsername, mPassword, ssl ? 443 : 80, ssl); - if (result != MessagingException.NO_ERROR) { - if (result == MessagingException.AUTHENTICATION_FAILED) { - throw new AuthenticationFailedException("Authentication failed."); - } else { - throw new MessagingException(result); - } - } - } catch (RemoteException e) { - throw new MessagingException("Call to validate generated an exception", e); - } catch (InterruptedException e) { - } finally { - if (mSyncManagerService != null) { - mContext.unbindService(mSyncManagerConnection); - } else - throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION); - } - - } + } /** * Typical helper function: Return existence of a given folder diff --git a/src/com/android/email/mail/store/ExchangeStore.java b/src/com/android/email/mail/store/ExchangeStore.java new file mode 100644 index 000000000..2db897375 --- /dev/null +++ b/src/com/android/email/mail/store/ExchangeStore.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2009 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.email.mail.store; + +import com.android.email.Email; +import com.android.email.mail.AuthenticationFailedException; +import com.android.email.mail.FetchProfile; +import com.android.email.mail.Flag; +import com.android.email.mail.Folder; +import com.android.email.mail.Message; +import com.android.email.mail.MessageRetrievalListener; +import com.android.email.mail.MessagingException; +import com.android.email.mail.Store; +import com.android.email.mail.StoreSynchronizer; +import com.android.email.service.EmailServiceProxy; +import com.android.exchange.Eas; +import com.android.exchange.IEmailService; +import com.android.exchange.IEmailServiceCallback; +import com.android.exchange.SyncManager; +import com.android.exchange.EmailContent.Attachment; + +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.Future2; +import android.accounts.Future2Callback; +import android.accounts.OperationCanceledException; +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Config; +import android.util.Log; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; + +/** + * This is a placeholder for use in Exchange implementations. It is based on the notion of + * lightweight adapter classes for Store, Folder, and Sender, and a common facade for the common + * Transport code. + */ +public class ExchangeStore extends Store { + public static final String LOG_TAG = "ExchangeStore"; + + private final Context mContext; + private URI mUri; + private PersistentDataCallbacks mCallbacks; + + private final ExchangeTransport mTransport; + private final HashMap mFolders = new HashMap(); + + private boolean mPushModeRunning = false; + + /** + * Factory method. + */ + public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks) + throws MessagingException { + return new ExchangeStore(uri, context, callbacks); + } + + /** + * eas://user:password@server/domain + * + * @param _uri + * @param application + * @throws MessagingException + */ + private ExchangeStore(String _uri, Context context, PersistentDataCallbacks callbacks) + throws MessagingException { + mContext = context; + try { + mUri = new URI(_uri); + } catch (URISyntaxException e) { + throw new MessagingException("Invalid uri for ExchangeStore"); + } + mCallbacks = callbacks; + + String scheme = mUri.getScheme(); + int connectionSecurity; + if (scheme.equals("eas")) { + connectionSecurity = ExchangeTransport.CONNECTION_SECURITY_NONE; + } else if (scheme.equals("eas+ssl+")) { + connectionSecurity = ExchangeTransport.CONNECTION_SECURITY_SSL_REQUIRED; + } else { + throw new MessagingException("Unsupported protocol"); + } + + mTransport = ExchangeTransport.getInstance(mUri, context); + } + + /** + * Retrieve the underlying transport. Used primarily for testing. + * @return + */ + /* package */ ExchangeTransport getTransport() { + return mTransport; + } + + @Override + public void checkSettings() throws MessagingException { + mTransport.checkSettings(mUri); + } + + @Override + public Folder getFolder(String name) throws MessagingException { + synchronized (mFolders) { + Folder folder = mFolders.get(name); + if (folder == null) { + folder = new ExchangeFolder(this, name); + mFolders.put(folder.getName(), folder); + } + return folder; + } + } + + @Override + public Folder[] getPersonalNamespaces() throws MessagingException { + return new Folder[] { + getFolder(ExchangeTransport.FOLDER_INBOX), + }; + } + + /** + * For a store that supports push mode, this is the API that enables it or disables it. + * The store should use this API to start or stop its persistent connection service or thread. + * + *

Note, may be called multiple times, even after push mode has been started or stopped. + * + * @param enablePushMode start or stop push mode delivery + */ + @Override + public void enablePushModeDelivery(boolean enablePushMode) { + if (Config.LOGD && Email.DEBUG) { + if (enablePushMode && !mPushModeRunning) { + Log.d(Email.LOG_TAG, "start push mode"); + } else if (!enablePushMode && mPushModeRunning) { + Log.d(Email.LOG_TAG, "stop push mode"); + } else { + Log.d(Email.LOG_TAG, enablePushMode ? + "push mode already started" : "push mode already stopped"); + } + } + mPushModeRunning = enablePushMode; + } + + /** + * Get class of SettingActivity for this Store class. + * @return Activity class that has class method actionEditIncomingSettings() + */ + @Override + public Class getSettingActivityClass() { + return com.android.email.activity.setup.AccountSetupExchange.class; + } + + /** + * Get class of sync'er for this Store class. Because exchange Sync rules are so different + * than IMAP or POP3, it's likely that an Exchange implementation will need its own sync + * controller. If so, this function must return a non-null value. + * + * @return Message Sync controller, or null to use default + */ + @Override + public StoreSynchronizer getMessageSynchronizer() { + return null; + } + + /** + * Inform MessagingController that this store requires message structures to be prefetched + * before it can fetch message bodies (this is due to EAS protocol restrictions.) + * @return always true for EAS + */ + @Override + public boolean requireStructurePrefetch() { + return true; + } + + /** + * Inform MessagingController that messages sent via EAS will be placed in the Sent folder + * automatically (server-side) and don't need to be uploaded. + * @return always false for EAS (assuming server-side copy is supported) + */ + @Override + public boolean requireCopyMessageToSentFolder() { + return false; + } + + public static class ExchangeTransport { + public static final int CONNECTION_SECURITY_NONE = 0; + public static final int CONNECTION_SECURITY_SSL_REQUIRED = 1; + + public static final String FOLDER_INBOX = Email.INBOX; + + private static final String TAG = "ExchangeTransport"; + private final Context mContext; + + private String mHost; + private String mDomain; + private String mUsername; + private String mPassword; + + private static HashMap sUriToInstanceMap = + new HashMap(); + private static final HashMap sFolderMap = new HashMap(); + + /** + * Public factory. The transport should be a singleton (per Uri) + */ + public synchronized static ExchangeTransport getInstance(URI uri, Context context) + throws MessagingException { + if (!uri.getScheme().equals("eas") && !uri.getScheme().equals("eas+ssl+")) { + throw new MessagingException("Invalid scheme"); + } + + final String key = uri.toString(); + ExchangeTransport transport = sUriToInstanceMap.get(key); + if (transport == null) { + transport = new ExchangeTransport(uri, context); + sUriToInstanceMap.put(key, transport); + } + return transport; + } + + /** + * Private constructor - use public factory. + */ + private ExchangeTransport(URI uri, Context context) throws MessagingException { + mContext = context; + setUri(uri); + } + + /** + * Use the Uri to set up a newly-constructed transport + * @param uri + * @throws MessagingException + */ + private void setUri(final URI uri) throws MessagingException { + mHost = uri.getHost(); + if (mHost == null) { + throw new MessagingException("host not specified"); + } + + mDomain = uri.getPath(); + if (!TextUtils.isEmpty(mDomain)) { + mDomain = mDomain.substring(1); + } + + final String userInfo = uri.getUserInfo(); + if (userInfo == null) { + throw new MessagingException("user information not specifed"); + } + final String[] uinfo = userInfo.split(":", 2); + if (uinfo.length != 2) { + throw new MessagingException("user name and password not specified"); + } + mUsername = uinfo[0]; + mPassword = uinfo[1]; + } + + /** + * Blocking call that checks for a useable server connection, credentials, etc. + * @param uri the server/account to try and connect to + * @throws MessagingException thrown if the connection, server, account are not useable + */ + + IEmailServiceCallback mCallback = new IEmailServiceCallback () { + + public void status(int statusCode, int progress) throws RemoteException { + Log.d("Status: ", "Code = " + statusCode + ", progress = " + progress); + } + + public IBinder asBinder() { return null; } + }; + + /** + * Here's where we check the settings for EAS. + * @param uri the URI of the account to create + * @throws MessagingException if we can't authenticate the account + */ + public void checkSettings(URI uri) throws MessagingException { + setUri(uri); + boolean ssl = uri.getScheme().contains("ssl+"); + try { + IEmailService svc = EmailServiceProxy.getService(mContext, SyncManager.class); + int result = svc.validate("eas", mHost, mUsername, mPassword, ssl ? 443 : 80, ssl); + if (result != MessagingException.NO_ERROR) { + if (result == MessagingException.AUTHENTICATION_FAILED) { + throw new AuthenticationFailedException("Authentication failed."); + } else { + throw new MessagingException(result); + } + } else { + // This code was taken from sample code in AccountsTester + Bundle options = new Bundle(); + options.putString("username", mUsername); + options.putString("password", mPassword); + Future2Callback callback = new Future2Callback() { + public void run(Future2 future) { + try { + Bundle bundle = future.getResult(); + bundle.keySet(); + Log.d(TAG, "account added: " + bundle); + } catch (OperationCanceledException e) { + Log.d(TAG, "addAccount was canceled"); + } catch (IOException e) { + Log.d(TAG, "addAccount failed: " + e); + } catch (AuthenticatorException e) { + Log.d(TAG, "addAccount failed: " + e); + } + + } + }; + // Here's where we tell AccountManager about the new account. The addAccount + // method in AccountManager calls the addAccount method in our authenticator + // service (EasAuthenticatorService) + AccountManager.get(mContext).addAccount(Eas.ACCOUNT_MANAGER_TYPE, null, null, + options, null, callback, null); + } + svc.loadAttachment(0, new Attachment(), mCallback); + } catch (RemoteException e) { + throw new MessagingException("Call to validate generated an exception", e); + } + } + + /** + * Typical helper function: Return existence of a given folder + */ + public boolean isFolderAvailable(final String folder) { + return sFolderMap.containsKey(folder); + } + } + + public static class ExchangeFolder extends Folder { + + private final ExchangeTransport mTransport; + @SuppressWarnings("unused") + private final ExchangeStore mStore; + @SuppressWarnings("unused") + private final String mName; + + @SuppressWarnings("unused") + private PersistentDataCallbacks mPersistenceCallbacks; + + public ExchangeFolder(ExchangeStore store, String name) + throws MessagingException { + mStore = store; + mTransport = store.getTransport(); + mName = name; + if (!mTransport.isFolderAvailable(name)) { + throw new MessagingException("folder not supported: " + name); + } + } + + @Override + public void appendMessages(Message[] messages) throws MessagingException { + // TODO Implement this function + } + + @Override + public void close(boolean expunge) throws MessagingException { + mPersistenceCallbacks = null; + // TODO Implement this function + } + + @Override + public void copyMessages(Message[] msgs, Folder folder, MessageUpdateCallbacks callbacks) + throws MessagingException { + // TODO Implement this function + } + + @Override + public boolean create(FolderType type) throws MessagingException { + // TODO Implement this function + return false; + } + + @Override + public void delete(boolean recurse) throws MessagingException { + // TODO Implement this function + } + + @Override + public boolean exists() throws MessagingException { + // TODO Implement this function + return false; + } + + @Override + public Message[] expunge() throws MessagingException { + // TODO Implement this function + return null; + } + + @Override + public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) + throws MessagingException { + // TODO Implement this function + } + + @Override + public Message getMessage(String uid) throws MessagingException { + // TODO Implement this function + return null; + } + + @Override + public int getMessageCount() throws MessagingException { + // TODO Implement this function + return 0; + } + + @Override + public Message[] getMessages(int start, int end, MessageRetrievalListener listener) + throws MessagingException { + // TODO Implement this function + return null; + } + + @Override + public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException { + // TODO Implement this function + return null; + } + + @Override + public Message[] getMessages(String[] uids, MessageRetrievalListener listener) + throws MessagingException { + // TODO Implement this function + return null; + } + + @Override + public OpenMode getMode() throws MessagingException { + // TODO Implement this function + return null; + } + + @Override + public String getName() { + // TODO Implement this function + return null; + } + + @Override + public Flag[] getPermanentFlags() throws MessagingException { + // TODO Implement this function + return null; + } + + @Override + public int getUnreadMessageCount() throws MessagingException { + // TODO Implement this function + return 0; + } + + @Override + public boolean isOpen() { + // TODO Implement this function + return false; + } + + @Override + public void open(OpenMode mode, PersistentDataCallbacks callbacks) + throws MessagingException { + mPersistenceCallbacks = callbacks; + // TODO Implement this function + } + + @Override + public void setFlags(Message[] messages, Flag[] flags, boolean value) + throws MessagingException { + // TODO Implement this function + } + + + } + + +} + diff --git a/src/com/android/email/service/EasAuthenticatorService.java b/src/com/android/email/service/EasAuthenticatorService.java new file mode 100644 index 000000000..faee0f45e --- /dev/null +++ b/src/com/android/email/service/EasAuthenticatorService.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2009 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.email.service; + +import com.android.exchange.Eas; + +import android.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.accounts.Constants; +import android.accounts.NetworkErrorException; +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +/** + * A very basic authenticator service for EAS. At the moment, it has no UI hooks. When called + * with addAccount, it simply adds the account to AccountManager directly with a username and + * password. We will need to implement confirmPassword, confirmCredentials, and updateCredentials. + */ +public class EasAuthenticatorService extends Service { + + class EasAuthenticator extends AbstractAccountAuthenticator { + + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, + String authTokenType, String[] requiredFeatures, Bundle options) + throws NetworkErrorException { + // The Bundle we are passed has username and password set + AccountManager.get(EasAuthenticatorService.this).blockingAddAccountExplicitly( + new Account(options.getString("username"), Eas.ACCOUNT_MANAGER_TYPE), + options.getString("password"), null); + Bundle b = new Bundle(); + b.putString(Constants.ACCOUNT_NAME_KEY, options.getString("username")); + b.putString(Constants.ACCOUNT_TYPE_KEY, Eas.ACCOUNT_MANAGER_TYPE); + return b; + } + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean confirmPassword(AccountAuthenticatorResponse response, Account account, + String password) throws NetworkErrorException { + // TODO Auto-generated method stub + return false; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { + return null; + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, + String authTokenType, Bundle loginOptions) throws NetworkErrorException { + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, + String[] features) throws NetworkErrorException { + return null; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, + String authTokenType, Bundle loginOptions) { + // TODO Auto-generated method stub + return null; + } + + } + + @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())) { + return new EasAuthenticator().getIAccountAuthenticator().asBinder(); + } else { + return null; + } + } +} diff --git a/src/com/android/email/service/EmailServiceProxy.java b/src/com/android/email/service/EmailServiceProxy.java new file mode 100644 index 000000000..4ad0a124d --- /dev/null +++ b/src/com/android/email/service/EmailServiceProxy.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008-2009 Marc Blank + * Licensed to 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.email.service; + +import java.util.HashMap; + +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.exchange.IEmailService; + +/** + * Proxy for an IEmailService (remote email service); handles all connections to the service. + * All calls via the proxy are synchronous; the UI must ensure that these calls are running + * on appropriate background threads. + * + * A call to loadAttachment, for example, would look like this (assuming MyService is the service) + * EmailProxyService.getService(context, MyService.class).loadAttachment(..args..); + */ + +public class EmailServiceProxy { + + // Map associating a context and a proxy + static HashMap sProxyMap = + new HashMap(); + + // Map associating, for a given proxy, a class name (String) and a connected service + public HashMap serviceMap = + new HashMap(); + + public EmailServiceProxy () { + } + + class EmailServiceConnection implements ServiceConnection { + EmailServiceProxy mProxy; + + EmailServiceConnection (EmailServiceProxy proxy) { + mProxy = proxy; + } + + void setProxy (EmailServiceProxy proxy) { + mProxy = proxy; + } + + public void onServiceConnected(ComponentName name, IBinder binder) { + synchronized (mProxy) { + IEmailService service = IEmailService.Stub.asInterface(binder); + mProxy.serviceMap.put(name.getClassName(), service); + } + } + + public void onServiceDisconnected(ComponentName name) { + synchronized (mProxy) { + mProxy.serviceMap.remove(name.getClassName()); + } + } + } + + public ServiceConnection mSyncManagerConnection = new EmailServiceConnection (this); + + static public IEmailService getService(Context context, Class klass) + throws RemoteException { + String className = klass.getName(); + + // First, lets get the proxy for this context + // Make sure we're synchronized on the map + EmailServiceProxy proxy; + synchronized (sProxyMap) { + proxy = sProxyMap.get(context); + if (proxy == null) { + proxy = new EmailServiceProxy(); + sProxyMap.put(context, proxy); + } + } + + // Once we have the proxy, we need to synchronize working with its map, connect to the + // appropriate service (if not already connected) and return that service + synchronized (proxy) { + if (proxy.serviceMap.get(klass) == null) { + context.bindService(new Intent(context, klass), proxy.mSyncManagerConnection, + Context.BIND_AUTO_CREATE); + } + } + + // Wait up to 5 seconds for the connection + int count = 0; + IEmailService service = null; + while (count++ < 10) { + synchronized (proxy) { + service = proxy.serviceMap.get(className); + if (service != null) { + break; + } + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + + if (service == null) { + throw new RemoteException(); + } + return service; + } +} diff --git a/src/com/android/exchange/Eas.java b/src/com/android/exchange/Eas.java index 86b5419d5..9e5099a21 100644 --- a/src/com/android/exchange/Eas.java +++ b/src/com/android/exchange/Eas.java @@ -27,7 +27,9 @@ public class Eas { // For temporary use while debugging public static boolean TEST_DEBUG = false; // DO NOT CHECK IN WITH THIS SET TO TRUE - public static String VERSION = "0.1"; + public static final String VERSION = "0.1"; + + public static final String ACCOUNT_MANAGER_TYPE = "com.android.exchange"; // From EAS spec // Mail Cal diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index 22a0d375d..a5cf488fc 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -176,7 +176,7 @@ public class EasSyncService extends InteractiveSyncService { @Override - public void loadAttachment(Attachment att, ISyncManagerCallback cb) { + public void loadAttachment(Attachment att, IEmailServiceCallback cb) { // TODO Auto-generated method stub } diff --git a/src/com/android/exchange/EmailServiceStatus.java b/src/com/android/exchange/EmailServiceStatus.java new file mode 100644 index 000000000..2a58ebf53 --- /dev/null +++ b/src/com/android/exchange/EmailServiceStatus.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008-2009 Marc Blank + * Licensed to 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; + +/** + * Definitions of service status codes returned to IEmailServiceCallback's status method + */ +public interface EmailServiceStatus { + public static final int SUCCESS = 0; + public static final int IN_PROGRESS = 1; + + public static final int MESSAGE_NOT_FOUND = 0x10; + public static final int ATTACHMENT_NOT_FOUND = 0x11; + public static final int FOLDER_NOT_DELETED = 0x12; + public static final int FOLDER_NOT_RENAMED = 0x13; + public static final int FOLDER_NOT_CREATED = 0x14; + + // Maybe we should automatically retry these? + public static final int CONNECTION_ERROR = 0x20; +} diff --git a/src/com/android/exchange/ISyncManager.aidl b/src/com/android/exchange/IEmailService.aidl similarity index 74% rename from src/com/android/exchange/ISyncManager.aidl rename to src/com/android/exchange/IEmailService.aidl index 25665492e..13c86f0a3 100644 --- a/src/com/android/exchange/ISyncManager.aidl +++ b/src/com/android/exchange/IEmailService.aidl @@ -14,28 +14,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.exchange; -import com.android.exchange.ISyncManagerCallback; +import com.android.exchange.IEmailServiceCallback; import com.android.exchange.EmailContent; -interface ISyncManager { - int validate(in String protocol, in String host, in String userName, in String password, int port, boolean ssl) ; - - void registerCallback(ISyncManagerCallback cb); - void unregisterCallback(ISyncManagerCallback cb); +interface IEmailService { + int validate(in String protocol, in String host, in String userName, in String password, + int port, boolean ssl) ; boolean startSync(long mailboxId); boolean stopSync(long mailboxId); - boolean updateFolderList(long accountId); + boolean loadMore(long messageId, IEmailServiceCallback cb); + boolean loadAttachment(long messageId, in EmailContent.Attachment att, + IEmailServiceCallback cb); - boolean loadMore(long messageId, ISyncManagerCallback cb); - boolean loadAttachment(long messageId, in EmailContent.Attachment att, ISyncManagerCallback cb); + boolean updateFolderList(long accountId); boolean createFolder(long accountId, String name); boolean deleteFolder(long accountId, String name); boolean renameFolder(long accountId, String oldName, String newName); - //AddressLookup - real-time address lookup (EAS) } \ No newline at end of file diff --git a/src/com/android/exchange/ISyncManagerCallback.aidl b/src/com/android/exchange/IEmailServiceCallback.aidl similarity index 88% rename from src/com/android/exchange/ISyncManagerCallback.aidl rename to src/com/android/exchange/IEmailServiceCallback.aidl index 0046ad304..c3f367424 100644 --- a/src/com/android/exchange/ISyncManagerCallback.aidl +++ b/src/com/android/exchange/IEmailServiceCallback.aidl @@ -17,6 +17,6 @@ package com.android.exchange; -oneway interface ISyncManagerCallback { - void progress(int value); +oneway interface IEmailServiceCallback { + void status(int statusCode, int progress); } diff --git a/src/com/android/exchange/InteractiveSyncService.java b/src/com/android/exchange/InteractiveSyncService.java index 23b116fc4..d3109e764 100644 --- a/src/com/android/exchange/InteractiveSyncService.java +++ b/src/com/android/exchange/InteractiveSyncService.java @@ -46,5 +46,5 @@ public abstract class InteractiveSyncService extends AbstractSyncService { public abstract void reloadFolderList(); - public abstract void loadAttachment(Attachment att, ISyncManagerCallback cb); + public abstract void loadAttachment(Attachment att, IEmailServiceCallback cb); } diff --git a/src/com/android/exchange/SyncManager.java b/src/com/android/exchange/SyncManager.java index 906564f82..1af6d0cc2 100644 --- a/src/com/android/exchange/SyncManager.java +++ b/src/com/android/exchange/SyncManager.java @@ -49,9 +49,9 @@ import android.net.NetworkInfo; import android.net.Uri; import android.net.NetworkInfo.State; import android.os.Bundle; +import android.os.Debug; import android.os.Handler; import android.os.PowerManager; -import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.PowerManager.WakeLock; import android.database.ContentObserver; @@ -94,33 +94,23 @@ public class SyncManager extends Service implements Runnable { new HashMap(); static private WakeLock mWakeLock = null; - final RemoteCallbackList mCallbacks = - new RemoteCallbackList(); - - private final ISyncManager.Stub mBinder = new ISyncManager.Stub() { + /** + * Create the binder for EmailService implementation here. These are the calls that are + * defined in AbstractSyncService. Only validate is now implemented; loadAttachment currently + * spins its wheels counting up to 100%. + */ + private final IEmailService.Stub mBinder = new IEmailService.Stub() { public int validate(String protocol, String host, String userName, String password, int port, boolean ssl) throws RemoteException { try { - AbstractSyncService.validate(EasSyncService.class, host, userName, password, port, ssl, - SyncManager.this); + AbstractSyncService.validate(EasSyncService.class, host, userName, password, port, + ssl, SyncManager.this); return MessagingException.NO_ERROR; } catch (MessagingException e) { return e.getExceptionType(); } } - public void registerCallback(ISyncManagerCallback cb) { - if (cb != null) { - mCallbacks.register(cb); - } - } - - public void unregisterCallback(ISyncManagerCallback cb) { - if (cb != null) { - mCallbacks.unregister(cb); - } - } - public boolean startSync(long mailboxId) throws RemoteException { // TODO Auto-generated method stub return false; @@ -136,7 +126,7 @@ public class SyncManager extends Service implements Runnable { return false; } - public boolean loadMore(long messageId, ISyncManagerCallback cb) throws RemoteException { + public boolean loadMore(long messageId, IEmailServiceCallback cb) throws RemoteException { // TODO Auto-generated method stub return false; } @@ -157,9 +147,17 @@ public class SyncManager extends Service implements Runnable { return false; } - public boolean loadAttachment(long messageId, Attachment att, ISyncManagerCallback cb) + public boolean loadAttachment(long messageId, Attachment att, IEmailServiceCallback cb) throws RemoteException { + for (int i = 0; i < 10; i++) { + cb.status(EmailServiceStatus.IN_PROGRESS, i * 10); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } // TODO Auto-generated method stub + cb.status(EmailServiceStatus.SUCCESS, 0); return false; } }; @@ -237,7 +235,7 @@ public class SyncManager extends Service implements Runnable { main.mDisplayName = "_main"; main.mServerId = "_main"; main.mAccountKey = acct.mId; - main.mType = Mailbox.TYPE_MAIL; + main.mType = Mailbox.TYPE_NOT_EMAIL; main.mSyncFrequency = Account.CHECK_INTERVAL_PUSH; main.mFlagVisible = false; main.save(getContext()); diff --git a/src/com/android/exchange/adapter/EasFolderSyncParser.java b/src/com/android/exchange/adapter/EasFolderSyncParser.java index 0e0b07f43..d5b8ab213 100644 --- a/src/com/android/exchange/adapter/EasFolderSyncParser.java +++ b/src/com/android/exchange/adapter/EasFolderSyncParser.java @@ -212,6 +212,7 @@ public class EasFolderSyncParser extends EasParser { m.mDisplayName = name; m.mServerId = serverId; m.mAccountKey = mAccountId; + m.mType = Mailbox.TYPE_MAIL; m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER; switch (type) { case INBOX_TYPE: