Add a bunch of stuff missed earlier
Change-Id: I7f707446a963912fe5786dacb5569e68db572d1c
This commit is contained in:
parent
f419287f22
commit
c5afb16430
30
emailsync/Android.mk
Normal file
30
emailsync/Android.mk
Normal file
@ -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)
|
305
emailsync/src/com/android/emailsync/AbstractSyncService.java
Normal file
305
emailsync/src/com/android/emailsync/AbstractSyncService.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
111
emailsync/src/com/android/emailsync/EmailSyncAlarmReceiver.java
Normal file
111
emailsync/src/com/android/emailsync/EmailSyncAlarmReceiver.java
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
emailsync/src/com/android/emailsync/FileLogger.java
Normal file
120
emailsync/src/com/android/emailsync/FileLogger.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
51
emailsync/src/com/android/emailsync/PartRequest.java
Normal file
51
emailsync/src/com/android/emailsync/PartRequest.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
36
emailsync/src/com/android/emailsync/Request.java
Normal file
36
emailsync/src/com/android/emailsync/Request.java
Normal file
@ -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();
|
||||||
|
}
|
2254
emailsync/src/com/android/emailsync/SyncServiceManager.java
Normal file
2254
emailsync/src/com/android/emailsync/SyncServiceManager.java
Normal file
File diff suppressed because it is too large
Load Diff
23
res/layout-sw600dp/account_type.xml
Normal file
23
res/layout-sw600dp/account_type.xml
Normal file
@ -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" />
|
27
res/layout/account_type.xml
Normal file
27
res/layout/account_type.xml
Normal file
@ -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"
|
||||||
|
/>
|
102
res/layout/conversation_item_view_normal.xml
Normal file
102
res/layout/conversation_item_view_normal.xml
Normal file
@ -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>
|
100
res/layout/conversation_item_view_wide.xml
Normal file
100
res/layout/conversation_item_view_wide.xml
Normal file
@ -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>
|
32
res/layout/quick_response_edit_dialog.xml
Normal file
32
res/layout/quick_response_edit_dialog.xml
Normal file
@ -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>
|
BIN
res/mipmap-hdpi/ic_launcher_mail.png
Normal file
BIN
res/mipmap-hdpi/ic_launcher_mail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
BIN
res/mipmap-mdpi/ic_launcher_mail.png
Normal file
BIN
res/mipmap-mdpi/ic_launcher_mail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
BIN
res/mipmap-xhdpi/ic_launcher_mail.png
Normal file
BIN
res/mipmap-xhdpi/ic_launcher_mail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
24
res/values-sw720dp-land/dimensions.xml
Normal file
24
res/values-sw720dp-land/dimensions.xml
Normal file
@ -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>
|
||||||
|
|
25
res/xml/searchable.xml
Normal file
25
res/xml/searchable.xml
Normal file
@ -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" />
|
104
res/xml/services.xml
Normal file
104
res/xml/services.xml
Normal file
@ -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>
|
76
src/com/android/email/activity/EventViewer.java
Normal file
76
src/com/android/email/activity/EventViewer.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
src/com/android/email/activity/setup/AccountSetupType.java
Normal file
138
src/com/android/email/activity/setup/AccountSetupType.java
Normal file
@ -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();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
30
src/com/android/email/activity/setup/ForwardingIntent.java
Normal file
30
src/com/android/email/activity/setup/ForwardingIntent.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
1292
src/com/android/email/provider/DBHelper.java
Normal file
1292
src/com/android/email/provider/DBHelper.java
Normal file
File diff suppressed because it is too large
Load Diff
150
src/com/android/email/provider/Utilities.java
Normal file
150
src/com/android/email/provider/Utilities.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
159
src/com/android/email/service/AuthenticatorService.java
Normal file
159
src/com/android/email/service/AuthenticatorService.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
561
src/com/android/email/service/EmailServiceStub.java
Normal file
561
src/com/android/email/service/EmailServiceStub.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
1396
src/com/android/email/service/ImapService.java
Normal file
1396
src/com/android/email/service/ImapService.java
Normal file
File diff suppressed because it is too large
Load Diff
127
src/com/android/email/service/ImapTempFileLiteral.java
Normal file
127
src/com/android/email/service/ImapTempFileLiteral.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
726
src/com/android/email/service/Pop3Service.java
Normal file
726
src/com/android/email/service/Pop3Service.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/com/android/email2/ui/CreateShortcutActivityEmail.java
Normal file
50
src/com/android/email2/ui/CreateShortcutActivityEmail.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
240
src/com/android/email2/ui/MailActivityEmail.java
Normal file
240
src/com/android/email2/ui/MailActivityEmail.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
23
src/com/android/email2/ui/MailboxSelectionActivityEmail.java
Normal file
23
src/com/android/email2/ui/MailboxSelectionActivityEmail.java
Normal file
@ -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 {
|
||||||
|
|
||||||
|
}
|
32
src/com/android/mail/browse/EmailConversationProvider.java
Normal file
32
src/com/android/mail/browse/EmailConversationProvider.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
28
src/com/android/mail/utils/LogTag.java
Normal file
28
src/com/android/mail/utils/LogTag.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
34
tests/oldAndroid.mk
Normal file
34
tests/oldAndroid.mk
Normal file
@ -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
Block a user