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.
This commit is contained in:
Andrew Stadler 2009-07-07 09:39:11 -07:00 committed by Marc Blank
parent ded3c915d8
commit 17250429db
18 changed files with 852 additions and 99 deletions

View File

@ -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

View File

@ -178,6 +178,16 @@
android:enabled="true"
>
</service>
<!--Required stanza to register the EasAuthenticatorService with AccountManager -->
<service android:name=".service.EasAuthenticatorService" android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<provider
android:name=".provider.AttachmentProvider"
android:authorities="com.android.email.attachmentprovider"

27
res/xml/authenticator.xml Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* 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.
*/
-->
<!-- The attributes in this XML file provide configuration information -->
<!-- for the Account Manager. -->
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.android.exchange"
android:icon="@drawable/icon"
android:label="@string/app_name"
/>

View File

@ -35,6 +35,6 @@
<store scheme="imap" class="com.android.email.mail.store.ImapStore" />
<!-- This is here for temporary demo purposes only. Do not ship with this. -->
<store scheme="eas" class="com.android.email.mail.exchange.ExchangeStoreExample"
push="true" visibleLimitDefault="-1" visibleLimitIncrement="-1" accountInstanceLimit="1" />
<store scheme="eas" class="com.android.email.mail.store.ExchangeStore"
push="true" visibleLimitDefault="-1" visibleLimitIncrement="-1" />
</stores>

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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<String, Folder> mFolders = new HashMap<String, Folder>();
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.
*
* <p>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<? extends android.app.Activity> 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<String, ExchangeTransport> sUriToInstanceMap =
new HashMap<String, ExchangeTransport>();
private static final HashMap<String, Integer> sFolderMap = new HashMap<String, Integer>();
/**
* 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
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<Context, EmailServiceProxy> sProxyMap =
new HashMap<Context, EmailServiceProxy>();
// Map associating, for a given proxy, a class name (String) and a connected service
public HashMap<String, IEmailService> serviceMap =
new HashMap<String, IEmailService>();
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<? extends Service> 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;
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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;
}

View File

@ -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)
}

View File

@ -17,6 +17,6 @@
package com.android.exchange;
oneway interface ISyncManagerCallback {
void progress(int value);
oneway interface IEmailServiceCallback {
void status(int statusCode, int progress);
}

View File

@ -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);
}

View File

@ -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<Long, PendingIntent>();
static private WakeLock mWakeLock = null;
final RemoteCallbackList<ISyncManagerCallback> mCallbacks =
new RemoteCallbackList<ISyncManagerCallback>();
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());

View File

@ -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: