Add a bunch of stuff missed earlier
Change-Id: I7f707446a963912fe5786dacb5569e68db572d1c
This commit is contained in:
parent
f419287f22
commit
c5afb16430
|
@ -0,0 +1,30 @@
|
|||
# Copyright 2012, 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.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# Build the com.android.emailcommon static library. At the moment, this includes
|
||||
# the emailcommon files themselves plus everything under src/org (apache code). All of our
|
||||
# AIDL files are also compiled into the static library
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
|
||||
LOCAL_MODULE := com.android.emailsync
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/emailsync)
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := com.android.emailcommon2
|
||||
|
||||
LOCAL_SDK_VERSION := 14
|
||||
|
||||
include $(BUILD_STATIC_JAVA_LIBRARY)
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* 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.emailsync;
|
||||
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* Base class for all protocol services SyncManager (extends Service, implements
|
||||
* Runnable) instantiates subclasses to run a sync (either timed, or push, or
|
||||
* mail placed in outbox, etc.) EasSyncService is currently implemented; my goal
|
||||
* would be to move IMAP to this structure when it comes time to introduce push
|
||||
* functionality.
|
||||
*/
|
||||
public abstract class AbstractSyncService implements Runnable {
|
||||
|
||||
public String TAG = "AbstractSyncService";
|
||||
|
||||
public static final int SECONDS = 1000;
|
||||
public static final int MINUTES = 60*SECONDS;
|
||||
public static final int HOURS = 60*MINUTES;
|
||||
public static final int DAYS = 24*HOURS;
|
||||
|
||||
public static final int CONNECT_TIMEOUT = 30*SECONDS;
|
||||
public static final int NETWORK_WAIT = 15*SECONDS;
|
||||
|
||||
public static final String EAS_PROTOCOL = "eas";
|
||||
public static final int EXIT_DONE = 0;
|
||||
public static final int EXIT_IO_ERROR = 1;
|
||||
public static final int EXIT_LOGIN_FAILURE = 2;
|
||||
public static final int EXIT_EXCEPTION = 3;
|
||||
public static final int EXIT_SECURITY_FAILURE = 4;
|
||||
public static final int EXIT_ACCESS_DENIED = 5;
|
||||
|
||||
public Mailbox mMailbox;
|
||||
protected long mMailboxId;
|
||||
protected int mExitStatus = EXIT_EXCEPTION;
|
||||
protected String mMailboxName;
|
||||
public Account mAccount;
|
||||
public Context mContext;
|
||||
public int mChangeCount = 0;
|
||||
public volatile int mSyncReason = 0;
|
||||
protected volatile boolean mStop = false;
|
||||
public volatile Thread mThread;
|
||||
protected final Object mSynchronizer = new Object();
|
||||
// Whether or not the sync service is valid (usable)
|
||||
public boolean mIsValid = true;
|
||||
|
||||
public boolean mUserLog = true; // STOPSHIP
|
||||
public boolean mFileLog = false;
|
||||
|
||||
protected volatile long mRequestTime = 0;
|
||||
protected LinkedBlockingQueue<Request> mRequestQueue = new LinkedBlockingQueue<Request>();
|
||||
|
||||
/**
|
||||
* Sent by SyncManager to request that the service stop itself cleanly
|
||||
*/
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* Sent by SyncManager to indicate that an alarm has fired for this service, and that its
|
||||
* pending (network) operation has timed out. The service is NOT automatically stopped,
|
||||
* although the behavior is service dependent.
|
||||
*
|
||||
* @return true if the operation was stopped normally; false if the thread needed to be
|
||||
* interrupted.
|
||||
*/
|
||||
public abstract boolean alarm();
|
||||
|
||||
/**
|
||||
* Sent by SyncManager to request that the service reset itself cleanly; the meaning of this
|
||||
* operation is service dependent.
|
||||
*/
|
||||
public abstract void reset();
|
||||
|
||||
/**
|
||||
* Called to validate an account; abstract to allow each protocol to do what
|
||||
* is necessary. For consistency with the Email app's original
|
||||
* functionality, success is indicated by a failure to throw an Exception
|
||||
* (ugh). Parameters are self-explanatory
|
||||
*
|
||||
* @param hostAuth
|
||||
* @return a Bundle containing a result code and, depending on the result, a PolicySet or an
|
||||
* error message
|
||||
*/
|
||||
public abstract Bundle validateAccount(HostAuth hostAuth, Context context);
|
||||
|
||||
/**
|
||||
* Called to clear the syncKey for the calendar associated with this service; this is necessary
|
||||
* because changes to calendar sync state cause a reset of data.
|
||||
*/
|
||||
public abstract void resetCalendarSyncKey();
|
||||
|
||||
public AbstractSyncService(Context _context, Mailbox _mailbox) {
|
||||
mContext = _context;
|
||||
mMailbox = _mailbox;
|
||||
mMailboxId = _mailbox.mId;
|
||||
mMailboxName = _mailbox.mServerId;
|
||||
mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
|
||||
}
|
||||
|
||||
// Will be required when subclasses are instantiated by name
|
||||
public AbstractSyncService(String prefix) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The UI can call this static method to perform account validation. This method wraps each
|
||||
* protocol's validateAccount method. Arguments are self-explanatory, except where noted.
|
||||
*
|
||||
* @param klass the protocol class (EasSyncService.class for example)
|
||||
* @param hostAuth
|
||||
* @param context
|
||||
* @return a Bundle containing a result code and, depending on the result, a PolicySet or an
|
||||
* error message
|
||||
*/
|
||||
public static Bundle validate(Class<? extends AbstractSyncService> klass,
|
||||
HostAuth hostAuth, Context context) {
|
||||
AbstractSyncService svc;
|
||||
try {
|
||||
svc = klass.newInstance();
|
||||
return svc.validateAccount(hostAuth, context);
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch (InstantiationException e) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class ValidationResult {
|
||||
static final int NO_FAILURE = 0;
|
||||
static final int CONNECTION_FAILURE = 1;
|
||||
static final int VALIDATION_FAILURE = 2;
|
||||
static final int EXCEPTION = 3;
|
||||
|
||||
static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
|
||||
boolean success;
|
||||
int failure = NO_FAILURE;
|
||||
String reason = null;
|
||||
Exception exception = null;
|
||||
|
||||
ValidationResult(boolean _success, int _failure, String _reason) {
|
||||
success = _success;
|
||||
failure = _failure;
|
||||
reason = _reason;
|
||||
}
|
||||
|
||||
ValidationResult(boolean _success) {
|
||||
success = _success;
|
||||
}
|
||||
|
||||
ValidationResult(Exception e) {
|
||||
success = false;
|
||||
failure = EXCEPTION;
|
||||
exception = e;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStopped() {
|
||||
return mStop;
|
||||
}
|
||||
|
||||
public Object getSynchronizer() {
|
||||
return mSynchronizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience methods to do user logging (i.e. connection activity). Saves a bunch of
|
||||
* repetitive code.
|
||||
*/
|
||||
public void userLog(String string, int code, String string2) {
|
||||
if (mUserLog) {
|
||||
userLog(string + code + string2);
|
||||
}
|
||||
}
|
||||
|
||||
public void userLog(String string, int code) {
|
||||
if (mUserLog) {
|
||||
userLog(string + code);
|
||||
}
|
||||
}
|
||||
|
||||
public void userLog(String str, Exception e) {
|
||||
if (mUserLog) {
|
||||
Log.e(TAG, str, e);
|
||||
} else {
|
||||
Log.e(TAG, str + e);
|
||||
}
|
||||
if (mFileLog) {
|
||||
FileLogger.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard logging for EAS.
|
||||
* If user logging is active, we concatenate any arguments and log them using Log.d
|
||||
* We also check for file logging, and log appropriately
|
||||
* @param strings strings to concatenate and log
|
||||
*/
|
||||
public void userLog(String ...strings) {
|
||||
if (mUserLog) {
|
||||
String logText;
|
||||
if (strings.length == 1) {
|
||||
logText = strings[0];
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
for (String string: strings) {
|
||||
sb.append(string);
|
||||
}
|
||||
logText = sb.toString();
|
||||
}
|
||||
Log.d(TAG, logText);
|
||||
if (mFileLog) {
|
||||
FileLogger.log(TAG, logText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error log is used for serious issues that should always be logged
|
||||
* @param str the string to log
|
||||
*/
|
||||
public void errorLog(String str) {
|
||||
Log.e(TAG, str);
|
||||
if (mFileLog) {
|
||||
FileLogger.log(TAG, str);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for up to 10 seconds for network connectivity; returns whether or not there is
|
||||
* network connectivity.
|
||||
*
|
||||
* @return whether there is network connectivity
|
||||
*/
|
||||
public boolean hasConnectivity() {
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
int tries = 0;
|
||||
while (tries++ < 1) {
|
||||
// Use the same test as in ExchangeService#waitForConnectivity
|
||||
// TODO: Create common code for this test in emailcommon
|
||||
NetworkInfo info = cm.getActiveNetworkInfo();
|
||||
if (info != null) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10*SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request handling (common functionality)
|
||||
* Can be overridden if desired
|
||||
*/
|
||||
|
||||
public void addRequest(Request req) {
|
||||
mRequestQueue.offer(req);
|
||||
}
|
||||
|
||||
public void removeRequest(Request req) {
|
||||
mRequestQueue.remove(req);
|
||||
}
|
||||
|
||||
public boolean hasPendingRequests() {
|
||||
return !mRequestQueue.isEmpty();
|
||||
}
|
||||
|
||||
public void clearRequests() {
|
||||
mRequestQueue.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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.emailsync;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.emailcommon.provider.EmailContent.Message;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.ProviderUnavailableException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* EmailSyncAlarmReceiver (USAR) is used by the SyncManager to start up-syncs of user-modified data
|
||||
* back to the Exchange server.
|
||||
*
|
||||
* Here's how this works for Email, for example:
|
||||
*
|
||||
* 1) User modifies or deletes an email from the UI.
|
||||
* 2) SyncManager, which has a ContentObserver watching the Message class, is alerted to a change
|
||||
* 3) SyncManager sets an alarm (to be received by USAR) for a few seconds in the
|
||||
* future (currently 15), the delay preventing excess syncing (think of it as a debounce mechanism).
|
||||
* 4) ESAR Receiver's onReceive method is called
|
||||
* 5) ESAR goes through all change and deletion records and compiles a list of mailboxes which have
|
||||
* changes to be uploaded.
|
||||
* 6) ESAR calls SyncManager to start syncs of those mailboxes
|
||||
*
|
||||
* If EmailProvider isn't available, the upsyncs will happen the next time ExchangeService starts
|
||||
*
|
||||
*/
|
||||
public class EmailSyncAlarmReceiver extends BroadcastReceiver {
|
||||
final String[] MAILBOX_DATA_PROJECTION = {MessageColumns.MAILBOX_KEY};
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
handleReceive(context);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void handleReceive(Context context) {
|
||||
ArrayList<Long> mailboxesToNotify = new ArrayList<Long>();
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
|
||||
// Get a selector for EAS accounts (we don't want to sync on changes to POP/IMAP messages)
|
||||
String selector = SyncServiceManager.getAccountSelector();
|
||||
|
||||
try {
|
||||
// Find all of the deletions
|
||||
Cursor c = cr.query(Message.DELETED_CONTENT_URI, MAILBOX_DATA_PROJECTION, selector,
|
||||
null, null);
|
||||
if (c == null) throw new ProviderUnavailableException();
|
||||
try {
|
||||
// Keep track of which mailboxes to notify; we'll only notify each one once
|
||||
while (c.moveToNext()) {
|
||||
long mailboxId = c.getLong(0);
|
||||
if (!mailboxesToNotify.contains(mailboxId)) {
|
||||
mailboxesToNotify.add(mailboxId);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
|
||||
// Now, find changed messages
|
||||
c = cr.query(Message.UPDATED_CONTENT_URI, MAILBOX_DATA_PROJECTION, selector,
|
||||
null, null);
|
||||
if (c == null) throw new ProviderUnavailableException();
|
||||
try {
|
||||
// Keep track of which mailboxes to notify; we'll only notify each one once
|
||||
while (c.moveToNext()) {
|
||||
long mailboxId = c.getLong(0);
|
||||
if (!mailboxesToNotify.contains(mailboxId)) {
|
||||
mailboxesToNotify.add(mailboxId);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
|
||||
// Request service from the mailbox
|
||||
for (Long mailboxId: mailboxesToNotify) {
|
||||
SyncServiceManager.serviceRequest(mailboxId, SyncServiceManager.SYNC_UPSYNC);
|
||||
}
|
||||
} catch (ProviderUnavailableException e) {
|
||||
Log.e("EmailSyncAlarmReceiver", "EmailProvider unavailable; aborting alarm receiver");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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.emailsync;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Date;
|
||||
|
||||
public class FileLogger {
|
||||
private static FileLogger LOGGER = null;
|
||||
private static FileWriter sLogWriter = null;
|
||||
public static String LOG_FILE_NAME =
|
||||
Environment.getExternalStorageDirectory() + "/emaillog.txt";
|
||||
|
||||
public synchronized static FileLogger getLogger (Context c) {
|
||||
LOGGER = new FileLogger();
|
||||
return LOGGER;
|
||||
}
|
||||
|
||||
private FileLogger() {
|
||||
try {
|
||||
sLogWriter = new FileWriter(LOG_FILE_NAME, true);
|
||||
} catch (IOException e) {
|
||||
// Doesn't matter
|
||||
}
|
||||
}
|
||||
|
||||
static public synchronized void close() {
|
||||
if (sLogWriter != null) {
|
||||
try {
|
||||
sLogWriter.close();
|
||||
} catch (IOException e) {
|
||||
// Doesn't matter
|
||||
}
|
||||
sLogWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
static public synchronized void log(Exception e) {
|
||||
if (sLogWriter != null) {
|
||||
log("Exception", "Stack trace follows...");
|
||||
PrintWriter pw = new PrintWriter(sLogWriter);
|
||||
e.printStackTrace(pw);
|
||||
pw.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static public synchronized void log(String prefix, String str) {
|
||||
if (LOGGER == null) {
|
||||
LOGGER = new FileLogger();
|
||||
log("Logger", "\r\n\r\n --- New Log ---");
|
||||
}
|
||||
Date d = new Date();
|
||||
int hr = d.getHours();
|
||||
int min = d.getMinutes();
|
||||
int sec = d.getSeconds();
|
||||
|
||||
// I don't use DateFormat here because (in my experience), it's much slower
|
||||
StringBuffer sb = new StringBuffer(256);
|
||||
sb.append('[');
|
||||
sb.append(hr);
|
||||
sb.append(':');
|
||||
if (min < 10)
|
||||
sb.append('0');
|
||||
sb.append(min);
|
||||
sb.append(':');
|
||||
if (sec < 10) {
|
||||
sb.append('0');
|
||||
}
|
||||
sb.append(sec);
|
||||
sb.append("] ");
|
||||
if (prefix != null) {
|
||||
sb.append(prefix);
|
||||
sb.append("| ");
|
||||
}
|
||||
sb.append(str);
|
||||
sb.append("\r\n");
|
||||
String s = sb.toString();
|
||||
|
||||
if (sLogWriter != null) {
|
||||
try {
|
||||
sLogWriter.write(s);
|
||||
sLogWriter.flush();
|
||||
} catch (IOException e) {
|
||||
// Something might have happened to the sdcard
|
||||
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
||||
// If the card is mounted and we can create the writer, retry
|
||||
LOGGER = new FileLogger();
|
||||
if (sLogWriter != null) {
|
||||
try {
|
||||
log("FileLogger", "Exception writing log; recreating...");
|
||||
log(prefix, str);
|
||||
} catch (Exception e1) {
|
||||
// Nothing to do at this point
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.emailsync;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
|
||||
/**
|
||||
* MailboxAlarmReceiver is used to "wake up" the ExchangeService at the appropriate time(s). It may
|
||||
* also be used for individual sync adapters, but this isn't implemented at the present time.
|
||||
*
|
||||
*/
|
||||
public class MailboxAlarmReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
long mailboxId = intent.getLongExtra("mailbox", SyncServiceManager.EXTRA_MAILBOX_ID);
|
||||
// EXCHANGE_SERVICE_MAILBOX_ID tells us that the service is asking to be started
|
||||
if (mailboxId == SyncServiceManager.SYNC_SERVICE_MAILBOX_ID) {
|
||||
context.startService(new Intent(context, SyncServiceManager.class));
|
||||
} else {
|
||||
SyncServiceManager.alert(context, mailboxId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.emailsync;
|
||||
|
||||
import com.android.emailcommon.provider.EmailContent.Attachment;
|
||||
|
||||
/**
|
||||
* PartRequest is the EAS wrapper for attachment loading requests. In addition to information about
|
||||
* the attachment to be loaded, it also contains the callback to be used for status/progress
|
||||
* updates to the UI.
|
||||
*/
|
||||
public class PartRequest extends Request {
|
||||
public final Attachment mAttachment;
|
||||
public final String mDestination;
|
||||
public final String mContentUriString;
|
||||
public final String mLocation;
|
||||
|
||||
public PartRequest(Attachment _att, String _destination, String _contentUriString) {
|
||||
super(_att.mMessageKey);
|
||||
mAttachment = _att;
|
||||
mLocation = mAttachment.mLocation;
|
||||
mDestination = _destination;
|
||||
mContentUriString = _contentUriString;
|
||||
}
|
||||
|
||||
// PartRequests are unique by their attachment id (i.e. multiple attachments might be queued
|
||||
// for a particular message, but any individual attachment can only be loaded once)
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof PartRequest)) return false;
|
||||
return ((PartRequest)o).mAttachment.mId == mAttachment.mId;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return (int)mAttachment.mId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.emailsync;
|
||||
|
||||
/**
|
||||
* Requests for mailbox actions are handled by subclasses of this abstract class.
|
||||
* Three subclasses are now defined: PartRequest (attachment load), MeetingResponseRequest
|
||||
* (respond to a meeting invitation), and MessageMoveRequest (move a message to another folder)
|
||||
*/
|
||||
public abstract class Request {
|
||||
public final long mTimeStamp = System.currentTimeMillis();
|
||||
public final long mMessageId;
|
||||
|
||||
public Request(long messageId) {
|
||||
mMessageId = messageId;
|
||||
}
|
||||
|
||||
// Subclasses of Request may have different semantics regarding equality; therefore,
|
||||
// we force them to implement the equals method
|
||||
public abstract boolean equals(Object o);
|
||||
public abstract int hashCode();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 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.
|
||||
-->
|
||||
|
||||
<!-- small -->
|
||||
|
||||
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginTop="@dimen/setup_buttons_vertical_spacing"
|
||||
android:layout_marginLeft="48dip"
|
||||
style="@style/accountSetupButton" />
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 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.
|
||||
-->
|
||||
|
||||
<!-- small -->
|
||||
|
||||
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/button"
|
||||
android:text="@string/account_setup_options_mail_window_auto"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="150sp"
|
||||
android:layout_marginTop="25dip"
|
||||
android:minWidth="@dimen/button_minWidth"
|
||||
android:layout_gravity="center_horizontal"
|
||||
/>
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2011 Google Inc.
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- This layout is used as a template to create custom view CanvasConversationHeaderView
|
||||
in normal mode. To be able to get the correct measurements, every source field should
|
||||
be populated with data here. E.g:
|
||||
- Text View should set text to a random long string (android:text="@string/long_string")
|
||||
- Image View should set source to a specific asset -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/conversation_item_height"
|
||||
android:orientation="vertical">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
<ImageView
|
||||
android:id="@+id/reply_state"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/replystate_margin_top"
|
||||
android:layout_marginLeft="@dimen/replystate_margin_left"
|
||||
android:layout_marginRight="@dimen/replystate_margin_right"
|
||||
android:src="@drawable/ic_badge_reply_holo_light"
|
||||
/>
|
||||
<com.android.mail.browse.SendersView
|
||||
android:id="@+id/senders"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/SendersStyle"
|
||||
android:text="@string/long_string"
|
||||
android:textSize="@dimen/senders_font_size"
|
||||
android:lines="1"
|
||||
android:layout_toRightOf="@id/reply_state" />
|
||||
<RelativeLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="@dimen/total_conversationbar_width"
|
||||
android:layout_alignParentRight="true">
|
||||
<View android:id="@+id/color_bar"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_height="@dimen/color_block_height"
|
||||
android:layout_width="@dimen/total_conversationbar_width" />
|
||||
<LinearLayout
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="@dimen/total_conversationbar_width"
|
||||
android:layout_alignParentRight="true">
|
||||
<ImageView
|
||||
android:id="@+id/paperclip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_attachment_holo_light"
|
||||
android:layout_marginTop="6sp" />
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="0dip"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/long_string"
|
||||
android:textSize="@dimen/date_font_size"
|
||||
android:lines="1"
|
||||
android:layout_marginRight="16dip"
|
||||
android:layout_marginTop="10sp" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<ImageView
|
||||
style="@style/CheckmarkStyle"
|
||||
android:id="@+id/checkmark"
|
||||
android:src="@drawable/btn_check_on_normal_holo_light"/>
|
||||
<TextView
|
||||
android:id="@+id/subject"
|
||||
android:layout_width="@dimen/subject_width"
|
||||
style="@style/SubjectStyle"
|
||||
android:text="@string/long_string"
|
||||
android:lines="2"/>
|
||||
<ImageView
|
||||
android:id="@+id/star"
|
||||
style="@style/StarStyle"
|
||||
android:src="@drawable/btn_star_off_normal_email_holo_light" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,100 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2011 Google Inc.
|
||||
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.
|
||||
-->
|
||||
|
||||
<!-- This layout is used as a template to create custom view CanvasConversationHeaderView
|
||||
in wide mode. To be able to get the correct measurements, every source field should
|
||||
be populated with data here. E.g:
|
||||
- Text View should set text to a random long string (android:text="@string/long_string")
|
||||
- Image View should set source to a specific asset -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="64sp"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
android:id="@+id/checkmark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/btn_check_on_normal_holo_light" />
|
||||
<com.android.mail.browse.SendersView
|
||||
android:id="@+id/senders"
|
||||
android:layout_width="224dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/long_string"
|
||||
android:textSize="@dimen/wide_senders_font_size"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:maxLines="2"
|
||||
android:layout_marginTop="@dimen/wide_senders_margin_top" />
|
||||
<ImageView
|
||||
android:id="@+id/reply_state"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_badge_reply_holo_light" />
|
||||
<TextView
|
||||
android:id="@+id/subject"
|
||||
android:layout_width="0dip"
|
||||
android:layout_weight="0.7"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/long_string"
|
||||
android:lines="2"
|
||||
android:textColor="@color/subject_text_color_unread"
|
||||
android:textSize="@dimen/wide_subject_font_size"
|
||||
android:layout_marginTop="2sp"
|
||||
android:layout_marginRight="@dimen/wide_subject_margin_right"/>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical">
|
||||
<ImageView
|
||||
android:id="@+id/paperclip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_attachment_holo_light"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="@dimen/wide_attachment_margin_top"/>
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/date"
|
||||
android:layout_marginTop="@dimen/wide_date_margin_top" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@+id/star"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="16dip"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/btn_star_off_normal_email_holo_light" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 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.
|
||||
-->
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingRight="4dip"
|
||||
android:paddingBottom="6dip">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/quick_response_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</RelativeLayout>
|
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<!-- tablet, landscape -->
|
||||
<resources>
|
||||
<!-- Account Setup Activities -->
|
||||
<dimen name="setup_padding_top">16dip</dimen>
|
||||
<dimen name="setup_padding_left">128dip</dimen>
|
||||
<dimen name="setup_padding_right">128dip</dimen>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Google Inc.
|
||||
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.
|
||||
-->
|
||||
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:label="@string/search_title"
|
||||
android:hint="@string/search_hint"
|
||||
android:icon="@drawable/ic_menu_search_holo_light"
|
||||
android:searchSuggestAuthority="com.android.email.suggestionsprovider"
|
||||
android:searchSuggestSelection="query LIKE ?"
|
||||
android:searchSuggestIntentAction="android.intent.action.SEARCH"
|
||||
android:imeOptions="actionSearch" />
|
|
@ -0,0 +1,104 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2012 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.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Email services (protocols) are defined here. For the present, these are baked into the Email
|
||||
apk; the goal is for remote services to register themselves into this file.
|
||||
|
||||
The required attributes are as follows (except that EITHER serviceClass or intent is required):
|
||||
protocol: the unique name used to identify the protocol
|
||||
name: the name of the account type option presented to users during account setup
|
||||
accountType: the AccountManager type of accounts created using this service
|
||||
serviceClass: a class implementing IEmailService (or null, if the service is remote)
|
||||
intent: the intent used to connect to a remote IEmailService
|
||||
port: the (default) port used when creating accounts using this service
|
||||
portSsl: as above, when SSL is selected
|
||||
syncIntervalStrings: a reference to an array of sync interval options
|
||||
syncIntervals: a reference to an array of values corresponding to syncIntervalStrings
|
||||
defaultSyncInterval: the default sync interval, selected from enums defined in attrs.xml
|
||||
|
||||
The following optional attributes default to "false":
|
||||
offerTls: whether a TLS option (e.g. STARTTLS) is offered for this service
|
||||
offerCerts: whether or not certificate authentication is an option for this service
|
||||
usesSmtp: whether SMTP is used as the outgoing protocol for this service
|
||||
offerPrefix: whether a "prefix" is offered to the user (for IMAP)
|
||||
offerLocalDeletes: whether an option to delete locally is offered
|
||||
syncChanges: whether non-deletion changes to messages sync back to the server
|
||||
offerAttachmentPreload: whether to offer attachment preloading (pre-caching)
|
||||
usesAutodiscover: whether to attempt using the "autodiscover" API when creating
|
||||
an account
|
||||
offerLookback: whether a sync "lookback" is offered (rather than the POP/IMAP
|
||||
legacy "25 most recent messages synced")
|
||||
defaultLookback: if "lookback" is offered, an enum of possible lookbacks
|
||||
syncCalendar: whether this service is capable of syncing a calendar (offering a checkbox)
|
||||
syncContacts: whether this service is capable of syncing contacts (offering a checkbox)
|
||||
-->
|
||||
|
||||
<emailservices xmlns:email="http://schemas.android.com/apk/res/com.android.email">
|
||||
<emailservice
|
||||
email:protocol="pop3"
|
||||
email:name="POP3"
|
||||
email:accountType="com.android.email"
|
||||
email:serviceClass="com.android.email.service.ImapService"
|
||||
email:port="110"
|
||||
email:portSsl="995"
|
||||
email:syncIntervalStrings="@array/account_settings_check_frequency_entries"
|
||||
email:syncIntervals="@array/account_settings_check_frequency_values"
|
||||
email:defaultSyncInterval="mins15"
|
||||
|
||||
email:offerTls="true"
|
||||
email:usesSmtp="true"
|
||||
email:offerLocalDeletes="true"
|
||||
/>
|
||||
<emailservice
|
||||
email:protocol="imap"
|
||||
email:name="IMAP"
|
||||
email:accountType="com.android.email"
|
||||
email:serviceClass="com.android.email.service.Pop3Service"
|
||||
email:port="143"
|
||||
email:portSsl="993"
|
||||
email:syncIntervalStrings="@array/account_settings_check_frequency_entries"
|
||||
email:syncIntervals="@array/account_settings_check_frequency_values"
|
||||
email:defaultSyncInterval="mins15"
|
||||
|
||||
email:offerTls="true"
|
||||
email:usesSmtp="true"
|
||||
email:offerAttachmentPreload="true"
|
||||
email:offerPrefix="true"
|
||||
email:syncChanges="true"
|
||||
/>
|
||||
<emailservice
|
||||
email:protocol="eas"
|
||||
email:name="Exchange"
|
||||
email:accountType="com.android.exchange"
|
||||
email:intent="com.android.email.EXCHANGE_INTENT"
|
||||
email:port="80"
|
||||
email:portSsl="443"
|
||||
email:syncIntervalStrings="@array/account_settings_check_frequency_entries_push"
|
||||
email:syncIntervals="@array/account_settings_check_frequency_values_push"
|
||||
email:defaultSyncInterval="push"
|
||||
|
||||
email:defaultSsl="true"
|
||||
email:offerCerts="true"
|
||||
email:syncChanges="true"
|
||||
email:usesAutodiscover="true"
|
||||
email:offerAttachmentPreload="true"
|
||||
email:offerLookback="true"
|
||||
email:defaultLookback="auto"
|
||||
email:syncContacts="true"
|
||||
email:syncCalendar="true"
|
||||
/>
|
||||
</emailservices>
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Copyright (c) 2012, Google Inc.
|
||||
*
|
||||
* 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.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
|
||||
import com.android.emailcommon.mail.MeetingInfo;
|
||||
import com.android.emailcommon.mail.PackedString;
|
||||
import com.android.emailcommon.provider.EmailContent.Message;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
public class EventViewer extends Activity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Uri uri = getIntent().getData();
|
||||
long messageId = Long.parseLong(uri.getLastPathSegment());
|
||||
Message msg = Message.restoreMessageWithId(this, messageId);
|
||||
if (msg == null) {
|
||||
finish();
|
||||
} else {
|
||||
PackedString info = new PackedString(msg.mMeetingInfo);
|
||||
String uid = info.get(MeetingInfo.MEETING_UID);
|
||||
long eventId = -1;
|
||||
if (uid != null) {
|
||||
Cursor c = getContentResolver().query(CalendarContract.Events.CONTENT_URI,
|
||||
new String[] {CalendarContract.Events._ID},
|
||||
CalendarContract.Events.SYNC_DATA2 + "=?",
|
||||
new String[] {uid}, null);
|
||||
if (c != null) {
|
||||
try {
|
||||
if (c.getCount() == 1) {
|
||||
c.moveToFirst();
|
||||
eventId = c.getLong(0);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
if (eventId != -1) {
|
||||
uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId);
|
||||
} else {
|
||||
long time =
|
||||
Utility.parseEmailDateTimeToMillis(info.get(MeetingInfo.MEETING_DTSTART));
|
||||
uri = Uri.parse("content://com.android.calendar/time/" + time);
|
||||
intent.putExtra("VIEW", "DAY");
|
||||
}
|
||||
intent.setData(uri);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (C) 2008 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.activity.setup;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.RelativeLayout.LayoutParams;
|
||||
|
||||
import com.android.email.R;
|
||||
import com.android.email.activity.ActivityHelper;
|
||||
import com.android.email.activity.UiUtilities;
|
||||
import com.android.email.service.EmailServiceUtils;
|
||||
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
|
||||
/**
|
||||
* Prompts the user to select an account type. The account type, along with the
|
||||
* passed in email address, password and makeDefault are then passed on to the
|
||||
* AccountSetupIncoming activity.
|
||||
*/
|
||||
public class AccountSetupType extends AccountSetupActivity implements OnClickListener {
|
||||
|
||||
public static void actionSelectAccountType(Activity fromActivity) {
|
||||
Intent i = new ForwardingIntent(fromActivity, AccountSetupType.class);
|
||||
fromActivity.startActivity(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ActivityHelper.debugSetWindowFlags(this);
|
||||
int flowMode = SetupData.getFlowMode();
|
||||
|
||||
String accountType = SetupData.getFlowAccountType();
|
||||
// If we're in account setup flow mode, see if there's just one protocol that matches
|
||||
if (flowMode == SetupData.FLOW_MODE_ACCOUNT_MANAGER) {
|
||||
int matches = 0;
|
||||
String protocol = null;
|
||||
for (EmailServiceInfo info: EmailServiceUtils.getServiceInfoList(this)) {
|
||||
if (info.accountType.equals(accountType)) {
|
||||
protocol = info.protocol;
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
// If so, select it...
|
||||
if (matches == 1) {
|
||||
onSelect(protocol);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise proceed into this screen
|
||||
setContentView(R.layout.account_setup_account_type);
|
||||
ViewGroup parent = UiUtilities.getView(this, R.id.accountTypes);
|
||||
boolean parentRelative = parent instanceof RelativeLayout;
|
||||
View lastView = parent.getChildAt(0);
|
||||
int i = 1;
|
||||
for (EmailServiceInfo info: EmailServiceUtils.getServiceInfoList(this)) {
|
||||
if (EmailServiceUtils.isServiceAvailable(this, info.protocol)) {
|
||||
// If we're looking for a specific account type, reject others
|
||||
if (accountType != null && !accountType.equals(info.accountType)) {
|
||||
continue;
|
||||
}
|
||||
LayoutInflater.from(this).inflate(R.layout.account_type, parent);
|
||||
Button button = (Button)parent.getChildAt(i);
|
||||
if (parentRelative) {
|
||||
LayoutParams params = (LayoutParams)button.getLayoutParams();
|
||||
params.addRule(RelativeLayout.BELOW, lastView.getId());
|
||||
}
|
||||
button.setId(i);
|
||||
button.setTag(info.protocol);
|
||||
button.setText(info.name);
|
||||
button.setOnClickListener(this);
|
||||
lastView = button;
|
||||
i++;
|
||||
// TODO: Remember vendor overlay for exchange name
|
||||
}
|
||||
}
|
||||
final Button previousButton = (Button) findViewById(R.id.previous); // xlarge only
|
||||
if (previousButton != null) previousButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has selected an exchange account type. Set the mail delete policy here, because
|
||||
* there is no UI (for exchange), and switch the default sync interval to "push".
|
||||
*/
|
||||
private void onSelect(String protocol) {
|
||||
Account account = SetupData.getAccount();
|
||||
HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
|
||||
recvAuth.setConnection(protocol, recvAuth.mAddress, recvAuth.mPort, recvAuth.mFlags);
|
||||
EmailServiceInfo info = EmailServiceUtils.getServiceInfo(this, protocol);
|
||||
if (info.usesAutodiscover) {
|
||||
SetupData.setCheckSettingsMode(SetupData.CHECK_AUTODISCOVER);
|
||||
} else {
|
||||
SetupData.setCheckSettingsMode(
|
||||
SetupData.CHECK_INCOMING | (info.usesSmtp ? SetupData.CHECK_OUTGOING : 0));
|
||||
}
|
||||
recvAuth.mLogin = recvAuth.mLogin + "@" + recvAuth.mAddress;
|
||||
AccountSetupBasics.setDefaultsForProtocol(this, account);
|
||||
AccountSetupIncoming.actionIncomingSettings(this, SetupData.getFlowMode(), account);
|
||||
// Back from the incoming screen returns to AccountSetupBasics
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.previous:
|
||||
finish();
|
||||
break;
|
||||
default:
|
||||
onSelect((String)v.getTag());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2011 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.activity.setup;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
||||
public class EmailPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
//***
|
||||
//if (!UiUtilities.useTwoPane(getActivity())) {
|
||||
// menu.clear();
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* Copyright (C) 2012 Google Inc.
|
||||
* 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.activity.setup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* An intent that forwards results
|
||||
*/
|
||||
public class ForwardingIntent extends Intent {
|
||||
public ForwardingIntent(Context activity, Class klass) {
|
||||
super(activity, klass);
|
||||
setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (C) 2011 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.activity.setup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Simple text preference allowing a large number of lines
|
||||
*/
|
||||
public class PolicyListPreference extends Preference {
|
||||
// Arbitrary, but large number (we don't, and won't, have nearly this many)
|
||||
public static final int MAX_POLICIES = 24;
|
||||
|
||||
public PolicyListPreference(Context ctx, AttributeSet attrs, int defStyle) {
|
||||
super(ctx, attrs, defStyle);
|
||||
}
|
||||
|
||||
public PolicyListPreference(Context ctx, AttributeSet attrs) {
|
||||
super(ctx, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
((TextView)view.findViewById(android.R.id.summary)).setMaxLines(MAX_POLICIES);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright (C) 2012 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.provider;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.LegacyConversions;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.internet.MimeUtility;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.mail.Part;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.utility.ConversionUtilities;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Utilities {
|
||||
/**
|
||||
* Copy one downloaded message (which may have partially-loaded sections)
|
||||
* into a newly created EmailProvider Message, given the account and mailbox
|
||||
*
|
||||
* @param message the remote message we've just downloaded
|
||||
* @param account the account it will be stored into
|
||||
* @param folder the mailbox it will be stored into
|
||||
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
||||
* EmailContent.Message.LOADED)
|
||||
*/
|
||||
public static void copyOneMessageToProvider(Context context, Message message, Account account,
|
||||
Mailbox folder, int loadStatus) {
|
||||
EmailContent.Message localMessage = null;
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = context.getContentResolver().query(
|
||||
EmailContent.Message.CONTENT_URI,
|
||||
EmailContent.Message.CONTENT_PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
||||
" AND " + MessageColumns.MAILBOX_KEY + "=?" +
|
||||
" AND " + SyncColumns.SERVER_ID + "=?",
|
||||
new String[] {
|
||||
String.valueOf(account.mId),
|
||||
String.valueOf(folder.mId),
|
||||
String.valueOf(message.getUid())
|
||||
},
|
||||
null);
|
||||
if (c.moveToNext()) {
|
||||
localMessage = EmailContent.getContent(c, EmailContent.Message.class);
|
||||
localMessage.mMailboxKey = folder.mId;
|
||||
localMessage.mAccountKey = account.mId;
|
||||
copyOneMessageToProvider(context, message, localMessage, loadStatus);
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy one downloaded message (which may have partially-loaded sections)
|
||||
* into an already-created EmailProvider Message
|
||||
*
|
||||
* @param message the remote message we've just downloaded
|
||||
* @param localMessage the EmailProvider Message, already created
|
||||
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
||||
* EmailContent.Message.LOADED)
|
||||
* @param context the context to be used for EmailProvider
|
||||
*/
|
||||
public static void copyOneMessageToProvider(Context context, Message message,
|
||||
EmailContent.Message localMessage, int loadStatus) {
|
||||
try {
|
||||
|
||||
EmailContent.Body body = EmailContent.Body.restoreBodyWithMessageId(context,
|
||||
localMessage.mId);
|
||||
if (body == null) {
|
||||
body = new EmailContent.Body();
|
||||
}
|
||||
try {
|
||||
// Copy the fields that are available into the message object
|
||||
LegacyConversions.updateMessageFields(localMessage, message,
|
||||
localMessage.mAccountKey, localMessage.mMailboxKey);
|
||||
|
||||
// Now process body parts & attachments
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
|
||||
ConversionUtilities.updateBodyFields(body, localMessage, viewables);
|
||||
|
||||
// Commit the message & body to the local store immediately
|
||||
saveOrUpdate(localMessage, context);
|
||||
saveOrUpdate(body, context);
|
||||
|
||||
// process (and save) attachments
|
||||
LegacyConversions.updateAttachments(context, localMessage, attachments);
|
||||
|
||||
// One last update of message with two updated flags
|
||||
localMessage.mFlagLoaded = loadStatus;
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT, localMessage.mFlagAttachment);
|
||||
cv.put(EmailContent.MessageColumns.FLAG_LOADED, localMessage.mFlagLoaded);
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI,
|
||||
localMessage.mId);
|
||||
context.getContentResolver().update(uri, cv, null, null);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Logging.LOG_TAG, "Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
Log.e(Logging.LOG_TAG, "Error while storing downloaded message." + rte.toString());
|
||||
} catch (IOException ioe) {
|
||||
Log.e(Logging.LOG_TAG, "Error while storing attachment." + ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveOrUpdate(EmailContent content, Context context) {
|
||||
if (content.isSaved()) {
|
||||
content.update(context, content.toContentValues());
|
||||
} else {
|
||||
content.save(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* 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.email.activity.setup.AccountSetupBasics;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.NetworkErrorException;
|
||||
import android.app.Service;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public class AuthenticatorService extends Service {
|
||||
public static final String OPTIONS_USERNAME = "username";
|
||||
public static final String OPTIONS_PASSWORD = "password";
|
||||
public static final String OPTIONS_CONTACTS_SYNC_ENABLED = "contacts";
|
||||
public static final String OPTIONS_CALENDAR_SYNC_ENABLED = "calendar";
|
||||
public static final String OPTIONS_EMAIL_SYNC_ENABLED = "email";
|
||||
|
||||
class Authenticator extends AbstractAccountAuthenticator {
|
||||
|
||||
public Authenticator(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
|
||||
String authTokenType, String[] requiredFeatures, Bundle options)
|
||||
throws NetworkErrorException {
|
||||
|
||||
// There are two cases here:
|
||||
// 1) We are called with a username/password; this comes from the traditional email
|
||||
// app UI; we simply create the account and return the proper bundle
|
||||
if (options != null && options.containsKey(OPTIONS_PASSWORD)
|
||||
&& options.containsKey(OPTIONS_USERNAME)) {
|
||||
final Account account = new Account(options.getString(OPTIONS_USERNAME),
|
||||
accountType);
|
||||
AccountManager.get(AuthenticatorService.this).addAccountExplicitly(
|
||||
account, options.getString(OPTIONS_PASSWORD), null);
|
||||
|
||||
// Set up contacts syncing, if appropriate
|
||||
if (options.containsKey(OPTIONS_CONTACTS_SYNC_ENABLED)) {
|
||||
boolean syncContacts = options.getBoolean(OPTIONS_CONTACTS_SYNC_ENABLED);
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
|
||||
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY,
|
||||
syncContacts);
|
||||
}
|
||||
|
||||
// Set up calendar syncing, if appropriate
|
||||
if (options.containsKey(OPTIONS_CALENDAR_SYNC_ENABLED)) {
|
||||
boolean syncCalendar = options.getBoolean(OPTIONS_CALENDAR_SYNC_ENABLED);
|
||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1);
|
||||
ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY,
|
||||
syncCalendar);
|
||||
}
|
||||
|
||||
// Set up email syncing (it's always syncable, but we respect the user's choice
|
||||
// for whether to enable it now)
|
||||
boolean syncEmail = false;
|
||||
if (options.containsKey(OPTIONS_EMAIL_SYNC_ENABLED) &&
|
||||
options.getBoolean(OPTIONS_EMAIL_SYNC_ENABLED)) {
|
||||
syncEmail = true;
|
||||
}
|
||||
ContentResolver.setIsSyncable(account, EmailContent.AUTHORITY, 1);
|
||||
ContentResolver.setSyncAutomatically(account, EmailContent.AUTHORITY,
|
||||
syncEmail);
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.putString(AccountManager.KEY_ACCOUNT_NAME, options.getString(OPTIONS_USERNAME));
|
||||
b.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
|
||||
return b;
|
||||
// 2) The other case is that we're creating a new account from an Account manager
|
||||
// activity. In this case, we add an intent that will be used to gather the
|
||||
// account information...
|
||||
} else {
|
||||
Bundle b = new Bundle();
|
||||
Intent intent =
|
||||
AccountSetupBasics.actionGetCreateAccountIntent(AuthenticatorService.this,
|
||||
accountType);
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||
b.putParcelable(AccountManager.KEY_INTENT, intent);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
|
||||
Bundle options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@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 String getAuthTokenLabel(String authTokenType) {
|
||||
// null means we don't have compartmentalized authtoken types
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (AccountManager.ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())) {
|
||||
return new Authenticator(this).getIBinder();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,561 @@
|
|||
/* Copyright (C) 2012 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 android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.NotificationController;
|
||||
import com.android.email.mail.Sender;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.email.provider.Utilities;
|
||||
import com.android.email2.ui.MailActivityEmail;
|
||||
import com.android.emailcommon.AccountManagerTypes;
|
||||
import com.android.emailcommon.Api;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.TrafficFlags;
|
||||
import com.android.emailcommon.internet.MimeBodyPart;
|
||||
import com.android.emailcommon.internet.MimeHeader;
|
||||
import com.android.emailcommon.internet.MimeMultipart;
|
||||
import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||
import com.android.emailcommon.mail.FetchProfile;
|
||||
import com.android.emailcommon.mail.Folder;
|
||||
import com.android.emailcommon.mail.Folder.MessageRetrievalListener;
|
||||
import com.android.emailcommon.mail.Folder.OpenMode;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.EmailContent.Attachment;
|
||||
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.Body;
|
||||
import com.android.emailcommon.provider.EmailContent.BodyColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.service.EmailServiceStatus;
|
||||
import com.android.emailcommon.service.IEmailService;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.service.SearchParams;
|
||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
import com.android.mail.providers.UIProvider;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* EmailServiceStub is an abstract class representing an EmailService
|
||||
*
|
||||
* This class provides legacy support for a few methods that are common to both
|
||||
* IMAP and POP3, including startSync, loadMore, loadAttachment, and sendMail
|
||||
*/
|
||||
public abstract class EmailServiceStub extends IEmailService.Stub implements IEmailService {
|
||||
|
||||
private static final int MAILBOX_COLUMN_ID = 0;
|
||||
private static final int MAILBOX_COLUMN_SERVER_ID = 1;
|
||||
private static final int MAILBOX_COLUMN_TYPE = 2;
|
||||
|
||||
public static final String SYNC_EXTRA_MAILBOX_ID = "__mailboxId__";
|
||||
|
||||
/** Small projection for just the columns required for a sync. */
|
||||
private static final String[] MAILBOX_PROJECTION = new String[] {
|
||||
MailboxColumns.ID,
|
||||
MailboxColumns.SERVER_ID,
|
||||
MailboxColumns.TYPE,
|
||||
};
|
||||
|
||||
private Context mContext;
|
||||
private IEmailServiceCallback.Stub mCallback;
|
||||
|
||||
protected void init(Context context, IEmailServiceCallback.Stub callbackProxy) {
|
||||
mContext = context;
|
||||
mCallback = callbackProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle validate(HostAuth hostauth) throws RemoteException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSync(long mailboxId, boolean userRequest) throws RemoteException {
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
||||
if (mailbox == null) return;
|
||||
Account account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey);
|
||||
if (account == null) return;
|
||||
android.accounts.Account acct = new android.accounts.Account(account.mEmailAddress,
|
||||
AccountManagerTypes.TYPE_POP_IMAP);
|
||||
Bundle extras = new Bundle();
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
|
||||
extras.putLong(SYNC_EXTRA_MAILBOX_ID, mailboxId);
|
||||
ContentResolver.requestSync(acct, EmailContent.AUTHORITY, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSync(long mailboxId) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMore(long messageId) throws RemoteException {
|
||||
// Load a message for view...
|
||||
try {
|
||||
// 1. Resample the message, in case it disappeared or synced while
|
||||
// this command was in queue
|
||||
EmailContent.Message message =
|
||||
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
||||
if (message == null) {
|
||||
mCallback.loadMessageStatus(messageId,
|
||||
EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
|
||||
return;
|
||||
}
|
||||
if (message.mFlagLoaded == EmailContent.Message.FLAG_LOADED_COMPLETE) {
|
||||
// We should NEVER get here
|
||||
mCallback.loadMessageStatus(messageId, 0, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Open the remote folder.
|
||||
// TODO combine with common code in loadAttachment
|
||||
Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
|
||||
if (account == null || mailbox == null) {
|
||||
//mListeners.loadMessageForViewFailed(messageId, "null account or mailbox");
|
||||
return;
|
||||
}
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account));
|
||||
|
||||
Store remoteStore = Store.getInstance(account, mContext);
|
||||
String remoteServerId = mailbox.mServerId;
|
||||
// If this is a search result, use the protocolSearchInfo field to get the
|
||||
// correct remote location
|
||||
if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
|
||||
remoteServerId = message.mProtocolSearchInfo;
|
||||
}
|
||||
Folder remoteFolder = remoteStore.getFolder(remoteServerId);
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// 3. Set up to download the entire message
|
||||
Message remoteMessage = remoteFolder.getMessage(message.mServerId);
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null);
|
||||
|
||||
// 4. Write to provider
|
||||
Utilities.copyOneMessageToProvider(mContext, remoteMessage, account, mailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
|
||||
// 5. Notify UI
|
||||
mCallback.loadMessageStatus(messageId, 0, 100);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
if (Logging.LOGD) Log.v(Logging.LOG_TAG, "", me);
|
||||
mCallback.loadMessageStatus(messageId, EmailServiceStatus.REMOTE_EXCEPTION, 0);
|
||||
} catch (RuntimeException rte) {
|
||||
mCallback.loadMessageStatus(messageId, EmailServiceStatus.REMOTE_EXCEPTION, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void doProgressCallback(long messageId, long attachmentId, int progress) {
|
||||
try {
|
||||
mCallback.loadAttachmentStatus(messageId, attachmentId,
|
||||
EmailServiceStatus.IN_PROGRESS, progress);
|
||||
} catch (RemoteException e) {
|
||||
// No danger if the client is no longer around
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachment(long attachmentId, boolean background) throws RemoteException {
|
||||
try {
|
||||
//1. Check if the attachment is already here and return early in that case
|
||||
Attachment attachment =
|
||||
Attachment.restoreAttachmentWithId(mContext, attachmentId);
|
||||
if (attachment == null) {
|
||||
mCallback.loadAttachmentStatus(0, attachmentId,
|
||||
EmailServiceStatus.ATTACHMENT_NOT_FOUND, 0);
|
||||
return;
|
||||
}
|
||||
long messageId = attachment.mMessageKey;
|
||||
|
||||
EmailContent.Message message =
|
||||
EmailContent.Message.restoreMessageWithId(mContext, attachment.mMessageKey);
|
||||
if (message == null) {
|
||||
mCallback.loadAttachmentStatus(messageId, attachmentId,
|
||||
EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
|
||||
}
|
||||
|
||||
// If the message is loaded, just report that we're finished
|
||||
if (Utility.attachmentExists(mContext, attachment)) {
|
||||
mCallback.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS,
|
||||
0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Say we're starting...
|
||||
doProgressCallback(messageId, attachmentId, 0);
|
||||
|
||||
// 2. Open the remote folder.
|
||||
Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
|
||||
|
||||
if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
||||
long sourceId = Utility.getFirstRowLong(mContext, Body.CONTENT_URI,
|
||||
new String[] {BodyColumns.SOURCE_MESSAGE_KEY},
|
||||
BodyColumns.MESSAGE_KEY + "=?",
|
||||
new String[] {Long.toString(messageId)}, null, 0, -1L);
|
||||
if (sourceId != -1 ) {
|
||||
EmailContent.Message sourceMsg =
|
||||
EmailContent.Message.restoreMessageWithId(mContext, sourceId);
|
||||
if (sourceMsg != null) {
|
||||
mailbox = Mailbox.restoreMailboxWithId(mContext, sourceMsg.mMailboxKey);
|
||||
message.mServerId = sourceMsg.mServerId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (account == null || mailbox == null) {
|
||||
// If the account/mailbox are gone, just report success; the UI handles this
|
||||
mCallback.loadAttachmentStatus(messageId, attachmentId,
|
||||
EmailServiceStatus.SUCCESS, 0);
|
||||
return;
|
||||
}
|
||||
TrafficStats.setThreadStatsTag(
|
||||
TrafficFlags.getAttachmentFlags(mContext, account));
|
||||
|
||||
Store remoteStore = Store.getInstance(account, mContext);
|
||||
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// 3. Generate a shell message in which to retrieve the attachment,
|
||||
// and a shell BodyPart for the attachment. Then glue them together.
|
||||
Message storeMessage = remoteFolder.createMessage(message.mServerId);
|
||||
MimeBodyPart storePart = new MimeBodyPart();
|
||||
storePart.setSize((int)attachment.mSize);
|
||||
storePart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA,
|
||||
attachment.mLocation);
|
||||
storePart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
String.format("%s;\n name=\"%s\"",
|
||||
attachment.mMimeType,
|
||||
attachment.mFileName));
|
||||
// TODO is this always true for attachments? I think we dropped the
|
||||
// true encoding along the way
|
||||
storePart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
|
||||
MimeMultipart multipart = new MimeMultipart();
|
||||
multipart.setSubType("mixed");
|
||||
multipart.addBodyPart(storePart);
|
||||
|
||||
storeMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
|
||||
storeMessage.setBody(multipart);
|
||||
|
||||
// 4. Now ask for the attachment to be fetched
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(storePart);
|
||||
remoteFolder.fetch(new Message[] { storeMessage }, fp,
|
||||
new MessageRetrievalListenerBridge(messageId, attachmentId));
|
||||
|
||||
// If we failed to load the attachment, throw an Exception here, so that
|
||||
// AttachmentDownloadService knows that we failed
|
||||
if (storePart.getBody() == null) {
|
||||
throw new MessagingException("Attachment not loaded.");
|
||||
}
|
||||
|
||||
// Save the attachment to wherever it's going
|
||||
AttachmentUtilities.saveAttachment(mContext, storePart.getBody().getInputStream(),
|
||||
attachment);
|
||||
|
||||
// 6. Report success
|
||||
mCallback.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0);
|
||||
|
||||
// Close the connection
|
||||
remoteFolder.close(false);
|
||||
}
|
||||
catch (MessagingException me) {
|
||||
if (Logging.LOGD) Log.v(Logging.LOG_TAG, "", me);
|
||||
// TODO: Fix this up; consider the best approach
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(AttachmentColumns.UI_STATE, UIProvider.AttachmentState.FAILED);
|
||||
Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId);
|
||||
mContext.getContentResolver().update(uri, cv, null, null);
|
||||
|
||||
mCallback.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.CONNECTION_ERROR, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge to intercept {@link MessageRetrievalListener#loadAttachmentProgress} and
|
||||
* pass down to {@link Result}.
|
||||
*/
|
||||
public class MessageRetrievalListenerBridge implements MessageRetrievalListener {
|
||||
private final long mMessageId;
|
||||
private final long mAttachmentId;
|
||||
|
||||
public MessageRetrievalListenerBridge(long messageId, long attachmentId) {
|
||||
mMessageId = messageId;
|
||||
mAttachmentId = attachmentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentProgress(int progress) {
|
||||
doProgressCallback(mMessageId, mAttachmentId, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageRetrieved(com.android.emailcommon.mail.Message message) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFolderList(long accountId) throws RemoteException {
|
||||
Account account = Account.restoreAccountWithId(mContext, accountId);
|
||||
if (account == null) return;
|
||||
Mailbox inbox = Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_INBOX);
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account));
|
||||
Cursor localFolderCursor = null;
|
||||
try {
|
||||
// Step 1: Get remote mailboxes
|
||||
Store store = Store.getInstance(account, mContext);
|
||||
Folder[] remoteFolders = store.updateFolders();
|
||||
HashSet<String> remoteFolderNames = new HashSet<String>();
|
||||
for (int i = 0, count = remoteFolders.length; i < count; i++) {
|
||||
remoteFolderNames.add(remoteFolders[i].getName());
|
||||
}
|
||||
|
||||
// Step 2: Get local mailboxes
|
||||
localFolderCursor = mContext.getContentResolver().query(
|
||||
Mailbox.CONTENT_URI,
|
||||
MAILBOX_PROJECTION,
|
||||
EmailContent.MailboxColumns.ACCOUNT_KEY + "=?",
|
||||
new String[] { String.valueOf(account.mId) },
|
||||
null);
|
||||
|
||||
// Step 3: Remove any local mailbox not on the remote list
|
||||
while (localFolderCursor.moveToNext()) {
|
||||
String mailboxPath = localFolderCursor.getString(MAILBOX_COLUMN_SERVER_ID);
|
||||
// Short circuit if we have a remote mailbox with the same name
|
||||
if (remoteFolderNames.contains(mailboxPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int mailboxType = localFolderCursor.getInt(MAILBOX_COLUMN_TYPE);
|
||||
long mailboxId = localFolderCursor.getLong(MAILBOX_COLUMN_ID);
|
||||
switch (mailboxType) {
|
||||
case Mailbox.TYPE_INBOX:
|
||||
case Mailbox.TYPE_DRAFTS:
|
||||
case Mailbox.TYPE_OUTBOX:
|
||||
case Mailbox.TYPE_SENT:
|
||||
case Mailbox.TYPE_TRASH:
|
||||
case Mailbox.TYPE_SEARCH:
|
||||
// Never, ever delete special mailboxes
|
||||
break;
|
||||
default:
|
||||
// Drop all attachment files related to this mailbox
|
||||
AttachmentUtilities.deleteAllMailboxAttachmentFiles(
|
||||
mContext, accountId, mailboxId);
|
||||
// Delete the mailbox; database triggers take care of related
|
||||
// Message, Body and Attachment records
|
||||
Uri uri = ContentUris.withAppendedId(
|
||||
Mailbox.CONTENT_URI, mailboxId);
|
||||
mContext.getContentResolver().delete(uri, null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
// We'll hope this is temporary
|
||||
} finally {
|
||||
if (localFolderCursor != null) {
|
||||
localFolderCursor.close();
|
||||
}
|
||||
// If this is a first sync, find the inbox and sync it
|
||||
if (inbox == null) {
|
||||
inbox = Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_INBOX);
|
||||
if (inbox != null) {
|
||||
startSync(inbox.mId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFolder(long accountId, String name) throws RemoteException {
|
||||
// Not required
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFolder(long accountId, String name) throws RemoteException {
|
||||
// Not required
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameFolder(long accountId, String oldName, String newName)
|
||||
throws RemoteException {
|
||||
// Not required
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(IEmailServiceCallback cb) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogging(int on) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hostChanged(long accountId) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle autoDiscover(String userName, String password) throws RemoteException {
|
||||
// Not required
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMeetingResponse(long messageId, int response) throws RemoteException {
|
||||
// Not required
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccountPIMData(long accountId) throws RemoteException {
|
||||
MailService.reconcilePopImapAccountsSync(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getApiLevel() throws RemoteException {
|
||||
return Api.LEVEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchMessages(long accountId, SearchParams params, long destMailboxId)
|
||||
throws RemoteException {
|
||||
// Not required
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMail(long accountId) throws RemoteException {
|
||||
sendMailImpl(mContext, accountId);
|
||||
}
|
||||
|
||||
public static void sendMailImpl(Context context, long accountId) {
|
||||
Account account = Account.restoreAccountWithId(context, accountId);
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSmtpFlags(context, account));
|
||||
NotificationController nc = NotificationController.getInstance(context);
|
||||
// 1. Loop through all messages in the account's outbox
|
||||
long outboxId = Mailbox.findMailboxOfType(context, account.mId, Mailbox.TYPE_OUTBOX);
|
||||
if (outboxId == Mailbox.NO_MAILBOX) {
|
||||
return;
|
||||
}
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Cursor c = resolver.query(EmailContent.Message.CONTENT_URI,
|
||||
EmailContent.Message.ID_COLUMN_PROJECTION,
|
||||
EmailContent.Message.MAILBOX_KEY + "=?", new String[] { Long.toString(outboxId) },
|
||||
null);
|
||||
try {
|
||||
// 2. exit early
|
||||
if (c.getCount() <= 0) {
|
||||
return;
|
||||
}
|
||||
Sender sender = Sender.getInstance(context, account);
|
||||
Store remoteStore = Store.getInstance(account, context);
|
||||
boolean requireMoveMessageToSentFolder = remoteStore.requireCopyMessageToSentFolder();
|
||||
ContentValues moveToSentValues = null;
|
||||
if (requireMoveMessageToSentFolder) {
|
||||
Mailbox sentFolder =
|
||||
Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_SENT);
|
||||
moveToSentValues = new ContentValues();
|
||||
moveToSentValues.put(MessageColumns.MAILBOX_KEY, sentFolder.mId);
|
||||
}
|
||||
|
||||
// 3. loop through the available messages and send them
|
||||
while (c.moveToNext()) {
|
||||
long messageId = -1;
|
||||
moveToSentValues.remove(EmailContent.MessageColumns.FLAGS);
|
||||
try {
|
||||
messageId = c.getLong(0);
|
||||
// Don't send messages with unloaded attachments
|
||||
if (Utility.hasUnloadedAttachments(context, messageId)) {
|
||||
if (MailActivityEmail.DEBUG) {
|
||||
Log.d(Logging.LOG_TAG, "Can't send #" + messageId +
|
||||
"; unloaded attachments");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
sender.sendMessage(messageId);
|
||||
} catch (MessagingException me) {
|
||||
// report error for this message, but keep trying others
|
||||
if (me instanceof AuthenticationFailedException) {
|
||||
nc.showLoginFailedNotification(account.mId);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// 4. move to sent, or delete
|
||||
Uri syncedUri =
|
||||
ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
|
||||
if (requireMoveMessageToSentFolder) {
|
||||
// If this is a forwarded message and it has attachments, delete them, as they
|
||||
// duplicate information found elsewhere (on the server). This saves storage.
|
||||
EmailContent.Message msg =
|
||||
EmailContent.Message.restoreMessageWithId(context, messageId);
|
||||
if (msg != null &&
|
||||
((msg.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0)) {
|
||||
AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId,
|
||||
messageId);
|
||||
}
|
||||
int flags = msg.mFlags & ~(EmailContent.Message.FLAG_TYPE_REPLY |
|
||||
EmailContent.Message.FLAG_TYPE_FORWARD);
|
||||
moveToSentValues.put(EmailContent.MessageColumns.FLAGS, flags);
|
||||
resolver.update(syncedUri, moveToSentValues, null, null);
|
||||
} else {
|
||||
AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId,
|
||||
messageId);
|
||||
Uri uri =
|
||||
ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId);
|
||||
resolver.delete(uri, null, null);
|
||||
resolver.delete(syncedUri, null, null);
|
||||
}
|
||||
}
|
||||
nc.cancelLoginFailedNotification(account.mId);
|
||||
} catch (MessagingException me) {
|
||||
if (me instanceof AuthenticationFailedException) {
|
||||
nc.showLoginFailedNotification(account.mId);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.email.service;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.FixedLengthInputStream;
|
||||
import com.android.email.mail.store.imap.ImapResponse;
|
||||
import com.android.email.mail.store.imap.ImapResponseParser;
|
||||
import com.android.email.mail.store.imap.ImapString;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.TempDirectory;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Subclass of {@link ImapString} used for literals backed by a temp file.
|
||||
*/
|
||||
public class ImapTempFileLiteral extends ImapString {
|
||||
/* package for test */ final File mFile;
|
||||
|
||||
/** Size is purely for toString() */
|
||||
private final int mSize;
|
||||
|
||||
/* package */ ImapTempFileLiteral(FixedLengthInputStream stream) throws IOException {
|
||||
mSize = stream.getLength();
|
||||
mFile = File.createTempFile("imap", ".tmp", TempDirectory.getTempDirectory());
|
||||
|
||||
// Unfortunately, we can't really use deleteOnExit(), because temp filenames are random
|
||||
// so it'd simply cause a memory leak.
|
||||
// deleteOnExit() simply adds filenames to a static list and the list will never shrink.
|
||||
// mFile.deleteOnExit();
|
||||
OutputStream out = new FileOutputStream(mFile);
|
||||
IOUtils.copy(stream, out);
|
||||
out.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we delete the temp file.
|
||||
*
|
||||
* We should always be calling {@link ImapResponse#destroy()}, but it's here as a last resort.
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
destroy();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getAsStream() {
|
||||
checkNotDestroyed();
|
||||
try {
|
||||
return new FileInputStream(mFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
// It's probably possible if we're low on storage and the system clears the cache dir.
|
||||
Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Temp file not found");
|
||||
|
||||
// Return 0 byte stream as a dummy...
|
||||
return new ByteArrayInputStream(new byte[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString() {
|
||||
checkNotDestroyed();
|
||||
try {
|
||||
byte[] bytes = IOUtils.toByteArray(getAsStream());
|
||||
// Prevent crash from OOM; we've seen this, but only rarely and not reproducibly
|
||||
if (bytes.length > ImapResponseParser.LITERAL_KEEP_IN_MEMORY_THRESHOLD) {
|
||||
throw new IOException();
|
||||
}
|
||||
return Utility.fromAscii(bytes);
|
||||
} catch (IOException e) {
|
||||
Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Error while reading temp file", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
try {
|
||||
if (!isDestroyed() && mFile.exists()) {
|
||||
mFile.delete();
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
// Just log and ignore.
|
||||
Log.w(Logging.LOG_TAG, "Failed to remove temp file: " + re.getMessage());
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{%d byte literal(file)}", mSize);
|
||||
}
|
||||
|
||||
public boolean tempFileExistsForTest() {
|
||||
return mFile.exists();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,726 @@
|
|||
/*
|
||||
* Copyright (C) 2012 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 android.app.Service;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.LegacyConversions;
|
||||
import com.android.email.NotificationController;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.email.provider.Utilities;
|
||||
import com.android.email2.ui.MailActivityEmail;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.TrafficFlags;
|
||||
import com.android.emailcommon.internet.MimeUtility;
|
||||
import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||
import com.android.emailcommon.mail.FetchProfile;
|
||||
import com.android.emailcommon.mail.Flag;
|
||||
import com.android.emailcommon.mail.Folder;
|
||||
import com.android.emailcommon.mail.Folder.FolderType;
|
||||
import com.android.emailcommon.mail.Folder.MessageRetrievalListener;
|
||||
import com.android.emailcommon.mail.Folder.OpenMode;
|
||||
import com.android.emailcommon.mail.Message;
|
||||
import com.android.emailcommon.mail.MessagingException;
|
||||
import com.android.emailcommon.mail.Part;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.service.EmailServiceStatus;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||
import com.android.mail.providers.UIProvider.AccountCapabilities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class Pop3Service extends Service {
|
||||
private static final String TAG = "Pop3Service";
|
||||
private static final int MAX_SMALL_MESSAGE_SIZE = (25 * 1024);
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
// Callbacks as set up via setCallback
|
||||
private static final RemoteCallbackList<IEmailServiceCallback> mCallbackList =
|
||||
new RemoteCallbackList<IEmailServiceCallback>();
|
||||
|
||||
private interface ServiceCallbackWrapper {
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy that can be used by various sync adapters to tie into ExchangeService's callback system
|
||||
* Used this way: ExchangeService.callback().callbackMethod(args...);
|
||||
* The proxy wraps checking for existence of a ExchangeService instance
|
||||
* Failures of these callbacks can be safely ignored.
|
||||
*/
|
||||
static private final IEmailServiceCallback.Stub sCallbackProxy =
|
||||
new IEmailServiceCallback.Stub() {
|
||||
|
||||
/**
|
||||
* Broadcast a callback to the everyone that's registered
|
||||
*
|
||||
* @param wrapper the ServiceCallbackWrapper used in the broadcast
|
||||
*/
|
||||
private synchronized void broadcastCallback(ServiceCallbackWrapper wrapper) {
|
||||
RemoteCallbackList<IEmailServiceCallback> callbackList = mCallbackList;
|
||||
if (callbackList != null) {
|
||||
// Call everyone on our callback list
|
||||
int count = callbackList.beginBroadcast();
|
||||
try {
|
||||
for (int i = 0; i < count; i++) {
|
||||
try {
|
||||
wrapper.call(callbackList.getBroadcastItem(i));
|
||||
} catch (RemoteException e) {
|
||||
// Safe to ignore
|
||||
} catch (RuntimeException e) {
|
||||
// We don't want an exception in one call to prevent other calls, so
|
||||
// we'll just log this and continue
|
||||
Log.e(TAG, "Caught RuntimeException in broadcast", e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// No matter what, we need to finish the broadcast
|
||||
callbackList.finishBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentStatus(final long messageId, final long attachmentId,
|
||||
final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.loadAttachmentStatus(messageId, attachmentId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageStatus(final long messageId, final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.loadMessageStatus(messageId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageStatus(final long messageId, final String subject, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.sendMessageStatus(messageId, subject, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxListStatus(final long accountId, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.syncMailboxListStatus(accountId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxStatus(final long mailboxId, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.syncMailboxStatus(mailboxId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create our EmailService implementation here.
|
||||
*/
|
||||
private final EmailServiceStub mBinder = new EmailServiceStub() {
|
||||
|
||||
@Override
|
||||
public void setCallback(IEmailServiceCallback cb) throws RemoteException {
|
||||
mCallbackList.register(cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities(long accountId) throws RemoteException {
|
||||
return AccountCapabilities.UNDO;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
mBinder.init(this, sCallbackProxy);
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
private static void sendMailboxStatus(Mailbox mailbox, int status) {
|
||||
try {
|
||||
sCallbackProxy.syncMailboxStatus(mailbox.mId, status, 0);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start foreground synchronization of the specified folder. This is called by
|
||||
* synchronizeMailbox or checkMail.
|
||||
* TODO this should use ID's instead of fully-restored objects
|
||||
* @param account
|
||||
* @param folder
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public static void synchronizeMailboxSynchronous(Context context, final Account account,
|
||||
final Mailbox folder) throws MessagingException {
|
||||
sendMailboxStatus(folder, EmailServiceStatus.IN_PROGRESS);
|
||||
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
|
||||
if ((folder.mFlags & Mailbox.FLAG_HOLDS_MAIL) == 0) {
|
||||
sendMailboxStatus(folder, EmailServiceStatus.SUCCESS);
|
||||
}
|
||||
NotificationController nc = NotificationController.getInstance(context);
|
||||
try {
|
||||
processPendingActionsSynchronous(context, account);
|
||||
synchronizeMailboxGeneric(context, account, folder);
|
||||
// Clear authentication notification for this account
|
||||
nc.cancelLoginFailedNotification(account.mId);
|
||||
sendMailboxStatus(folder, EmailServiceStatus.SUCCESS);
|
||||
} catch (MessagingException e) {
|
||||
if (Logging.LOGD) {
|
||||
Log.v(Logging.LOG_TAG, "synchronizeMailbox", e);
|
||||
}
|
||||
if (e instanceof AuthenticationFailedException) {
|
||||
// Generate authentication notification
|
||||
nc.showLoginFailedNotification(account.mId);
|
||||
}
|
||||
sendMailboxStatus(folder, e.getExceptionType());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight record for the first pass of message sync, where I'm just seeing if
|
||||
* the local message requires sync. Later (for messages that need syncing) we'll do a full
|
||||
* readout from the DB.
|
||||
*/
|
||||
private static class LocalMessageInfo {
|
||||
private static final int COLUMN_ID = 0;
|
||||
private static final int COLUMN_FLAG_READ = 1;
|
||||
private static final int COLUMN_FLAG_FAVORITE = 2;
|
||||
private static final int COLUMN_FLAG_LOADED = 3;
|
||||
private static final int COLUMN_SERVER_ID = 4;
|
||||
private static final int COLUMN_FLAGS = 7;
|
||||
private static final String[] PROJECTION = new String[] {
|
||||
EmailContent.RECORD_ID,
|
||||
MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_LOADED,
|
||||
SyncColumns.SERVER_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
|
||||
MessageColumns.FLAGS
|
||||
};
|
||||
|
||||
final long mId;
|
||||
final boolean mFlagRead;
|
||||
final boolean mFlagFavorite;
|
||||
final int mFlagLoaded;
|
||||
final String mServerId;
|
||||
final int mFlags;
|
||||
|
||||
public LocalMessageInfo(Cursor c) {
|
||||
mId = c.getLong(COLUMN_ID);
|
||||
mFlagRead = c.getInt(COLUMN_FLAG_READ) != 0;
|
||||
mFlagFavorite = c.getInt(COLUMN_FLAG_FAVORITE) != 0;
|
||||
mFlagLoaded = c.getInt(COLUMN_FLAG_LOADED);
|
||||
mServerId = c.getString(COLUMN_SERVER_ID);
|
||||
mFlags = c.getInt(COLUMN_FLAGS);
|
||||
// Note: mailbox key and account key not needed - they are projected for the SELECT
|
||||
}
|
||||
}
|
||||
|
||||
private static void saveOrUpdate(EmailContent content, Context context) {
|
||||
if (content.isSaved()) {
|
||||
content.update(context, content.toContentValues());
|
||||
} else {
|
||||
content.save(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the structure and body of messages not yet synced
|
||||
* @param account the account we're syncing
|
||||
* @param remoteFolder the (open) Folder we're working on
|
||||
* @param unsyncedMessages an array of Message's we've got headers for
|
||||
* @param toMailbox the destination mailbox we're syncing
|
||||
* @throws MessagingException
|
||||
*/
|
||||
static void loadUnsyncedMessages(final Context context, final Account account,
|
||||
Folder remoteFolder, ArrayList<Message> unsyncedMessages, final Mailbox toMailbox)
|
||||
throws MessagingException {
|
||||
|
||||
// 1. Divide the unsynced messages into small & large (by size)
|
||||
|
||||
// TODO doing this work here (synchronously) is problematic because it prevents the UI
|
||||
// from affecting the order (e.g. download a message because the user requested it.) Much
|
||||
// of this logic should move out to a different sync loop that attempts to update small
|
||||
// groups of messages at a time, as a background task. However, we can't just return
|
||||
// (yet) because POP messages don't have an envelope yet....
|
||||
|
||||
ArrayList<Message> largeMessages = new ArrayList<Message>();
|
||||
ArrayList<Message> smallMessages = new ArrayList<Message>();
|
||||
for (Message message : unsyncedMessages) {
|
||||
if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) {
|
||||
largeMessages.add(message);
|
||||
} else {
|
||||
smallMessages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Download small messages
|
||||
|
||||
// TODO Problems with this implementation. 1. For IMAP, where we get a real envelope,
|
||||
// this is going to be inefficient and duplicate work we've already done. 2. It's going
|
||||
// back to the DB for a local message that we already had (and discarded).
|
||||
|
||||
// For small messages, we specify "body", which returns everything (incl. attachments)
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), fp,
|
||||
new MessageRetrievalListener() {
|
||||
@Override
|
||||
public void messageRetrieved(Message message) {
|
||||
// Store the updated message locally and mark it fully loaded
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentProgress(int progress) {
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Download large messages. We ask the server to give us the message structure,
|
||||
// but not all of the attachments.
|
||||
fp.clear();
|
||||
fp.add(FetchProfile.Item.STRUCTURE);
|
||||
remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), fp, null);
|
||||
for (Message message : largeMessages) {
|
||||
if (message.getBody() == null) {
|
||||
// POP doesn't support STRUCTURE mode, so we'll just do a partial download
|
||||
// (hopefully enough to see some/all of the body) and mark the message for
|
||||
// further download.
|
||||
fp.clear();
|
||||
fp.add(FetchProfile.Item.BODY_SANE);
|
||||
// TODO a good optimization here would be to make sure that all Stores set
|
||||
// the proper size after this fetch and compare the before and after size. If
|
||||
// they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED
|
||||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||
|
||||
// Store the partially-loaded message and mark it partially loaded
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_PARTIAL);
|
||||
} else {
|
||||
// We have a structure to deal with, from which
|
||||
// we can pull down the parts we want to actually store.
|
||||
// Build a list of parts we are interested in. Text parts will be downloaded
|
||||
// right now, attachments will be left for later.
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
// Download the viewables immediately
|
||||
for (Part part : viewables) {
|
||||
fp.clear();
|
||||
fp.add(part);
|
||||
// TODO what happens if the network connection dies? We've got partial
|
||||
// messages with incorrect status stored.
|
||||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||
}
|
||||
// Store the updated message locally and mark it fully loaded
|
||||
Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
|
||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void downloadFlagAndEnvelope(final Context context, final Account account,
|
||||
final Mailbox mailbox, Folder remoteFolder, ArrayList<Message> unsyncedMessages,
|
||||
HashMap<String, LocalMessageInfo> localMessageMap, final ArrayList<Long> unseenMessages)
|
||||
throws MessagingException {
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.FLAGS);
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
|
||||
final HashMap<String, LocalMessageInfo> localMapCopy;
|
||||
if (localMessageMap != null)
|
||||
localMapCopy = new HashMap<String, LocalMessageInfo>(localMessageMap);
|
||||
else {
|
||||
localMapCopy = new HashMap<String, LocalMessageInfo>();
|
||||
}
|
||||
|
||||
remoteFolder.fetch(unsyncedMessages.toArray(new Message[0]), fp,
|
||||
new MessageRetrievalListener() {
|
||||
@Override
|
||||
public void messageRetrieved(Message message) {
|
||||
try {
|
||||
// Determine if the new message was already known (e.g. partial)
|
||||
// And create or reload the full message info
|
||||
LocalMessageInfo localMessageInfo =
|
||||
localMapCopy.get(message.getUid());
|
||||
EmailContent.Message localMessage = null;
|
||||
if (localMessageInfo == null) {
|
||||
localMessage = new EmailContent.Message();
|
||||
} else {
|
||||
localMessage = EmailContent.Message.restoreMessageWithId(
|
||||
context, localMessageInfo.mId);
|
||||
}
|
||||
|
||||
if (localMessage != null) {
|
||||
try {
|
||||
// Copy the fields that are available into the message
|
||||
LegacyConversions.updateMessageFields(localMessage,
|
||||
message, account.mId, mailbox.mId);
|
||||
// Commit the message to the local store
|
||||
saveOrUpdate(localMessage, context);
|
||||
// Track the "new" ness of the downloaded message
|
||||
if (!message.isSet(Flag.SEEN) && unseenMessages != null) {
|
||||
unseenMessages.add(localMessage.mId);
|
||||
}
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Logging.LOG_TAG,
|
||||
"Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.e(Logging.LOG_TAG,
|
||||
"Error while storing downloaded message." + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentProgress(int progress) {
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizer for IMAP.
|
||||
*
|
||||
* TODO Break this method up into smaller chunks.
|
||||
*
|
||||
* @param account the account to sync
|
||||
* @param mailbox the mailbox to sync
|
||||
* @return results of the sync pass
|
||||
* @throws MessagingException
|
||||
*/
|
||||
private static void synchronizeMailboxGeneric(final Context context,
|
||||
final Account account, final Mailbox mailbox) throws MessagingException {
|
||||
|
||||
/*
|
||||
* A list of IDs for messages that were downloaded and did not have the seen flag set.
|
||||
* This serves as the "true" new message count reported to the user via notification.
|
||||
*/
|
||||
final ArrayList<Long> unseenMessages = new ArrayList<Long>();
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
|
||||
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Get the message list from the local store and create an index of the uids
|
||||
|
||||
Cursor localUidCursor = null;
|
||||
HashMap<String, LocalMessageInfo> localMessageMap = new HashMap<String, LocalMessageInfo>();
|
||||
|
||||
try {
|
||||
localUidCursor = resolver.query(
|
||||
EmailContent.Message.CONTENT_URI,
|
||||
LocalMessageInfo.PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
||||
" AND " + MessageColumns.MAILBOX_KEY + "=?",
|
||||
new String[] {
|
||||
String.valueOf(account.mId),
|
||||
String.valueOf(mailbox.mId)
|
||||
},
|
||||
null);
|
||||
while (localUidCursor.moveToNext()) {
|
||||
LocalMessageInfo info = new LocalMessageInfo(localUidCursor);
|
||||
localMessageMap.put(info.mServerId, info);
|
||||
}
|
||||
} finally {
|
||||
if (localUidCursor != null) {
|
||||
localUidCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Open the remote folder and create the remote folder if necessary
|
||||
|
||||
Store remoteStore = Store.getInstance(account, context);
|
||||
// The account might have been deleted
|
||||
if (remoteStore == null) return;
|
||||
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
||||
|
||||
/*
|
||||
* If the folder is a "special" folder we need to see if it exists
|
||||
* on the remote server. It if does not exist we'll try to create it. If we
|
||||
* can't create we'll abort. This will happen on every single Pop3 folder as
|
||||
* designed and on Imap folders during error conditions. This allows us
|
||||
* to treat Pop3 and Imap the same in this code.
|
||||
*/
|
||||
if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT
|
||||
|| mailbox.mType == Mailbox.TYPE_DRAFTS) {
|
||||
if (!remoteFolder.exists()) {
|
||||
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3, Open the remote folder. This pre-loads certain metadata like message count.
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// 4. Trash any remote messages that are marked as trashed locally.
|
||||
// TODO - this comment was here, but no code was here.
|
||||
|
||||
// 5. Get the remote message count.
|
||||
int remoteMessageCount = remoteFolder.getMessageCount();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MailboxColumns.TOTAL_COUNT, remoteMessageCount);
|
||||
mailbox.update(context, values);
|
||||
|
||||
// 6. Determine the limit # of messages to download
|
||||
int visibleLimit = mailbox.mVisibleLimit;
|
||||
if (visibleLimit <= 0) {
|
||||
visibleLimit = MailActivityEmail.VISIBLE_LIMIT_DEFAULT;
|
||||
}
|
||||
|
||||
// 7. Create a list of messages to download
|
||||
Message[] remoteMessages = new Message[0];
|
||||
final ArrayList<Message> unsyncedMessages = new ArrayList<Message>();
|
||||
HashMap<String, Message> remoteUidMap = new HashMap<String, Message>();
|
||||
|
||||
if (remoteMessageCount > 0) {
|
||||
/*
|
||||
* Message numbers start at 1.
|
||||
*/
|
||||
int remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1;
|
||||
int remoteEnd = remoteMessageCount;
|
||||
remoteMessages = remoteFolder.getMessages(remoteStart, remoteEnd, null);
|
||||
// TODO Why are we running through the list twice? Combine w/ for loop below
|
||||
for (Message message : remoteMessages) {
|
||||
remoteUidMap.put(message.getUid(), message);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a list of the messages that are in the remote list but not on the
|
||||
* local store, or messages that are in the local store but failed to download
|
||||
* on the last sync. These are the new messages that we will download.
|
||||
* Note, we also skip syncing messages which are flagged as "deleted message" sentinels,
|
||||
* because they are locally deleted and we don't need or want the old message from
|
||||
* the server.
|
||||
*/
|
||||
for (Message message : remoteMessages) {
|
||||
LocalMessageInfo localMessage = localMessageMap.get(message.getUid());
|
||||
// localMessage == null -> message has never been created (not even headers)
|
||||
// mFlagLoaded = UNLOADED -> message created, but none of body loaded
|
||||
// mFlagLoaded = PARTIAL -> message created, a "sane" amt of body has been loaded
|
||||
// mFlagLoaded = COMPLETE -> message body has been completely loaded
|
||||
// mFlagLoaded = DELETED -> message has been deleted
|
||||
// Only the first two of these are "unsynced", so let's retrieve them
|
||||
if (localMessage == null ||
|
||||
(localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_UNLOADED)) {
|
||||
unsyncedMessages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Download basic info about the new/unloaded messages (if any)
|
||||
/*
|
||||
* Fetch the flags and envelope only of the new messages. This is intended to get us
|
||||
* critical data as fast as possible, and then we'll fill in the details.
|
||||
*/
|
||||
if (unsyncedMessages.size() > 0) {
|
||||
downloadFlagAndEnvelope(context, account, mailbox, remoteFolder, unsyncedMessages,
|
||||
localMessageMap, unseenMessages);
|
||||
}
|
||||
|
||||
// 9. Refresh the flags for any messages in the local store that we didn't just download.
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.FLAGS);
|
||||
remoteFolder.fetch(remoteMessages, fp, null);
|
||||
boolean remoteSupportsSeen = false;
|
||||
boolean remoteSupportsFlagged = false;
|
||||
boolean remoteSupportsAnswered = false;
|
||||
for (Flag flag : remoteFolder.getPermanentFlags()) {
|
||||
if (flag == Flag.SEEN) {
|
||||
remoteSupportsSeen = true;
|
||||
}
|
||||
if (flag == Flag.FLAGGED) {
|
||||
remoteSupportsFlagged = true;
|
||||
}
|
||||
if (flag == Flag.ANSWERED) {
|
||||
remoteSupportsAnswered = true;
|
||||
}
|
||||
}
|
||||
// Update SEEN/FLAGGED/ANSWERED (star) flags (if supported remotely - e.g. not for POP3)
|
||||
if (remoteSupportsSeen || remoteSupportsFlagged || remoteSupportsAnswered) {
|
||||
for (Message remoteMessage : remoteMessages) {
|
||||
LocalMessageInfo localMessageInfo = localMessageMap.get(remoteMessage.getUid());
|
||||
if (localMessageInfo == null) {
|
||||
continue;
|
||||
}
|
||||
boolean localSeen = localMessageInfo.mFlagRead;
|
||||
boolean remoteSeen = remoteMessage.isSet(Flag.SEEN);
|
||||
boolean newSeen = (remoteSupportsSeen && (remoteSeen != localSeen));
|
||||
boolean localFlagged = localMessageInfo.mFlagFavorite;
|
||||
boolean remoteFlagged = remoteMessage.isSet(Flag.FLAGGED);
|
||||
boolean newFlagged = (remoteSupportsFlagged && (localFlagged != remoteFlagged));
|
||||
int localFlags = localMessageInfo.mFlags;
|
||||
boolean localAnswered = (localFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0;
|
||||
boolean remoteAnswered = remoteMessage.isSet(Flag.ANSWERED);
|
||||
boolean newAnswered = (remoteSupportsAnswered && (localAnswered != remoteAnswered));
|
||||
if (newSeen || newFlagged || newAnswered) {
|
||||
Uri uri = ContentUris.withAppendedId(
|
||||
EmailContent.Message.CONTENT_URI, localMessageInfo.mId);
|
||||
ContentValues updateValues = new ContentValues();
|
||||
updateValues.put(MessageColumns.FLAG_READ, remoteSeen);
|
||||
updateValues.put(MessageColumns.FLAG_FAVORITE, remoteFlagged);
|
||||
if (remoteAnswered) {
|
||||
localFlags |= EmailContent.Message.FLAG_REPLIED_TO;
|
||||
} else {
|
||||
localFlags &= ~EmailContent.Message.FLAG_REPLIED_TO;
|
||||
}
|
||||
updateValues.put(MessageColumns.FLAGS, localFlags);
|
||||
resolver.update(uri, updateValues, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Remove any messages that are in the local store but no longer on the remote store.
|
||||
HashSet<String> localUidsToDelete = new HashSet<String>(localMessageMap.keySet());
|
||||
localUidsToDelete.removeAll(remoteUidMap.keySet());
|
||||
for (String uidToDelete : localUidsToDelete) {
|
||||
LocalMessageInfo infoToDelete = localMessageMap.get(uidToDelete);
|
||||
|
||||
// Delete associated data (attachment files)
|
||||
// Attachment & Body records are auto-deleted when we delete the Message record
|
||||
AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId,
|
||||
infoToDelete.mId);
|
||||
|
||||
// Delete the message itself
|
||||
Uri uriToDelete = ContentUris.withAppendedId(
|
||||
EmailContent.Message.CONTENT_URI, infoToDelete.mId);
|
||||
resolver.delete(uriToDelete, null, null);
|
||||
|
||||
// Delete extra rows (e.g. synced or deleted)
|
||||
Uri syncRowToDelete = ContentUris.withAppendedId(
|
||||
EmailContent.Message.UPDATED_CONTENT_URI, infoToDelete.mId);
|
||||
resolver.delete(syncRowToDelete, null, null);
|
||||
Uri deletERowToDelete = ContentUris.withAppendedId(
|
||||
EmailContent.Message.UPDATED_CONTENT_URI, infoToDelete.mId);
|
||||
resolver.delete(deletERowToDelete, null, null);
|
||||
}
|
||||
|
||||
loadUnsyncedMessages(context, account, remoteFolder, unsyncedMessages, mailbox);
|
||||
|
||||
// 14. Clean up and report results
|
||||
remoteFolder.close(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find messages in the updated table that need to be written back to server.
|
||||
*
|
||||
* Handles:
|
||||
* Read/Unread
|
||||
* Flagged
|
||||
* Append (upload)
|
||||
* Move To Trash
|
||||
* Empty trash
|
||||
* TODO:
|
||||
* Move
|
||||
*
|
||||
* @param account the account to scan for pending actions
|
||||
* @throws MessagingException
|
||||
*/
|
||||
private static void processPendingActionsSynchronous(Context context, Account account)
|
||||
throws MessagingException {
|
||||
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
|
||||
String[] accountIdArgs = new String[] { Long.toString(account.mId) };
|
||||
|
||||
// Handle deletes first, it's always better to get rid of things first
|
||||
processPendingDeletesSynchronous(context, account, accountIdArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for messages that are in the Message_Deletes table, look for differences that
|
||||
* we can deal with, and do the work.
|
||||
*
|
||||
* @param account
|
||||
* @param resolver
|
||||
* @param accountIdArgs
|
||||
*/
|
||||
private static void processPendingDeletesSynchronous(Context context, Account account,
|
||||
String[] accountIdArgs) {
|
||||
Cursor deletes = context.getContentResolver().query(
|
||||
EmailContent.Message.DELETED_CONTENT_URI,
|
||||
EmailContent.Message.CONTENT_PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs,
|
||||
EmailContent.MessageColumns.MAILBOX_KEY);
|
||||
try {
|
||||
// loop through messages marked as deleted
|
||||
while (deletes.moveToNext()) {
|
||||
EmailContent.Message oldMessage =
|
||||
EmailContent.getContent(deletes, EmailContent.Message.class);
|
||||
|
||||
// Finally, delete the update
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.DELETED_CONTENT_URI,
|
||||
oldMessage.mId);
|
||||
context.getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
} finally {
|
||||
deletes.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Google Inc.
|
||||
* 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.email2.ui;
|
||||
|
||||
import com.android.mail.providers.Account;
|
||||
import com.android.mail.ui.FolderSelectionActivity;
|
||||
import com.android.mail.ui.MailboxSelectionActivity;
|
||||
import com.android.mail.utils.AccountUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class CreateShortcutActivityEmail extends Activity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
Account[] cachedAccounts = AccountUtils.getSyncingAccounts(this);
|
||||
Intent intent = getIntent();
|
||||
if (cachedAccounts != null && cachedAccounts.length == 1) {
|
||||
intent.setClass(this, FolderSelectionActivity.class);
|
||||
intent.setFlags(
|
||||
Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
intent.setAction(Intent.ACTION_CREATE_SHORTCUT);
|
||||
intent.putExtra(FolderSelectionActivity.EXTRA_ACCOUNT_SHORTCUT,
|
||||
cachedAccounts[0]);
|
||||
} else {
|
||||
intent.setClass(this, MailboxSelectionActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
}
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* Copyright (C) 2008 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.email2.ui;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.email.NotificationController;
|
||||
import com.android.email.Preferences;
|
||||
import com.android.email.R;
|
||||
import com.android.email.service.AttachmentDownloadService;
|
||||
import com.android.email.service.EmailServiceUtils;
|
||||
import com.android.email.service.MailService;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.TempDirectory;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
import com.android.emailcommon.utility.EmailAsyncTask;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
||||
public class MailActivityEmail extends com.android.mail.ui.MailActivity {
|
||||
/**
|
||||
* If this is enabled there will be additional logging information sent to
|
||||
* Log.d, including protocol dumps.
|
||||
*
|
||||
* This should only be used for logs that are useful for debbuging user problems,
|
||||
* not for internal/development logs.
|
||||
*
|
||||
* This can be enabled by typing "debug" in the AccountFolderList activity.
|
||||
* Changing the value to 'true' here will likely have no effect at all!
|
||||
*
|
||||
* TODO: rename this to sUserDebug, and rename LOGD below to DEBUG.
|
||||
*/
|
||||
public static boolean DEBUG;
|
||||
|
||||
// Exchange debugging flags (passed to Exchange, when available, via EmailServiceProxy)
|
||||
public static boolean DEBUG_EXCHANGE;
|
||||
public static boolean DEBUG_VERBOSE;
|
||||
public static boolean DEBUG_FILE;
|
||||
|
||||
/**
|
||||
* If true, inhibit hardware graphics acceleration in UI (for a/b testing)
|
||||
*/
|
||||
public static boolean sDebugInhibitGraphicsAcceleration = false;
|
||||
|
||||
/**
|
||||
* Specifies how many messages will be shown in a folder by default. This number is set
|
||||
* on each new folder and can be incremented with "Load more messages..." by the
|
||||
* VISIBLE_LIMIT_INCREMENT
|
||||
*/
|
||||
public static final int VISIBLE_LIMIT_DEFAULT = 25;
|
||||
|
||||
/**
|
||||
* Number of additional messages to load when a user selects "Load more messages..."
|
||||
*/
|
||||
public static final int VISIBLE_LIMIT_INCREMENT = 25;
|
||||
|
||||
/**
|
||||
* This is used to force stacked UI to return to the "welcome" screen any time we change
|
||||
* the accounts list (e.g. deleting accounts in the Account Manager preferences.)
|
||||
*/
|
||||
private static boolean sAccountsChangedNotification = false;
|
||||
|
||||
private static String sMessageDecodeErrorString;
|
||||
|
||||
private static Thread sUiThread;
|
||||
|
||||
/**
|
||||
* Asynchronous version of {@link #setServicesEnabledSync(Context)}. Use when calling from
|
||||
* UI thread (or lifecycle entry points.)
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public static void setServicesEnabledAsync(final Context context) {
|
||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setServicesEnabledSync(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called throughout the application when the number of accounts has changed. This method
|
||||
* enables or disables the Compose activity, the boot receiver and the service based on
|
||||
* whether any accounts are configured.
|
||||
*
|
||||
* Blocking call - do not call from UI/lifecycle threads.
|
||||
*
|
||||
* @param context
|
||||
* @return true if there are any accounts configured.
|
||||
*/
|
||||
public static boolean setServicesEnabledSync(Context context) {
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = context.getContentResolver().query(
|
||||
Account.CONTENT_URI,
|
||||
Account.ID_PROJECTION,
|
||||
null, null, null);
|
||||
boolean enable = c.getCount() > 0;
|
||||
setServicesEnabled(context, enable);
|
||||
return enable;
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void setServicesEnabled(Context context, boolean enabled) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
pm.setComponentEnabledSetting(
|
||||
new ComponentName(context, MailService.class),
|
||||
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
pm.setComponentEnabledSetting(
|
||||
new ComponentName(context, AttachmentDownloadService.class),
|
||||
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
|
||||
// Start/stop the various services depending on whether there are any accounts
|
||||
startOrStopService(enabled, context, new Intent(context, AttachmentDownloadService.class));
|
||||
NotificationController.getInstance(context).watchForMessages(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or stops the service as necessary.
|
||||
* @param enabled If {@code true}, the service will be started. Otherwise, it will be stopped.
|
||||
* @param context The context to manage the service with.
|
||||
* @param intent The intent of the service to be managed.
|
||||
*/
|
||||
private static void startOrStopService(boolean enabled, Context context, Intent intent) {
|
||||
if (enabled) {
|
||||
context.startService(intent);
|
||||
} else {
|
||||
context.stopService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
sUiThread = Thread.currentThread();
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
DEBUG = prefs.getEnableDebugLogging();
|
||||
sDebugInhibitGraphicsAcceleration = prefs.getInhibitGraphicsAcceleration();
|
||||
enableStrictMode(prefs.getEnableStrictMode());
|
||||
TempDirectory.setTempDirectory(this);
|
||||
|
||||
// Enable logging in the EAS service, so it starts up as early as possible.
|
||||
updateLoggingFlags(this);
|
||||
|
||||
// Get a helper string used deep inside message decoders (which don't have context)
|
||||
sMessageDecodeErrorString = getString(R.string.message_decode_error);
|
||||
|
||||
// Make sure all required services are running when the app is started (can prevent
|
||||
// issues after an adb sync/install)
|
||||
setServicesEnabledAsync(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load enabled debug flags from the preferences and update the EAS debug flag.
|
||||
*/
|
||||
public static void updateLoggingFlags(Context context) {
|
||||
Preferences prefs = Preferences.getPreferences(context);
|
||||
int debugLogging = prefs.getEnableDebugLogging() ? EmailServiceProxy.DEBUG_BIT : 0;
|
||||
int verboseLogging =
|
||||
prefs.getEnableExchangeLogging() ? EmailServiceProxy.DEBUG_VERBOSE_BIT : 0;
|
||||
int fileLogging =
|
||||
prefs.getEnableExchangeFileLogging() ? EmailServiceProxy.DEBUG_FILE_BIT : 0;
|
||||
int enableStrictMode =
|
||||
prefs.getEnableStrictMode() ? EmailServiceProxy.DEBUG_ENABLE_STRICT_MODE : 0;
|
||||
int debugBits = debugLogging | verboseLogging | fileLogging | enableStrictMode;
|
||||
EmailServiceUtils.setRemoteServicesLogging(context, debugBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal, utility method for logging.
|
||||
* The calls to log() must be guarded with "if (Email.LOGD)" for performance reasons.
|
||||
*/
|
||||
public static void log(String message) {
|
||||
Log.d(Logging.LOG_TAG, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the accounts reconciler to notify that accounts have changed, or by "Welcome"
|
||||
* to clear the flag.
|
||||
* @param setFlag true to set the notification flag, false to clear it
|
||||
*/
|
||||
public static synchronized void setNotifyUiAccountsChanged(boolean setFlag) {
|
||||
sAccountsChangedNotification = setFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from activity onResume() functions to check for an accounts-changed condition, at
|
||||
* which point they should finish() and jump to the Welcome activity.
|
||||
*/
|
||||
public static synchronized boolean getNotifyUiAccountsChanged() {
|
||||
return sAccountsChangedNotification;
|
||||
}
|
||||
|
||||
public static void warnIfUiThread() {
|
||||
if (Thread.currentThread().equals(sUiThread)) {
|
||||
Log.w(Logging.LOG_TAG, "Method called on the UI thread", new Exception("STACK TRACE"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a simple string that can be used when message decoders encounter bad data.
|
||||
* This is provided here because the protocol decoders typically don't have mContext.
|
||||
*/
|
||||
public static String getMessageDecodeErrorString() {
|
||||
return sMessageDecodeErrorString != null ? sMessageDecodeErrorString : "";
|
||||
}
|
||||
|
||||
public static void enableStrictMode(boolean enabled) {
|
||||
Utility.enableStrictMode(enabled);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (C) 2012 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.email2.ui;
|
||||
|
||||
/* An activity that shows the list of all the available accounts and return the
|
||||
* one selected in onResult().
|
||||
*/
|
||||
public class MailboxSelectionActivityEmail extends com.android.mail.ui.MailboxSelectionActivity {
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2012 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.mail.browse;
|
||||
|
||||
import com.android.mail.browse.ConversationCursor.ConversationProvider;
|
||||
|
||||
import java.lang.Override;
|
||||
|
||||
public class EmailConversationProvider extends ConversationProvider {
|
||||
// The authority of our conversation provider (a forwarding provider)
|
||||
// This string must match the declaration in AndroidManifest.xml
|
||||
private static final String sAuthority = "com.android.email2.conversation.provider";
|
||||
|
||||
@Override
|
||||
protected String getAuthority() {
|
||||
return sAuthority;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2012 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.mail.providers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.email.activity.setup.AccountSettings;
|
||||
|
||||
public class EmailAccountCacheProvider extends MailAppProvider {
|
||||
// Content provider for Email
|
||||
private static final String sAuthority = "com.android.email2.accountcache";
|
||||
/**
|
||||
* Authority for the suggestions provider. This is specified in AndroidManifest.xml and
|
||||
* res/xml/searchable.xml.
|
||||
*/
|
||||
private static final String sSuggestionsAuthority = "com.android.email.suggestionsprovider";
|
||||
|
||||
@Override
|
||||
protected String getAuthority() {
|
||||
return sAuthority;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getNoAccountsIntent(Context context) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_EDIT);
|
||||
intent.setData(Uri.parse("content://ui.email.android.com/settings"));
|
||||
intent.putExtra(AccountSettings.EXTRA_NO_ACCOUNTS, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSuggestionAuthority() {
|
||||
return sSuggestionsAuthority;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (C) 2012 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.mail.providers.protos.boot;
|
||||
|
||||
import com.android.mail.providers.MailAppProvider;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
public class AccountReceiver extends BroadcastReceiver {
|
||||
/**
|
||||
* Intent used to notify interested parties that the Mail provider has been created.
|
||||
*/
|
||||
public static final String ACTION_PROVIDER_CREATED
|
||||
= "com.android.email2.providers.protos.boot.intent.ACTION_PROVIDER_CREATED";
|
||||
|
||||
private static final Uri ACCOUNTS_URI =
|
||||
Uri.parse("content://com.android.email.provider/uiaccts");
|
||||
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
MailAppProvider.addAccountsForUriAsync(ACCOUNTS_URI);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Copyright (c) 2012, Google Inc.
|
||||
*
|
||||
* 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.mail.utils;
|
||||
|
||||
public class LogTag {
|
||||
private static String LOG_TAG = "Email";
|
||||
|
||||
/**
|
||||
* Get the log tag to apply to logging.
|
||||
*/
|
||||
public static String getLogTag() {
|
||||
return LOG_TAG;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright 2008, 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.
|
||||
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# We only want this apk build for tests.
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_JAVA_LIBRARIES := android.test.runner
|
||||
|
||||
# Include all test java files.
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
# Notice that we don't have to include the src files of Email because, by
|
||||
# running the tests using an instrumentation targeting Email, we
|
||||
# automatically get all of its classes loaded into our environment.
|
||||
|
||||
LOCAL_PACKAGE_NAME := EmailTests
|
||||
|
||||
LOCAL_INSTRUMENTATION_FOR := Email
|
||||
|
||||
include $(BUILD_PACKAGE)
|
Loading…
Reference in New Issue