merge extra crap

This commit is contained in:
Jesse Vincent 2008-12-19 00:31:17 +00:00
parent 9cf84ee2c8
commit d55e335f48
68 changed files with 0 additions and 18414 deletions

View File

@ -1,408 +0,0 @@
package com.fsck.k9;
import java.io.Serializable;
import java.util.Arrays;
import java.util.UUID;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
/**
* Account stores all of the settings for a single account defined by the user. It is able to save
* and delete itself given a Preferences to work with. Each account is defined by a UUID.
*/
public class Account implements Serializable {
public static final int DELETE_POLICY_NEVER = 0;
public static final int DELETE_POLICY_7DAYS = 1;
public static final int DELETE_POLICY_ON_DELETE = 2;
private static final long serialVersionUID = 2975156672298625121L;
String mUuid;
String mStoreUri;
String mLocalStoreUri;
String mTransportUri;
String mDescription;
String mName;
String mEmail;
String mSignature;
String mAlwaysBcc;
int mAutomaticCheckIntervalMinutes;
int mDisplayCount;
long mLastAutomaticCheckTime;
boolean mNotifyNewMail;
boolean mNotifyRingtone;
String mDraftsFolderName;
String mSentFolderName;
String mTrashFolderName;
String mOutboxFolderName;
int mAccountNumber;
boolean mVibrate;
String mRingtoneUri;
/**
* <pre>
* 0 Never
* 1 After 7 days
* 2 When I delete from inbox
* </pre>
*/
int mDeletePolicy;
public Account(Context context) {
// TODO Change local store path to something readable / recognizable
mUuid = UUID.randomUUID().toString();
mLocalStoreUri = "local://localhost/" + context.getDatabasePath(mUuid + ".db");
mAutomaticCheckIntervalMinutes = -1;
mDisplayCount = -1;
mAccountNumber = -1;
mNotifyNewMail = true;
mNotifyRingtone = false;
mSignature = "Sent from my Android phone with K-9. Please excuse my brevity.";
mVibrate = false;
mRingtoneUri = "content://settings/system/notification_sound";
}
Account(Preferences preferences, String uuid) {
this.mUuid = uuid;
refresh(preferences);
}
/**
* Refresh the account from the stored settings.
*/
public void refresh(Preferences preferences) {
mStoreUri = Utility.base64Decode(preferences.mSharedPreferences.getString(mUuid
+ ".storeUri", null));
mLocalStoreUri = preferences.mSharedPreferences.getString(mUuid + ".localStoreUri", null);
mTransportUri = Utility.base64Decode(preferences.mSharedPreferences.getString(mUuid
+ ".transportUri", null));
mDescription = preferences.mSharedPreferences.getString(mUuid + ".description", null);
mAlwaysBcc = preferences.mSharedPreferences.getString(mUuid + ".alwaysBcc", mAlwaysBcc);
mName = preferences.mSharedPreferences.getString(mUuid + ".name", mName);
mEmail = preferences.mSharedPreferences.getString(mUuid + ".email", mEmail);
mSignature = preferences.mSharedPreferences.getString(mUuid + ".signature", mSignature);
mAutomaticCheckIntervalMinutes = preferences.mSharedPreferences.getInt(mUuid
+ ".automaticCheckIntervalMinutes", -1);
mDisplayCount = preferences.mSharedPreferences.getInt(mUuid + ".displayCount", -1);
mLastAutomaticCheckTime = preferences.mSharedPreferences.getLong(mUuid
+ ".lastAutomaticCheckTime", 0);
mNotifyNewMail = preferences.mSharedPreferences.getBoolean(mUuid + ".notifyNewMail",
false);
mNotifyRingtone = preferences.mSharedPreferences.getBoolean(mUuid + ".notifyRingtone",
false);
mDeletePolicy = preferences.mSharedPreferences.getInt(mUuid + ".deletePolicy", 0);
mDraftsFolderName = preferences.mSharedPreferences.getString(mUuid + ".draftsFolderName",
"Drafts");
mSentFolderName = preferences.mSharedPreferences.getString(mUuid + ".sentFolderName",
"Sent");
mTrashFolderName = preferences.mSharedPreferences.getString(mUuid + ".trashFolderName",
"Trash");
mOutboxFolderName = preferences.mSharedPreferences.getString(mUuid + ".outboxFolderName",
"Outbox");
mAccountNumber = preferences.mSharedPreferences.getInt(mUuid + ".accountNumber", 0);
mVibrate = preferences.mSharedPreferences.getBoolean(mUuid + ".vibrate", false);
mRingtoneUri = preferences.mSharedPreferences.getString(mUuid + ".ringtone",
"content://settings/system/notification_sound");
}
public String getUuid() {
return mUuid;
}
public String getStoreUri() {
return mStoreUri;
}
public void setStoreUri(String storeUri) {
this.mStoreUri = storeUri;
}
public String getTransportUri() {
return mTransportUri;
}
public void setTransportUri(String transportUri) {
this.mTransportUri = transportUri;
}
public String getDescription() {
return mDescription;
}
public void setDescription(String description) {
this.mDescription = description;
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
public String getSignature() {
return mSignature;
}
public void setSignature(String signature) {
this.mSignature = signature;
}
public String getEmail() {
return mEmail;
}
public void setEmail(String email) {
this.mEmail = email;
}
public String getAlwaysBcc() {
return mAlwaysBcc;
}
public void setAlwaysBcc(String alwaysBcc) {
this.mAlwaysBcc = alwaysBcc;
}
public boolean isVibrate() {
return mVibrate;
}
public void setVibrate(boolean vibrate) {
mVibrate = vibrate;
}
public String getRingtone() {
return mRingtoneUri;
}
public void setRingtone(String ringtoneUri) {
mRingtoneUri = ringtoneUri;
}
public void delete(Preferences preferences) {
String[] uuids = preferences.mSharedPreferences.getString("accountUuids", "").split(",");
StringBuffer sb = new StringBuffer();
for (int i = 0, length = uuids.length; i < length; i++) {
if (!uuids[i].equals(mUuid)) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(uuids[i]);
}
}
String accountUuids = sb.toString();
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
editor.putString("accountUuids", accountUuids);
editor.remove(mUuid + ".storeUri");
editor.remove(mUuid + ".localStoreUri");
editor.remove(mUuid + ".transportUri");
editor.remove(mUuid + ".description");
editor.remove(mUuid + ".name");
editor.remove(mUuid + ".email");
editor.remove(mUuid + ".alwaysBcc");
editor.remove(mUuid + ".automaticCheckIntervalMinutes");
editor.remove(mUuid + ".lastAutomaticCheckTime");
editor.remove(mUuid + ".notifyNewMail");
editor.remove(mUuid + ".deletePolicy");
editor.remove(mUuid + ".draftsFolderName");
editor.remove(mUuid + ".sentFolderName");
editor.remove(mUuid + ".trashFolderName");
editor.remove(mUuid + ".outboxFolderName");
editor.remove(mUuid + ".accountNumber");
editor.remove(mUuid + ".vibrate");
editor.remove(mUuid + ".ringtone");
editor.commit();
}
public void save(Preferences preferences) {
if (!preferences.mSharedPreferences.getString("accountUuids", "").contains(mUuid)) {
/*
* When the account is first created we assign it a unique account number. The
* account number will be unique to that account for the lifetime of the account.
* So, we get all the existing account numbers, sort them ascending, loop through
* the list and check if the number is greater than 1 + the previous number. If so
* we use the previous number + 1 as the account number. This refills gaps.
* mAccountNumber starts as -1 on a newly created account. It must be -1 for this
* algorithm to work.
*
* I bet there is a much smarter way to do this. Anyone like to suggest it?
*/
Account[] accounts = preferences.getAccounts();
int[] accountNumbers = new int[accounts.length];
for (int i = 0; i < accounts.length; i++) {
accountNumbers[i] = accounts[i].getAccountNumber();
}
Arrays.sort(accountNumbers);
for (int accountNumber : accountNumbers) {
if (accountNumber > mAccountNumber + 1) {
break;
}
mAccountNumber = accountNumber;
}
mAccountNumber++;
String accountUuids = preferences.mSharedPreferences.getString("accountUuids", "");
accountUuids += (accountUuids.length() != 0 ? "," : "") + mUuid;
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
editor.putString("accountUuids", accountUuids);
editor.commit();
}
SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
editor.putString(mUuid + ".storeUri", Utility.base64Encode(mStoreUri));
editor.putString(mUuid + ".localStoreUri", mLocalStoreUri);
editor.putString(mUuid + ".transportUri", Utility.base64Encode(mTransportUri));
editor.putString(mUuid + ".description", mDescription);
editor.putString(mUuid + ".name", mName);
editor.putString(mUuid + ".email", mEmail);
editor.putString(mUuid + ".signature", mSignature);
editor.putString(mUuid + ".alwaysBcc", mAlwaysBcc);
editor.putInt(mUuid + ".automaticCheckIntervalMinutes", mAutomaticCheckIntervalMinutes);
editor.putInt(mUuid + ".displayCount", mDisplayCount);
editor.putLong(mUuid + ".lastAutomaticCheckTime", mLastAutomaticCheckTime);
editor.putBoolean(mUuid + ".notifyNewMail", mNotifyNewMail);
editor.putBoolean(mUuid + ".notifyRingtone", mNotifyRingtone);
editor.putInt(mUuid + ".deletePolicy", mDeletePolicy);
editor.putString(mUuid + ".draftsFolderName", mDraftsFolderName);
editor.putString(mUuid + ".sentFolderName", mSentFolderName);
editor.putString(mUuid + ".trashFolderName", mTrashFolderName);
editor.putString(mUuid + ".outboxFolderName", mOutboxFolderName);
editor.putInt(mUuid + ".accountNumber", mAccountNumber);
editor.putBoolean(mUuid + ".vibrate", mVibrate);
editor.putString(mUuid + ".ringtone", mRingtoneUri);
editor.commit();
}
public String toString() {
return mDescription;
}
public Uri getContentUri() {
return Uri.parse("content://accounts/" + getUuid());
}
public String getLocalStoreUri() {
return mLocalStoreUri;
}
public void setLocalStoreUri(String localStoreUri) {
this.mLocalStoreUri = localStoreUri;
}
/**
* Returns -1 for never.
*/
public int getAutomaticCheckIntervalMinutes() {
return mAutomaticCheckIntervalMinutes;
}
public int getDisplayCount() {
if (mDisplayCount == -1) {
this.mDisplayCount = k9.DEFAULT_VISIBLE_LIMIT;
}
return mDisplayCount;
}
/**
* @param automaticCheckIntervalMinutes or -1 for never.
*/
public void setAutomaticCheckIntervalMinutes(int automaticCheckIntervalMinutes) {
this.mAutomaticCheckIntervalMinutes = automaticCheckIntervalMinutes;
}
/**
* @param displayCount
*/
public void setDisplayCount(int displayCount) {
if (displayCount != -1) {
this.mDisplayCount = displayCount;
} else {
this.mDisplayCount = k9.DEFAULT_VISIBLE_LIMIT;
}
}
public long getLastAutomaticCheckTime() {
return mLastAutomaticCheckTime;
}
public void setLastAutomaticCheckTime(long lastAutomaticCheckTime) {
this.mLastAutomaticCheckTime = lastAutomaticCheckTime;
}
public boolean isNotifyRingtone() {
return mNotifyRingtone;
}
public void setNotifyRingtone(boolean notifyRingtone) {
this.mNotifyRingtone = notifyRingtone;
}
public boolean isNotifyNewMail() {
return mNotifyNewMail;
}
public void setNotifyNewMail(boolean notifyNewMail) {
this.mNotifyNewMail = notifyNewMail;
}
public int getDeletePolicy() {
return mDeletePolicy;
}
public void setDeletePolicy(int deletePolicy) {
this.mDeletePolicy = deletePolicy;
}
public String getDraftsFolderName() {
return mDraftsFolderName;
}
public void setDraftsFolderName(String draftsFolderName) {
mDraftsFolderName = draftsFolderName;
}
public String getSentFolderName() {
return mSentFolderName;
}
public void setSentFolderName(String sentFolderName) {
mSentFolderName = sentFolderName;
}
public String getTrashFolderName() {
return mTrashFolderName;
}
public void setTrashFolderName(String trashFolderName) {
mTrashFolderName = trashFolderName;
}
public String getOutboxFolderName() {
return mOutboxFolderName;
}
public void setOutboxFolderName(String outboxFolderName) {
mOutboxFolderName = outboxFolderName;
}
public int getAccountNumber() {
return mAccountNumber;
}
@Override
public boolean equals(Object o) {
if (o instanceof Account) {
return ((Account)o).mUuid.equals(mUuid);
}
return super.equals(o);
}
}

View File

@ -1,88 +0,0 @@
/*
* Copyright (C) 2007 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.fsck.k9;
import static android.provider.Contacts.ContactMethods.CONTENT_EMAIL_URI;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.People;
import android.view.View;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
import com.fsck.k9.mail.Address;
public class EmailAddressAdapter extends ResourceCursorAdapter {
public static final int NAME_INDEX = 1;
public static final int DATA_INDEX = 2;
private static final String SORT_ORDER = People.TIMES_CONTACTED + " DESC, " + People.NAME;
private ContentResolver mContentResolver;
private static final String[] PROJECTION = {
ContactMethods._ID, // 0
ContactMethods.NAME, // 1
ContactMethods.DATA
// 2
};
public EmailAddressAdapter(Context context) {
super(context, R.layout.recipient_dropdown_item, null);
mContentResolver = context.getContentResolver();
}
@Override
public final String convertToString(Cursor cursor) {
String name = cursor.getString(NAME_INDEX);
String address = cursor.getString(DATA_INDEX);
return new Address(address, name).toString();
}
@Override
public final void bindView(View view, Context context, Cursor cursor) {
TextView text1 = (TextView)view.findViewById(R.id.text1);
TextView text2 = (TextView)view.findViewById(R.id.text2);
text1.setText(cursor.getString(NAME_INDEX));
text2.setText(cursor.getString(DATA_INDEX));
}
@Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
String where = null;
if (constraint != null) {
String filter = DatabaseUtils.sqlEscapeString(constraint.toString() + '%');
StringBuilder s = new StringBuilder();
s.append("(people.name LIKE ");
s.append(filter);
s.append(") OR (contact_methods.data LIKE ");
s.append(filter);
s.append(")");
where = s.toString();
}
return mContentResolver.query(CONTENT_EMAIL_URI, PROJECTION, where, null, SORT_ORDER);
}
}

View File

@ -1,18 +0,0 @@
package com.fsck.k9;
import com.fsck.k9.mail.Address;
import android.util.Config;
import android.util.Log;
import android.widget.AutoCompleteTextView.Validator;
public class EmailAddressValidator implements Validator {
public CharSequence fixText(CharSequence invalidText) {
return "";
}
public boolean isValid(CharSequence text) {
return Address.parse(text.toString()).length > 0;
}
}

View File

@ -1,60 +0,0 @@
package com.fsck.k9;
import java.io.IOException;
import java.io.InputStream;
/**
* A filtering InputStream that stops allowing reads after the given length has been read. This
* is used to allow a client to read directly from an underlying protocol stream without reading
* past where the protocol handler intended the client to read.
*/
public class FixedLengthInputStream extends InputStream {
private InputStream mIn;
private int mLength;
private int mCount;
public FixedLengthInputStream(InputStream in, int length) {
this.mIn = in;
this.mLength = length;
}
@Override
public int available() throws IOException {
return mLength - mCount;
}
@Override
public int read() throws IOException {
if (mCount < mLength) {
mCount++;
return mIn.read();
} else {
return -1;
}
}
@Override
public int read(byte[] b, int offset, int length) throws IOException {
if (mCount < mLength) {
int d = mIn.read(b, offset, Math.min(mLength - mCount, length));
if (d == -1) {
return -1;
} else {
mCount += d;
return d;
}
} else {
return -1;
}
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public String toString() {
return String.format("FixedLengthInputStream(in=%s, length=%d)", mIn.toString(), mLength);
}
}

View File

@ -1,14 +0,0 @@
/* AUTO-GENERATED FILE. DO NOT MODIFY.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package com.fsck.k9;
public final class Manifest {
public static final class permission {
public static final String READ_ATTACHMENT="com.fsck.k9.permission.READ_ATTACHMENT";
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,132 +0,0 @@
package com.fsck.k9;
import android.content.Context;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
/**
* Defines the interface that MessagingController will use to callback to requesters. This class
* is defined as non-abstract so that someone who wants to receive only a few messages can
* do so without implementing the entire interface. It is highly recommended that users of
* this interface use the @Override annotation in their implementations to avoid being caught by
* changes in this class.
*/
public class MessagingListener {
public void listFoldersStarted(Account account) {
}
public void listFolders(Account account, Folder[] folders) {
}
public void listFoldersFailed(Account account, String message) {
}
public void listFoldersFinished(Account account) {
}
public void listLocalMessagesStarted(Account account, String folder) {
}
public void listLocalMessages(Account account, String folder, Message[] messages) {
}
public void listLocalMessagesFailed(Account account, String folder, String message) {
}
public void listLocalMessagesFinished(Account account, String folder) {
}
public void synchronizeMailboxStarted(Account account, String folder) {
}
public void synchronizeMailboxNewMessage(Account account, String folder, Message message) {
}
public void synchronizeMailboxRemovedMessage(Account account, String folder,Message message) {
}
public void synchronizeMailboxFinished(Account account, String folder,
int totalMessagesInMailbox, int numNewMessages) {
}
public void synchronizeMailboxFailed(Account account, String folder,
String message) {
}
public void loadMessageForViewStarted(Account account, String folder, String uid) {
}
public void loadMessageForViewHeadersAvailable(Account account, String folder, String uid,
Message message) {
}
public void loadMessageForViewBodyAvailable(Account account, String folder, String uid,
Message message) {
}
public void loadMessageForViewFinished(Account account, String folder, String uid,
Message message) {
}
public void loadMessageForViewFailed(Account account, String folder, String uid, String message) {
}
public void checkMailStarted(Context context, Account account) {
}
public void checkMailFinished(Context context, Account account) {
}
public void checkMailFailed(Context context, Account account, String reason) {
}
public void sendPendingMessagesCompleted(Account account) {
}
public void emptyTrashCompleted(Account account) {
}
public void messageUidChanged(Account account, String folder, String oldUid, String newUid) {
}
public void loadAttachmentStarted(
Account account,
Message message,
Part part,
Object tag,
boolean requiresDownload)
{
}
public void loadAttachmentFinished(
Account account,
Message message,
Part part,
Object tag)
{
}
public void loadAttachmentFailed(
Account account,
Message message,
Part part,
Object tag,
String reason)
{
}
/**
* General notification messages subclasses can override to be notified that the controller
* has completed a command. This is useful for turning off progress indicators that may have
* been left over from previous commands.
* @param moreCommandsToRun True if the controller will continue on to another command
* immediately.
*/
public void controllerCommandCompleted(boolean moreCommandsToRun) {
}
}

View File

@ -1,64 +0,0 @@
package com.fsck.k9;
import java.io.IOException;
import java.io.InputStream;
/**
* A filtering InputStream that allows single byte "peeks" without consuming the byte. The
* client of this stream can call peek() to see the next available byte in the stream
* and a subsequent read will still return the peeked byte.
*/
public class PeekableInputStream extends InputStream {
private InputStream mIn;
private boolean mPeeked;
private int mPeekedByte;
public PeekableInputStream(InputStream in) {
this.mIn = in;
}
@Override
public int read() throws IOException {
if (!mPeeked) {
return mIn.read();
} else {
mPeeked = false;
return mPeekedByte;
}
}
public int peek() throws IOException {
if (!mPeeked) {
mPeekedByte = read();
mPeeked = true;
}
return mPeekedByte;
}
@Override
public int read(byte[] b, int offset, int length) throws IOException {
if (!mPeeked) {
return mIn.read(b, offset, length);
} else {
b[0] = (byte)mPeekedByte;
mPeeked = false;
int r = mIn.read(b, offset + 1, length - 1);
if (r == -1) {
return 1;
} else {
return r + 1;
}
}
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public String toString() {
return String.format("PeekableInputStream(in=%s, peeked=%b, peekedByte=%d)",
mIn.toString(), mPeeked, mPeekedByte);
}
}

View File

@ -1,123 +0,0 @@
package com.fsck.k9;
import java.util.Arrays;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Config;
import android.util.Log;
public class Preferences {
private static Preferences preferences;
SharedPreferences mSharedPreferences;
private Preferences(Context context) {
mSharedPreferences = context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE);
}
/**
* TODO need to think about what happens if this gets GCed along with the
* Activity that initialized it. Do we lose ability to read Preferences in
* further Activities? Maybe this should be stored in the Application
* context.
*
* @return
*/
public static synchronized Preferences getPreferences(Context context) {
if (preferences == null) {
preferences = new Preferences(context);
}
return preferences;
}
/**
* Returns an array of the accounts on the system. If no accounts are
* registered the method returns an empty array.
*
* @return
*/
public Account[] getAccounts() {
String accountUuids = mSharedPreferences.getString("accountUuids", null);
if (accountUuids == null || accountUuids.length() == 0) {
return new Account[] {};
}
String[] uuids = accountUuids.split(",");
Account[] accounts = new Account[uuids.length];
for (int i = 0, length = uuids.length; i < length; i++) {
accounts[i] = new Account(this, uuids[i]);
}
return accounts;
}
public Account getAccountByContentUri(Uri uri) {
return new Account(this, uri.getPath().substring(1));
}
/**
* Returns the Account marked as default. If no account is marked as default
* the first account in the list is marked as default and then returned. If
* there are no accounts on the system the method returns null.
*
* @return
*/
public Account getDefaultAccount() {
String defaultAccountUuid = mSharedPreferences.getString("defaultAccountUuid", null);
Account defaultAccount = null;
Account[] accounts = getAccounts();
if (defaultAccountUuid != null) {
for (Account account : accounts) {
if (account.getUuid().equals(defaultAccountUuid)) {
defaultAccount = account;
break;
}
}
}
if (defaultAccount == null) {
if (accounts.length > 0) {
defaultAccount = accounts[0];
setDefaultAccount(defaultAccount);
}
}
return defaultAccount;
}
public void setDefaultAccount(Account account) {
mSharedPreferences.edit().putString("defaultAccountUuid", account.getUuid()).commit();
}
public void setEnableDebugLogging(boolean value) {
mSharedPreferences.edit().putBoolean("enableDebugLogging", value).commit();
}
public boolean geteEnableDebugLogging() {
return mSharedPreferences.getBoolean("enableDebugLogging", false);
}
public void setEnableSensitiveLogging(boolean value) {
mSharedPreferences.edit().putBoolean("enableSensitiveLogging", value).commit();
}
public boolean getEnableSensitiveLogging() {
return mSharedPreferences.getBoolean("enableSensitiveLogging", false);
}
public void save() {
}
public void clear() {
mSharedPreferences.edit().clear().commit();
}
public void dump() {
if (Config.LOGV) {
for (String key : mSharedPreferences.getAll().keySet()) {
Log.v(k9.LOG_TAG, key + " = " + mSharedPreferences.getAll().get(key));
}
}
}
}

View File

@ -1,176 +0,0 @@
package com.fsck.k9;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import com.fsck.k9.codec.binary.Base64;
import android.text.Editable;
import android.widget.TextView;
public class Utility {
public final static String readInputStream(InputStream in, String encoding) throws IOException {
InputStreamReader reader = new InputStreamReader(in, encoding);
StringBuffer sb = new StringBuffer();
int count;
char[] buf = new char[512];
while ((count = reader.read(buf)) != -1) {
sb.append(buf, 0, count);
}
return sb.toString();
}
public final static boolean arrayContains(Object[] a, Object o) {
for (int i = 0, count = a.length; i < count; i++) {
if (a[i].equals(o)) {
return true;
}
}
return false;
}
/**
* Combines the given array of Objects into a single string using the
* seperator character and each Object's toString() method. between each
* part.
*
* @param parts
* @param seperator
* @return
*/
public static String combine(Object[] parts, char seperator) {
if (parts == null) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < parts.length; i++) {
sb.append(parts[i].toString());
if (i < parts.length - 1) {
sb.append(seperator);
}
}
return sb.toString();
}
public static String base64Decode(String encoded) {
if (encoded == null) {
return null;
}
byte[] decoded = new Base64().decode(encoded.getBytes());
return new String(decoded);
}
public static String base64Encode(String s) {
if (s == null) {
return s;
}
byte[] encoded = new Base64().encode(s.getBytes());
return new String(encoded);
}
public static boolean requiredFieldValid(TextView view) {
return view.getText() != null && view.getText().length() > 0;
}
public static boolean requiredFieldValid(Editable s) {
return s != null && s.length() > 0;
}
/**
* Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the
* double quote character to start and end if it's not already there.
* sample -> "sample"
* "sample" -> "sample"
* ""sample"" -> "sample"
* "sample"" -> "sample"
* sa"mp"le -> "sa"mp"le"
* "sa"mp"le" -> "sa"mp"le"
* (empty string) -> ""
* " -> ""
* @param s
* @return
*/
public static String quoteString(String s) {
if (s == null) {
return null;
}
if (!s.matches("^\".*\"$")) {
return "\"" + s + "\"";
}
else {
return s;
}
}
/**
* A fast version of URLDecoder.decode() that works only with UTF-8 and does only two
* allocations. This version is around 3x as fast as the standard one and I'm using it
* hundreds of times in places that slow down the UI, so it helps.
*/
public static String fastUrlDecode(String s) {
try {
byte[] bytes = s.getBytes("UTF-8");
byte ch;
int length = 0;
for (int i = 0, count = bytes.length; i < count; i++) {
ch = bytes[i];
if (ch == '%') {
int h = (bytes[i + 1] - '0');
int l = (bytes[i + 2] - '0');
if (h > 9) {
h -= 7;
}
if (l > 9) {
l -= 7;
}
bytes[length] = (byte) ((h << 4) | l);
i += 2;
}
else if (ch == '+') {
bytes[length] = ' ';
}
else {
bytes[length] = bytes[i];
}
length++;
}
return new String(bytes, 0, length, "UTF-8");
}
catch (UnsupportedEncodingException uee) {
return null;
}
}
/**
* Returns true if the specified date is within today. Returns false otherwise.
* @param date
* @return
*/
public static boolean isDateToday(Date date) {
// TODO But Calendar is so slowwwwwww....
Date today = new Date();
if (date.getYear() == today.getYear() &&
date.getMonth() == today.getMonth() &&
date.getDate() == today.getDate()) {
return true;
}
return false;
}
/*
* TODO disabled this method globally. It is used in all the settings screens but I just
* noticed that an unrelated icon was dimmed. Android must share drawables internally.
*/
public static void setCompoundDrawablesAlpha(TextView view, int alpha) {
// Drawable[] drawables = view.getCompoundDrawables();
// for (Drawable drawable : drawables) {
// if (drawable != null) {
// drawable.setAlpha(alpha);
// }
// }
}
}

View File

@ -1,296 +0,0 @@
package com.fsck.k9.activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.MessagingController;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.setup.AccountSettings;
import com.fsck.k9.activity.setup.AccountSetupBasics;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
public class Accounts extends ListActivity implements OnItemClickListener, OnClickListener {
private static final int DIALOG_REMOVE_ACCOUNT = 1;
/**
* Key codes used to open a debug settings screen.
*/
private static int[] secretKeyCodes = {
KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_U,
KeyEvent.KEYCODE_G
};
private int mSecretKeyCodeIndex = 0;
private Account mSelectedContextAccount;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.accounts);
ListView listView = getListView();
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(false);
listView.setEmptyView(findViewById(R.id.empty));
findViewById(R.id.add_new_account).setOnClickListener(this);
registerForContextMenu(listView);
if (icicle != null && icicle.containsKey("selectedContextAccount")) {
mSelectedContextAccount = (Account) icicle.getSerializable("selectedContextAccount");
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mSelectedContextAccount != null) {
outState.putSerializable("selectedContextAccount", mSelectedContextAccount);
}
}
@Override
public void onResume() {
super.onResume();
NotificationManager notifMgr = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
notifMgr.cancel(1);
refresh();
}
private void refresh() {
Account[] accounts = Preferences.getPreferences(this).getAccounts();
getListView().setAdapter(new AccountsAdapter(accounts));
}
private void onAddNewAccount() {
AccountSetupBasics.actionNewAccount(this);
}
private void onEditAccount(Account account) {
AccountSettings.actionSettings(this, account);
}
private void onRefresh() {
MessagingController.getInstance(getApplication()).checkMail(this, null, null);
}
private void onCompose() {
Account defaultAccount =
Preferences.getPreferences(this).getDefaultAccount();
if (defaultAccount != null) {
MessageCompose.actionCompose(this, defaultAccount);
}
else {
onAddNewAccount();
}
}
private void onOpenAccount(Account account) {
FolderMessageList.actionHandleAccount(this, account);
}
public void onClick(View view) {
if (view.getId() == R.id.add_new_account) {
onAddNewAccount();
}
}
private void onDeleteAccount(Account account) {
mSelectedContextAccount = account;
showDialog(DIALOG_REMOVE_ACCOUNT);
}
@Override
public Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_REMOVE_ACCOUNT:
return createRemoveAccountDialog();
}
return super.onCreateDialog(id);
}
private Dialog createRemoveAccountDialog() {
return new AlertDialog.Builder(this)
.setTitle(R.string.account_delete_dlg_title)
.setMessage(getString(R.string.account_delete_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()))
.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_REMOVE_ACCOUNT);
try {
((LocalStore)Store.getInstance(
mSelectedContextAccount.getLocalStoreUri(),
getApplication())).delete();
} catch (Exception e) {
// Ignore
}
mSelectedContextAccount.delete(Preferences.getPreferences(Accounts.this));
k9.setServicesEnabled(Accounts.this);
refresh();
}
})
.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_REMOVE_ACCOUNT);
}
})
.create();
}
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo)item.getMenuInfo();
Account account = (Account)getListView().getItemAtPosition(menuInfo.position);
switch (item.getItemId()) {
case R.id.delete_account:
onDeleteAccount(account);
break;
case R.id.edit_account:
onEditAccount(account);
break;
case R.id.open:
onOpenAccount(account);
break;
}
return true;
}
public void onItemClick(AdapterView parent, View view, int position, long id) {
Account account = (Account)parent.getItemAtPosition(position);
onOpenAccount(account);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_new_account:
onAddNewAccount();
break;
case R.id.check_mail:
onRefresh();
break;
case R.id.compose:
onCompose();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.accounts_option, menu);
return true;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.setHeaderTitle(R.string.accounts_context_menu_title);
getMenuInflater().inflate(R.menu.accounts_context, menu);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getKeyCode() == secretKeyCodes[mSecretKeyCodeIndex]) {
mSecretKeyCodeIndex++;
if (mSecretKeyCodeIndex == secretKeyCodes.length) {
mSecretKeyCodeIndex = 0;
startActivity(new Intent(this, Debug.class));
}
} else {
mSecretKeyCodeIndex = 0;
}
return super.onKeyDown(keyCode, event);
}
class AccountsAdapter extends ArrayAdapter<Account> {
public AccountsAdapter(Account[] accounts) {
super(Accounts.this, 0, accounts);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Account account = getItem(position);
View view;
if (convertView != null) {
view = convertView;
}
else {
view = getLayoutInflater().inflate(R.layout.accounts_item, parent, false);
}
AccountViewHolder holder = (AccountViewHolder) view.getTag();
if (holder == null) {
holder = new AccountViewHolder();
holder.description = (TextView) view.findViewById(R.id.description);
holder.email = (TextView) view.findViewById(R.id.email);
holder.newMessageCount = (TextView) view.findViewById(R.id.new_message_count);
view.setTag(holder);
}
holder.description.setText(account.getDescription());
holder.email.setText(account.getEmail());
if (account.getEmail().equals(account.getDescription())) {
holder.email.setVisibility(View.GONE);
}
int unreadMessageCount = 0;
try {
LocalStore localStore = (LocalStore) Store.getInstance(
account.getLocalStoreUri(),
getApplication());
LocalFolder localFolder = (LocalFolder) localStore.getFolder(k9.INBOX);
if (localFolder.exists()) {
unreadMessageCount = localFolder.getUnreadMessageCount();
}
}
catch (MessagingException me) {
/*
* This is not expected to fail under normal circumstances.
*/
throw new RuntimeException("Unable to get unread count from local store.", me);
}
holder.newMessageCount.setText(Integer.toString(unreadMessageCount));
holder.newMessageCount.setVisibility(unreadMessageCount > 0 ? View.VISIBLE : View.GONE);
return view;
}
class AccountViewHolder {
public TextView description;
public TextView email;
public TextView newMessageCount;
}
}
}

View File

@ -1,73 +0,0 @@
package com.fsck.k9.activity;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.fsck.k9.k9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
public class Debug extends Activity implements OnCheckedChangeListener {
private TextView mVersionView;
private CheckBox mEnableDebugLoggingView;
private CheckBox mEnableSensitiveLoggingView;
private Preferences mPreferences;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.debug);
mPreferences = Preferences.getPreferences(this);
mVersionView = (TextView)findViewById(R.id.version);
mEnableDebugLoggingView = (CheckBox)findViewById(R.id.debug_logging);
mEnableSensitiveLoggingView = (CheckBox)findViewById(R.id.sensitive_logging);
mEnableDebugLoggingView.setOnCheckedChangeListener(this);
mEnableSensitiveLoggingView.setOnCheckedChangeListener(this);
mVersionView.setText(String.format(getString(R.string.debug_version_fmt).toString(),
getString(R.string.build_number)));
mEnableDebugLoggingView.setChecked(k9.DEBUG);
mEnableSensitiveLoggingView.setChecked(k9.DEBUG_SENSITIVE);
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView.getId() == R.id.debug_logging) {
k9.DEBUG = isChecked;
mPreferences.setEnableDebugLogging(k9.DEBUG);
} else if (buttonView.getId() == R.id.sensitive_logging) {
k9.DEBUG_SENSITIVE = isChecked;
mPreferences.setEnableSensitiveLogging(k9.DEBUG_SENSITIVE);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.dump_settings) {
Preferences.getPreferences(this).dump();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.debug_option, menu);
return true;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,916 +0,0 @@
package com.fsck.k9.activity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Map;
import java.util.regex.Matcher;
import org.apache.commons.io.IOUtils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Process;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.util.Regex;
import android.text.util.Linkify;
import android.util.Config;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.webkit.CacheManager;
import android.webkit.UrlInterceptHandler;
import android.webkit.WebView;
import android.webkit.CacheManager.CacheResult;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.MessagingController;
import com.fsck.k9.MessagingListener;
import com.fsck.k9.R;
import com.fsck.k9.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.provider.AttachmentProvider;
public class MessageView extends Activity
implements UrlInterceptHandler, OnClickListener {
private static final String EXTRA_ACCOUNT = "com.fsck.k9.MessageView_account";
private static final String EXTRA_FOLDER = "com.fsck.k9.MessageView_folder";
private static final String EXTRA_MESSAGE = "com.fsck.k9.MessageView_message";
private static final String EXTRA_FOLDER_UIDS = "com.fsck.k9.MessageView_folderUids";
private static final String EXTRA_NEXT = "com.fsck.k9.MessageView_next";
private TextView mFromView;
private TextView mDateView;
private TextView mToView;
private TextView mSubjectView;
private WebView mMessageContentView;
private LinearLayout mAttachments;
private View mAttachmentIcon;
private View mShowPicturesSection;
private Account mAccount;
private String mFolder;
private String mMessageUid;
private ArrayList<String> mFolderUids;
private Message mMessage;
private String mNextMessageUid = null;
private String mPreviousMessageUid = null;
private DateFormat mDateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
private DateFormat mTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
private Listener mListener = new Listener();
private MessageViewHandler mHandler = new MessageViewHandler();
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DEL: { onDelete(); return true;}
case KeyEvent.KEYCODE_D: { onDelete(); return true;}
case KeyEvent.KEYCODE_F: { onForward(); return true;}
case KeyEvent.KEYCODE_A: { onReplyAll(); return true; }
case KeyEvent.KEYCODE_R: { onReply(); return true; }
case KeyEvent.KEYCODE_J: { onPrevious(); return true; }
case KeyEvent.KEYCODE_K: { onNext(); return true; }
case KeyEvent.KEYCODE_Z: { if (event.isShiftPressed()) {
mMessageContentView.zoomIn();
} else {
mMessageContentView.zoomOut();
}
return true; }
}
return super.onKeyDown(keyCode, event);
}
class MessageViewHandler extends Handler {
private static final int MSG_PROGRESS = 2;
private static final int MSG_ADD_ATTACHMENT = 3;
private static final int MSG_SET_ATTACHMENTS_ENABLED = 4;
private static final int MSG_SET_HEADERS = 5;
private static final int MSG_NETWORK_ERROR = 6;
private static final int MSG_ATTACHMENT_SAVED = 7;
private static final int MSG_ATTACHMENT_NOT_SAVED = 8;
private static final int MSG_SHOW_SHOW_PICTURES = 9;
private static final int MSG_FETCHING_ATTACHMENT = 10;
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_PROGRESS:
setProgressBarIndeterminateVisibility(msg.arg1 != 0);
break;
case MSG_ADD_ATTACHMENT:
mAttachments.addView((View) msg.obj);
mAttachments.setVisibility(View.VISIBLE);
break;
case MSG_SET_ATTACHMENTS_ENABLED:
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
Attachment attachment = (Attachment) mAttachments.getChildAt(i).getTag();
attachment.viewButton.setEnabled(msg.arg1 == 1);
attachment.downloadButton.setEnabled(msg.arg1 == 1);
}
break;
case MSG_SET_HEADERS:
String[] values = (String[]) msg.obj;
setTitle(values[0]);
mSubjectView.setText(values[0]);
mFromView.setText(values[1]);
mDateView.setText(values[2]);
mToView.setText(values[3]);
mAttachmentIcon.setVisibility(msg.arg1 == 1 ? View.VISIBLE : View.GONE);
break;
case MSG_NETWORK_ERROR:
Toast.makeText(MessageView.this,
R.string.status_network_error, Toast.LENGTH_LONG).show();
break;
case MSG_ATTACHMENT_SAVED:
Toast.makeText(MessageView.this, String.format(
getString(R.string.message_view_status_attachment_saved), msg.obj),
Toast.LENGTH_LONG).show();
break;
case MSG_ATTACHMENT_NOT_SAVED:
Toast.makeText(MessageView.this,
getString(R.string.message_view_status_attachment_not_saved),
Toast.LENGTH_LONG).show();
break;
case MSG_SHOW_SHOW_PICTURES:
mShowPicturesSection.setVisibility(msg.arg1 == 1 ? View.VISIBLE : View.GONE);
break;
case MSG_FETCHING_ATTACHMENT:
Toast.makeText(MessageView.this,
getString(R.string.message_view_fetching_attachment_toast),
Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
public void progress(boolean progress) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_PROGRESS;
msg.arg1 = progress ? 1 : 0;
sendMessage(msg);
}
public void addAttachment(View attachmentView) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_ADD_ATTACHMENT;
msg.obj = attachmentView;
sendMessage(msg);
}
public void setAttachmentsEnabled(boolean enabled) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_SET_ATTACHMENTS_ENABLED;
msg.arg1 = enabled ? 1 : 0;
sendMessage(msg);
}
public void setHeaders(
String subject,
String from,
String date,
String to,
boolean hasAttachments) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_SET_HEADERS;
msg.arg1 = hasAttachments ? 1 : 0;
msg.obj = new String[] { subject, from, date, to };
sendMessage(msg);
}
public void networkError() {
sendEmptyMessage(MSG_NETWORK_ERROR);
}
public void attachmentSaved(String filename) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_ATTACHMENT_SAVED;
msg.obj = filename;
sendMessage(msg);
}
public void attachmentNotSaved() {
sendEmptyMessage(MSG_ATTACHMENT_NOT_SAVED);
}
public void fetchingAttachment() {
sendEmptyMessage(MSG_FETCHING_ATTACHMENT);
}
public void showShowPictures(boolean show) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_SHOW_SHOW_PICTURES;
msg.arg1 = show ? 1 : 0;
sendMessage(msg);
}
}
class Attachment {
public String name;
public String contentType;
public long size;
public LocalAttachmentBodyPart part;
public Button viewButton;
public Button downloadButton;
public ImageView iconView;
}
public static void actionView(Context context, Account account,
String folder, String messageUid, ArrayList<String> folderUids) {
actionView(context, account, folder, messageUid, folderUids, null);
}
public static void actionView(Context context, Account account,
String folder, String messageUid, ArrayList<String> folderUids, Bundle extras) {
Intent i = new Intent(context, MessageView.class);
i.putExtra(EXTRA_ACCOUNT, account);
i.putExtra(EXTRA_FOLDER, folder);
i.putExtra(EXTRA_MESSAGE, messageUid);
i.putExtra(EXTRA_FOLDER_UIDS, folderUids);
if (extras != null) {
i.putExtras(extras);
}
context.startActivity(i);
}
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.message_view);
mFromView = (TextView)findViewById(R.id.from);
mToView = (TextView)findViewById(R.id.to);
mSubjectView = (TextView)findViewById(R.id.subject);
mDateView = (TextView)findViewById(R.id.date);
mMessageContentView = (WebView)findViewById(R.id.message_content);
mAttachments = (LinearLayout)findViewById(R.id.attachments);
mAttachmentIcon = findViewById(R.id.attachment);
mShowPicturesSection = findViewById(R.id.show_pictures_section);
mMessageContentView.setVerticalScrollBarEnabled(false);
mAttachments.setVisibility(View.GONE);
mAttachmentIcon.setVisibility(View.GONE);
findViewById(R.id.reply).setOnClickListener(this);
findViewById(R.id.reply_all).setOnClickListener(this);
findViewById(R.id.delete).setOnClickListener(this);
findViewById(R.id.show_pictures).setOnClickListener(this);
// UrlInterceptRegistry.registerHandler(this);
mMessageContentView.getSettings().setBlockNetworkImage(true);
mMessageContentView.getSettings().setSupportZoom(true);
setTitle("");
Intent intent = getIntent();
mAccount = (Account) intent.getSerializableExtra(EXTRA_ACCOUNT);
mFolder = intent.getStringExtra(EXTRA_FOLDER);
mMessageUid = intent.getStringExtra(EXTRA_MESSAGE);
mFolderUids = intent.getStringArrayListExtra(EXTRA_FOLDER_UIDS);
View next = findViewById(R.id.next);
View previous = findViewById(R.id.previous);
/*
* Next and Previous Message are not shown in landscape mode, so
* we need to check before we use them.
*/
if (next != null && previous != null) {
next.setOnClickListener(this);
previous.setOnClickListener(this);
findSurroundingMessagesUid();
previous.setVisibility(mPreviousMessageUid != null ? View.VISIBLE : View.GONE);
next.setVisibility(mNextMessageUid != null ? View.VISIBLE : View.GONE);
boolean goNext = intent.getBooleanExtra(EXTRA_NEXT, false);
if (goNext) {
next.requestFocus();
}
}
MessagingController.getInstance(getApplication()).addListener(mListener);
new Thread() {
public void run() {
// TODO this is a spot that should be eventually handled by a MessagingController
// thread pool. We want it in a thread but it can't be blocked by the normal
// synchronization stuff in MC.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
MessagingController.getInstance(getApplication()).loadMessageForView(
mAccount,
mFolder,
mMessageUid,
mListener);
}
}.start();
}
private void findSurroundingMessagesUid() {
for (int i = 0, count = mFolderUids.size(); i < count; i++) {
String messageUid = mFolderUids.get(i);
if (messageUid.equals(mMessageUid)) {
if (i != 0) {
mPreviousMessageUid = mFolderUids.get(i - 1);
}
if (i != count - 1) {
mNextMessageUid = mFolderUids.get(i + 1);
}
break;
}
}
}
public void onResume() {
super.onResume();
MessagingController.getInstance(getApplication()).addListener(mListener);
}
public void onPause() {
super.onPause();
MessagingController.getInstance(getApplication()).removeListener(mListener);
}
private void onDelete() {
if (mMessage != null) {
MessagingController.getInstance(getApplication()).deleteMessage(
mAccount,
mFolder,
mMessage,
null);
Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show();
// Remove this message's Uid locally
mFolderUids.remove(mMessage.getUid());
// Check if we have previous/next messages available before choosing
// which one to display
findSurroundingMessagesUid();
if (mPreviousMessageUid != null) {
onPrevious();
} else if (mNextMessageUid != null) {
onNext();
} else {
finish();
}
}
}
private void onReply() {
if (mMessage != null) {
MessageCompose.actionReply(this, mAccount, mMessage, false);
finish();
}
}
private void onReplyAll() {
if (mMessage != null) {
MessageCompose.actionReply(this, mAccount, mMessage, true);
finish();
}
}
private void onForward() {
if (mMessage != null) {
MessageCompose.actionForward(this, mAccount, mMessage);
finish();
}
}
private void onNext() {
Bundle extras = new Bundle(1);
extras.putBoolean(EXTRA_NEXT, true);
MessageView.actionView(this, mAccount, mFolder, mNextMessageUid, mFolderUids, extras);
finish();
}
private void onPrevious() {
MessageView.actionView(this, mAccount, mFolder, mPreviousMessageUid, mFolderUids);
finish();
}
private void onMarkAsUnread() {
MessagingController.getInstance(getApplication()).markMessageRead(
mAccount,
mFolder,
mMessage.getUid(),
false);
}
/**
* Creates a unique file in the given directory by appending a hyphen
* and a number to the given filename.
* @param directory
* @param filename
* @return
*/
private File createUniqueFile(File directory, String filename) {
File file = new File(directory, filename);
if (!file.exists()) {
return file;
}
// Get the extension of the file, if any.
int index = filename.lastIndexOf('.');
String format;
if (index != -1) {
String name = filename.substring(0, index);
String extension = filename.substring(index);
format = name + "-%d" + extension;
}
else {
format = filename + "-%d";
}
for (int i = 2; i < Integer.MAX_VALUE; i++) {
file = new File(directory, String.format(format, i));
if (!file.exists()) {
return file;
}
}
return null;
}
private void onDownloadAttachment(Attachment attachment) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
/*
* Abort early if there's no place to save the attachment. We don't want to spend
* the time downloading it and then abort.
*/
Toast.makeText(this,
getString(R.string.message_view_status_attachment_not_saved),
Toast.LENGTH_SHORT).show();
return;
}
MessagingController.getInstance(getApplication()).loadAttachment(
mAccount,
mMessage,
attachment.part,
new Object[] { true, attachment },
mListener);
}
private void onViewAttachment(Attachment attachment) {
MessagingController.getInstance(getApplication()).loadAttachment(
mAccount,
mMessage,
attachment.part,
new Object[] { false, attachment },
mListener);
}
private void onShowPictures() {
mMessageContentView.getSettings().setBlockNetworkImage(false);
mShowPicturesSection.setVisibility(View.GONE);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.reply:
onReply();
break;
case R.id.reply_all:
onReplyAll();
break;
case R.id.delete:
onDelete();
break;
case R.id.next:
onNext();
break;
case R.id.previous:
onPrevious();
break;
case R.id.download:
onDownloadAttachment((Attachment) view.getTag());
break;
case R.id.view:
onViewAttachment((Attachment) view.getTag());
break;
case R.id.show_pictures:
onShowPictures();
break;
}
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.delete:
onDelete();
break;
case R.id.reply:
onReply();
break;
case R.id.reply_all:
onReplyAll();
break;
case R.id.forward:
onForward();
break;
case R.id.mark_as_unread:
onMarkAsUnread();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.message_view_option, menu);
return true;
}
public CacheResult service(String url, Map<String, String> headers) {
String prefix = "http://cid/";
if (url.startsWith(prefix)) {
try {
String contentId = url.substring(prefix.length());
final Part part = MimeUtility.findPartByContentId(mMessage, "<" + contentId + ">");
if (part != null) {
CacheResult cr = new CacheManager.CacheResult();
// TODO looks fixed in Mainline, cr.setInputStream
// part.getBody().writeTo(cr.getStream());
return cr;
}
}
catch (Exception e) {
// TODO
}
}
return null;
}
private Bitmap getPreviewIcon(Attachment attachment) throws MessagingException {
try {
return BitmapFactory.decodeStream(
getContentResolver().openInputStream(
AttachmentProvider.getAttachmentThumbnailUri(mAccount,
attachment.part.getAttachmentId(),
62,
62)));
}
catch (Exception e) {
/*
* We don't care what happened, we just return null for the preview icon.
*/
return null;
}
}
/*
* Formats the given size as a String in bytes, kB, MB or GB with a single digit
* of precision. Ex: 12,315,000 = 12.3 MB
*/
public static String formatSize(float size) {
long kb = 1024;
long mb = (kb * 1024);
long gb = (mb * 1024);
if (size < kb) {
return String.format("%d bytes", (int) size);
}
else if (size < mb) {
return String.format("%.1f kB", size / kb);
}
else if (size < gb) {
return String.format("%.1f MB", size / mb);
}
else {
return String.format("%.1f GB", size / gb);
}
}
private void renderAttachments(Part part, int depth) throws MessagingException {
String contentType = MimeUtility.unfoldAndDecode(part.getContentType());
String name = MimeUtility.getHeaderParameter(contentType, "name");
if (name != null) {
/*
* We're guaranteed size because LocalStore.fetch puts it there.
*/
String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition());
int size = Integer.parseInt(MimeUtility.getHeaderParameter(contentDisposition, "size"));
Attachment attachment = new Attachment();
attachment.size = size;
attachment.contentType = part.getMimeType();
attachment.name = name;
attachment.part = (LocalAttachmentBodyPart) part;
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(R.layout.message_view_attachment, null);
TextView attachmentName = (TextView)view.findViewById(R.id.attachment_name);
TextView attachmentInfo = (TextView)view.findViewById(R.id.attachment_info);
ImageView attachmentIcon = (ImageView)view.findViewById(R.id.attachment_icon);
Button attachmentView = (Button)view.findViewById(R.id.view);
Button attachmentDownload = (Button)view.findViewById(R.id.download);
if ((!MimeUtility.mimeTypeMatches(attachment.contentType,
k9.ACCEPTABLE_ATTACHMENT_VIEW_TYPES))
|| (MimeUtility.mimeTypeMatches(attachment.contentType,
k9.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
attachmentView.setVisibility(View.GONE);
}
if ((!MimeUtility.mimeTypeMatches(attachment.contentType,
k9.ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))
|| (MimeUtility.mimeTypeMatches(attachment.contentType,
k9.UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))) {
attachmentDownload.setVisibility(View.GONE);
}
if (attachment.size > k9.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
attachmentView.setVisibility(View.GONE);
attachmentDownload.setVisibility(View.GONE);
}
attachment.viewButton = attachmentView;
attachment.downloadButton = attachmentDownload;
attachment.iconView = attachmentIcon;
view.setTag(attachment);
attachmentView.setOnClickListener(this);
attachmentView.setTag(attachment);
attachmentDownload.setOnClickListener(this);
attachmentDownload.setTag(attachment);
attachmentName.setText(name);
attachmentInfo.setText(formatSize(size));
Bitmap previewIcon = getPreviewIcon(attachment);
if (previewIcon != null) {
attachmentIcon.setImageBitmap(previewIcon);
}
mHandler.addAttachment(view);
}
if (part.getBody() instanceof Multipart) {
Multipart mp = (Multipart)part.getBody();
for (int i = 0; i < mp.getCount(); i++) {
renderAttachments(mp.getBodyPart(i), depth + 1);
}
}
}
class Listener extends MessagingListener {
@Override
public void loadMessageForViewHeadersAvailable(Account account, String folder, String uid,
final Message message) {
MessageView.this.mMessage = message;
try {
String subjectText = message.getSubject();
String fromText = Address.toFriendly(message.getFrom());
String dateText = Utility.isDateToday(message.getSentDate()) ?
mTimeFormat.format(message.getSentDate()) :
mDateTimeFormat.format(message.getSentDate());
String toText = Address.toFriendly(message.getRecipients(RecipientType.TO));
boolean hasAttachments = ((LocalMessage) message).getAttachmentCount() > 0;
mHandler.setHeaders(subjectText,
fromText,
dateText,
toText,
hasAttachments);
}
catch (MessagingException me) {
if (Config.LOGV) {
Log.v(k9.LOG_TAG, "loadMessageForViewHeadersAvailable", me);
}
}
}
@Override
public void loadMessageForViewBodyAvailable(Account account, String folder, String uid,
Message message) {
SpannableString markup;
MessageView.this.mMessage = message;
try {
Part part = MimeUtility.findFirstPartByMimeType(mMessage, "text/html");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(mMessage, "text/plain");
}
if (part != null) {
String text = MimeUtility.getTextFromPart(part);
if (part.getMimeType().equalsIgnoreCase("text/html")) {
text = text.replaceAll("cid:", "http://cid/");
} else {
Matcher m = Regex.WEB_URL_PATTERN.matcher(text);
StringBuffer sb = new StringBuffer();
while (m.find()) {
int start = m.start();
if (start != 0 && text.charAt(start - 1) != '@') {
m.appendReplacement(sb, "<a href=\"$0\">$0</a>");
}
else {
m.appendReplacement(sb, "$0");
}
}
m.appendTail(sb);
/*
* Convert plain text to HTML by replacing
* \r?\n with <br> and adding a html/body wrapper.
*/
text = sb.toString().replaceAll("\r?\n", "<br>");
text = "<html><body>" + text + "</body></html>";
}
/*
* TODO this should be smarter, change to regex for img, but consider how to
* get backgroung images and a million other things that HTML allows.
*/
if (text.contains("<img")) {
mHandler.showShowPictures(true);
}
markup = new SpannableString(text);
Linkify.addLinks(markup, Linkify.ALL);
mMessageContentView.loadDataWithBaseURL("email://", markup.toString(), "text/html",
"utf-8", null);
}
else {
mMessageContentView.loadUrl("file:///android_asset/empty.html");
}
renderAttachments(mMessage, 0);
}
catch (Exception e) {
if (Config.LOGV) {
Log.v(k9.LOG_TAG, "loadMessageForViewBodyAvailable", e);
}
}
}
@Override
public void loadMessageForViewFailed(Account account, String folder, String uid,
final String message) {
mHandler.post(new Runnable() {
public void run() {
setProgressBarIndeterminateVisibility(false);
mHandler.networkError();
mMessageContentView.loadUrl("file:///android_asset/empty.html");
}
});
}
@Override
public void loadMessageForViewFinished(Account account, String folder, String uid,
Message message) {
mHandler.post(new Runnable() {
public void run() {
setProgressBarIndeterminateVisibility(false);
}
});
}
@Override
public void loadMessageForViewStarted(Account account, String folder, String uid) {
mHandler.post(new Runnable() {
public void run() {
mMessageContentView.loadUrl("file:///android_asset/loading.html");
setProgressBarIndeterminateVisibility(true);
}
});
}
@Override
public void loadAttachmentStarted(Account account, Message message,
Part part, Object tag, boolean requiresDownload) {
mHandler.setAttachmentsEnabled(false);
mHandler.progress(true);
if (requiresDownload) {
mHandler.fetchingAttachment();
}
}
@Override
public void loadAttachmentFinished(Account account, Message message,
Part part, Object tag) {
mHandler.setAttachmentsEnabled(true);
mHandler.progress(false);
Object[] params = (Object[]) tag;
boolean download = (Boolean) params[0];
Attachment attachment = (Attachment) params[1];
if (download) {
try {
File file = createUniqueFile(Environment.getExternalStorageDirectory(),
attachment.name);
Uri uri = AttachmentProvider.getAttachmentUri(
mAccount,
attachment.part.getAttachmentId());
InputStream in = getContentResolver().openInputStream(uri);
OutputStream out = new FileOutputStream(file);
IOUtils.copy(in, out);
out.flush();
out.close();
in.close();
mHandler.attachmentSaved(file.getName());
new MediaScannerNotifier(MessageView.this, file);
}
catch (IOException ioe) {
mHandler.attachmentNotSaved();
}
}
else {
Uri uri = AttachmentProvider.getAttachmentUri(
mAccount,
attachment.part.getAttachmentId());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
}
}
@Override
public void loadAttachmentFailed(Account account, Message message, Part part,
Object tag, String reason) {
mHandler.setAttachmentsEnabled(true);
mHandler.progress(false);
mHandler.networkError();
}
}
class MediaScannerNotifier implements MediaScannerConnectionClient {
private MediaScannerConnection mConnection;
private File mFile;
public MediaScannerNotifier(Context context, File file) {
mFile = file;
mConnection = new MediaScannerConnection(context, this);
mConnection.connect();
}
public void onMediaScannerConnected() {
mConnection.scanFile(mFile.getAbsolutePath(), null);
}
public void onScanCompleted(String path, Uri uri) {
try {
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
startActivity(intent);
}
} finally {
mConnection.disconnect();
}
}
}
}

View File

@ -1,36 +0,0 @@
package com.fsck.k9.activity;
import android.content.Context;
/**
* A listener that the user can register for global, persistent progress events.
*/
public interface ProgressListener {
/**
* @param context
* @param title
* @param message
* @param currentProgress
* @param maxProgress
* @param indeterminate
*/
void showProgress(Context context, String title, String message, long currentProgress,
long maxProgress, boolean indeterminate);
/**
* @param context
* @param title
* @param message
* @param currentProgress
* @param maxProgress
* @param indeterminate
*/
void updateProgress(Context context, String title, String message, long currentProgress,
long maxProgress, boolean indeterminate);
/**
* @param context
*/
void hideProgress(Context context);
}

View File

@ -1,36 +0,0 @@
package com.fsck.k9.activity;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.Preferences;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
/**
* The Welcome activity initializes the application and decides what Activity
* the user should start with.
* If no accounts are configured the user is taken to the Accounts Activity where they
* can configure an account.
* If a single account is configured the user is taken directly to the FolderMessageList for
* the INBOX of that account.
* If more than one account is configuref the user is takaen to the Accounts Activity so they
* can select an account.
*/
public class Welcome extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Account[] accounts = Preferences.getPreferences(this).getAccounts();
if (accounts.length == 1) {
FolderMessageList.actionHandleAccount(this, accounts[0], k9.INBOX);
} else {
startActivity(new Intent(this, Accounts.class));
}
finish();
}
}

View File

@ -1,192 +0,0 @@
package com.fsck.k9.activity.setup;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.KeyEvent;
import android.preference.PreferenceActivity;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.RingtonePreference;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
public class AccountSettings extends PreferenceActivity {
private static final String EXTRA_ACCOUNT = "account";
private static final String PREFERENCE_TOP_CATERGORY = "account_settings";
private static final String PREFERENCE_DESCRIPTION = "account_description";
private static final String PREFERENCE_COMPOSITION = "composition";
private static final String PREFERENCE_FREQUENCY = "account_check_frequency";
private static final String PREFERENCE_DISPLAY_COUNT = "account_display_count";
private static final String PREFERENCE_DEFAULT = "account_default";
private static final String PREFERENCE_NOTIFY = "account_notify";
private static final String PREFERENCE_NOTIFY_RINGTONE = "account_notify_ringtone";
private static final String PREFERENCE_VIBRATE = "account_vibrate";
private static final String PREFERENCE_RINGTONE = "account_ringtone";
private static final String PREFERENCE_INCOMING = "incoming";
private static final String PREFERENCE_OUTGOING = "outgoing";
private Account mAccount;
private EditTextPreference mAccountDescription;
private ListPreference mCheckFrequency;
private ListPreference mDisplayCount;
private CheckBoxPreference mAccountDefault;
private CheckBoxPreference mAccountNotify;
private CheckBoxPreference mAccountNotifyRingtone;
private CheckBoxPreference mAccountVibrate;
private RingtonePreference mAccountRingtone;
public static void actionSettings(Context context, Account account) {
Intent i = new Intent(context, AccountSettings.class);
i.putExtra(EXTRA_ACCOUNT, account);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
addPreferencesFromResource(R.xml.account_settings_preferences);
Preference category = findPreference(PREFERENCE_TOP_CATERGORY);
category.setTitle(getString(R.string.account_settings_title_fmt));
mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
mAccountDescription.setSummary(mAccount.getDescription());
mAccountDescription.setText(mAccount.getDescription());
mAccountDescription.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
mAccountDescription.setSummary(summary);
mAccountDescription.setText(summary);
return false;
}
});
mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
mCheckFrequency.setValue(String.valueOf(mAccount.getAutomaticCheckIntervalMinutes()));
mCheckFrequency.setSummary(mCheckFrequency.getEntry());
mCheckFrequency.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
int index = mCheckFrequency.findIndexOfValue(summary);
mCheckFrequency.setSummary(mCheckFrequency.getEntries()[index]);
mCheckFrequency.setValue(summary);
return false;
}
});
mDisplayCount = (ListPreference) findPreference(PREFERENCE_DISPLAY_COUNT);
mDisplayCount.setValue(String.valueOf(mAccount.getDisplayCount()));
mDisplayCount.setSummary(mDisplayCount.getEntry());
mDisplayCount.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
int index = mDisplayCount.findIndexOfValue(summary);
mDisplayCount.setSummary(mDisplayCount.getEntries()[index]);
mDisplayCount.setValue(summary);
return false;
}
});
mAccountDefault = (CheckBoxPreference) findPreference(PREFERENCE_DEFAULT);
mAccountDefault.setChecked(
mAccount.equals(Preferences.getPreferences(this).getDefaultAccount()));
mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
mAccountNotify.setChecked(mAccount.isNotifyNewMail());
mAccountNotifyRingtone = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY_RINGTONE);
mAccountNotifyRingtone.setChecked(mAccount.isNotifyRingtone());
mAccountRingtone = (RingtonePreference) findPreference(PREFERENCE_RINGTONE);
// XXX: The following two lines act as a workaround for the RingtonePreference
// which does not let us set/get the value programmatically
SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
prefs.edit().putString(PREFERENCE_RINGTONE, mAccount.getRingtone()).commit();
mAccountVibrate = (CheckBoxPreference) findPreference(PREFERENCE_VIBRATE);
mAccountVibrate.setChecked(mAccount.isVibrate());
findPreference(PREFERENCE_COMPOSITION).setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
onCompositionSettings();
return true;
}
});
findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
onIncomingSettings();
return true;
}
});
findPreference(PREFERENCE_OUTGOING).setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
onOutgoingSettings();
return true;
}
});
}
@Override
public void onResume() {
super.onResume();
mAccount.refresh(Preferences.getPreferences(this));
}
private void saveSettings() {
if (mAccountDefault.isChecked()) {
Preferences.getPreferences(this).setDefaultAccount(mAccount);
}
mAccount.setDescription(mAccountDescription.getText());
mAccount.setNotifyNewMail(mAccountNotify.isChecked());
mAccount.setNotifyRingtone(mAccountNotifyRingtone.isChecked());
mAccount.setAutomaticCheckIntervalMinutes(Integer.parseInt(mCheckFrequency.getValue()));
mAccount.setDisplayCount(Integer.parseInt(mDisplayCount.getValue()));
mAccount.setVibrate(mAccountVibrate.isChecked());
SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
mAccount.setRingtone(prefs.getString(PREFERENCE_RINGTONE, null));
mAccount.save(Preferences.getPreferences(this));
k9.setServicesEnabled(this);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
saveSettings();
}
return super.onKeyDown(keyCode, event);
}
private void onCompositionSettings() {
AccountSetupComposition.actionEditCompositionSettings(this, mAccount);
}
private void onIncomingSettings() {
AccountSetupIncoming.actionEditIncomingSettings(this, mAccount);
}
private void onOutgoingSettings() {
AccountSetupOutgoing.actionEditOutgoingSettings(this, mAccount);
}
}

View File

@ -1,109 +0,0 @@
package com.fsck.k9.activity.setup;
import java.net.URI;
import java.net.URISyntaxException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.fsck.k9.Account;
import com.fsck.k9.R;
/**
* 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 AccountSetupAccountType extends Activity implements OnClickListener {
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
private Account mAccount;
private boolean mMakeDefault;
public static void actionSelectAccountType(Context context, Account account, boolean makeDefault) {
Intent i = new Intent(context, AccountSetupAccountType.class);
i.putExtra(EXTRA_ACCOUNT, account);
i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_account_type);
((Button)findViewById(R.id.pop)).setOnClickListener(this);
((Button)findViewById(R.id.imap)).setOnClickListener(this);
((Button)findViewById(R.id.webdav)).setOnClickListener(this);
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
mMakeDefault = (boolean)getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
}
private void onPop() {
try {
URI uri = new URI(mAccount.getStoreUri());
uri = new URI("pop3", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null);
mAccount.setStoreUri(uri.toString());
} catch (URISyntaxException use) {
/*
* This should not happen.
*/
throw new Error(use);
}
AccountSetupIncoming.actionIncomingSettings(this, mAccount, mMakeDefault);
finish();
}
private void onImap() {
try {
URI uri = new URI(mAccount.getStoreUri());
uri = new URI("imap", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null);
mAccount.setStoreUri(uri.toString());
} catch (URISyntaxException use) {
/*
* This should not happen.
*/
throw new Error(use);
}
AccountSetupIncoming.actionIncomingSettings(this, mAccount, mMakeDefault);
finish();
}
private void onWebDav() {
try {
URI uri = new URI(mAccount.getStoreUri());
uri = new URI("webdav", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null);
mAccount.setStoreUri(uri.toString());
} catch (URISyntaxException use) {
/*
* This should not happen.
*/
throw new Error(use);
}
AccountSetupIncoming.actionIncomingSettings(this, mAccount, mMakeDefault);
finish();
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.pop:
onPop();
break;
case R.id.imap:
onImap();
break;
case R.id.webdav:
onWebDav();
break;
}
}
}

View File

@ -1,388 +0,0 @@
package com.fsck.k9.activity.setup;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Contacts;
import android.provider.Contacts.People.ContactMethods;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.EmailAddressValidator;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.Utility;
/**
* Prompts the user for the email address and password. Also prompts for
* "Use this account as default" if this is the 2nd+ account being set up.
* Attempts to lookup default settings for the domain the user specified. If the
* domain is known the settings are handed off to the AccountSetupCheckSettings
* activity. If no settings are found the settings are handed off to the
* AccountSetupAccountType activity.
*/
public class AccountSetupBasics extends Activity
implements OnClickListener, TextWatcher {
private final static String EXTRA_ACCOUNT = "com.fsck.k9.AccountSetupBasics.account";
private final static int DIALOG_NOTE = 1;
private final static String STATE_KEY_PROVIDER =
"com.fsck.k9.AccountSetupBasics.provider";
private Preferences mPrefs;
private EditText mEmailView;
private EditText mPasswordView;
private CheckBox mDefaultView;
private Button mNextButton;
private Button mManualSetupButton;
private Account mAccount;
private Provider mProvider;
private EmailAddressValidator mEmailValidator = new EmailAddressValidator();
public static void actionNewAccount(Context context) {
Intent i = new Intent(context, AccountSetupBasics.class);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_basics);
mPrefs = Preferences.getPreferences(this);
mEmailView = (EditText)findViewById(R.id.account_email);
mPasswordView = (EditText)findViewById(R.id.account_password);
mDefaultView = (CheckBox)findViewById(R.id.account_default);
mNextButton = (Button)findViewById(R.id.next);
mManualSetupButton = (Button)findViewById(R.id.manual_setup);
mNextButton.setOnClickListener(this);
mManualSetupButton.setOnClickListener(this);
mEmailView.addTextChangedListener(this);
mPasswordView.addTextChangedListener(this);
if (mPrefs.getAccounts().length > 0) {
mDefaultView.setVisibility(View.VISIBLE);
}
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
mAccount = (Account)savedInstanceState.getSerializable(EXTRA_ACCOUNT);
}
if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) {
mProvider = (Provider)savedInstanceState.getSerializable(STATE_KEY_PROVIDER);
}
}
@Override
public void onResume() {
super.onResume();
validateFields();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(EXTRA_ACCOUNT, mAccount);
if (mProvider != null) {
outState.putSerializable(STATE_KEY_PROVIDER, mProvider);
}
}
public void afterTextChanged(Editable s) {
validateFields();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
private void validateFields() {
boolean valid = Utility.requiredFieldValid(mEmailView)
&& Utility.requiredFieldValid(mPasswordView)
&& mEmailValidator.isValid(mEmailView.getText().toString());
mNextButton.setEnabled(valid);
mManualSetupButton.setEnabled(valid);
/*
* Dim the next button's icon to 50% if the button is disabled.
* TODO this can probably be done with a stateful drawable. Check into it.
* android:state_enabled
*/
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
}
private String getOwnerName() {
String name = null;
String projection[] = {
ContactMethods.NAME
};
Cursor c = getContentResolver().query(
Uri.withAppendedPath(Contacts.People.CONTENT_URI, "owner"), projection, null, null,
null);
if (c.getCount() > 0) {
c.moveToFirst();
name = c.getString(0);
c.close();
}
if (name == null || name.length() == 0) {
Account account = Preferences.getPreferences(this).getDefaultAccount();
if (account != null) {
name = account.getName();
}
}
return name;
}
@Override
public Dialog onCreateDialog(int id) {
if (id == DIALOG_NOTE) {
if (mProvider != null && mProvider.note != null) {
return new AlertDialog.Builder(this)
.setMessage(mProvider.note)
.setPositiveButton(
getString(R.string.okay_action),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finishAutoSetup();
}
})
.setNegativeButton(
getString(R.string.cancel_action),
null)
.create();
}
}
return null;
}
private void finishAutoSetup() {
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
String[] emailParts = email.split("@");
String user = emailParts[0];
String domain = emailParts[1];
URI incomingUri = null;
URI outgoingUri = null;
try {
String incomingUsername = mProvider.incomingUsernameTemplate;
incomingUsername = incomingUsername.replaceAll("\\$email", email);
incomingUsername = incomingUsername.replaceAll("\\$user", user);
incomingUsername = incomingUsername.replaceAll("\\$domain", domain);
URI incomingUriTemplate = mProvider.incomingUriTemplate;
incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":"
+ password, incomingUriTemplate.getHost(), incomingUriTemplate.getPort(), null,
null, null);
String outgoingUsername = mProvider.outgoingUsernameTemplate;
outgoingUsername = outgoingUsername.replaceAll("\\$email", email);
outgoingUsername = outgoingUsername.replaceAll("\\$user", user);
outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain);
URI outgoingUriTemplate = mProvider.outgoingUriTemplate;
outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":"
+ password, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), null,
null, null);
} catch (URISyntaxException use) {
/*
* If there is some problem with the URI we give up and go on to
* manual setup.
*/
onManualSetup();
return;
}
mAccount = new Account(this);
mAccount.setName(getOwnerName());
mAccount.setEmail(email);
mAccount.setStoreUri(incomingUri.toString());
mAccount.setTransportUri(outgoingUri.toString());
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
if (incomingUri.toString().startsWith("imap")) {
mAccount.setDeletePolicy(Account.DELETE_POLICY_ON_DELETE);
}
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true);
}
private void onNext() {
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
String[] emailParts = email.split("@");
String user = emailParts[0];
String domain = emailParts[1];
mProvider = findProviderForDomain(domain);
if (mProvider == null) {
/*
* We don't have default settings for this account, start the manual
* setup process.
*/
onManualSetup();
return;
}
if (mProvider.note != null) {
showDialog(DIALOG_NOTE);
}
else {
finishAutoSetup();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
mAccount.setDescription(mAccount.getEmail());
mAccount.save(Preferences.getPreferences(this));
if (mDefaultView.isChecked()) {
Preferences.getPreferences(this).setDefaultAccount(mAccount);
}
k9.setServicesEnabled(this);
AccountSetupNames.actionSetNames(this, mAccount);
finish();
}
}
private void onManualSetup() {
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
String[] emailParts = email.split("@");
String user = emailParts[0];
String domain = emailParts[1];
mAccount = new Account(this);
mAccount.setName(getOwnerName());
mAccount.setEmail(email);
try {
URI uri = new URI("placeholder", user + ":" + password, "mail." + domain, -1, null,
null, null);
mAccount.setStoreUri(uri.toString());
mAccount.setTransportUri(uri.toString());
} catch (URISyntaxException use) {
/*
* If we can't set up the URL we just continue. It's only for
* convenience.
*/
}
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked());
finish();
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.next:
onNext();
break;
case R.id.manual_setup:
onManualSetup();
break;
}
}
/**
* Attempts to get the given attribute as a String resource first, and if it fails
* returns the attribute as a simple String value.
* @param xml
* @param name
* @return
*/
private String getXmlAttribute(XmlResourceParser xml, String name) {
int resId = xml.getAttributeResourceValue(null, name, 0);
if (resId == 0) {
return xml.getAttributeValue(null, name);
}
else {
return getString(resId);
}
}
private Provider findProviderForDomain(String domain) {
try {
XmlResourceParser xml = getResources().getXml(R.xml.providers);
int xmlEventType;
Provider provider = null;
while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
if (xmlEventType == XmlResourceParser.START_TAG
&& "provider".equals(xml.getName())
&& domain.equalsIgnoreCase(getXmlAttribute(xml, "domain"))) {
provider = new Provider();
provider.id = getXmlAttribute(xml, "id");
provider.label = getXmlAttribute(xml, "label");
provider.domain = getXmlAttribute(xml, "domain");
provider.note = getXmlAttribute(xml, "note");
}
else if (xmlEventType == XmlResourceParser.START_TAG
&& "incoming".equals(xml.getName())
&& provider != null) {
provider.incomingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
provider.incomingUsernameTemplate = getXmlAttribute(xml, "username");
}
else if (xmlEventType == XmlResourceParser.START_TAG
&& "outgoing".equals(xml.getName())
&& provider != null) {
provider.outgoingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
provider.outgoingUsernameTemplate = getXmlAttribute(xml, "username");
}
else if (xmlEventType == XmlResourceParser.END_TAG
&& "provider".equals(xml.getName())
&& provider != null) {
return provider;
}
}
}
catch (Exception e) {
Log.e(k9.LOG_TAG, "Error while trying to load provider settings.", e);
}
return null;
}
static class Provider implements Serializable {
private static final long serialVersionUID = 8511656164616538989L;
public String id;
public String label;
public String domain;
public URI incomingUriTemplate;
public String incomingUsernameTemplate;
public URI outgoingUriTemplate;
public String outgoingUsernameTemplate;
public String note;
}
}

View File

@ -1,274 +0,0 @@
package com.fsck.k9.activity.setup;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.fsck.k9.Account;
import com.fsck.k9.R;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.store.TrustManagerFactory;
/**
* Checks the given settings to make sure that they can be used to send and
* receive mail.
*
* XXX NOTE: The manifest for this app has it ignore config changes, because
* it doesn't correctly deal with restarting while its thread is running.
*/
public class AccountSetupCheckSettings extends Activity implements OnClickListener {
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_CHECK_INCOMING = "checkIncoming";
private static final String EXTRA_CHECK_OUTGOING = "checkOutgoing";
private Handler mHandler = new Handler();
private ProgressBar mProgressBar;
private TextView mMessageView;
private Account mAccount;
private boolean mCheckIncoming;
private boolean mCheckOutgoing;
private boolean mCanceled;
private boolean mDestroyed;
public static void actionCheckSettings(Activity context, Account account,
boolean checkIncoming, boolean checkOutgoing) {
Intent i = new Intent(context, AccountSetupCheckSettings.class);
i.putExtra(EXTRA_ACCOUNT, account);
i.putExtra(EXTRA_CHECK_INCOMING, checkIncoming);
i.putExtra(EXTRA_CHECK_OUTGOING, checkOutgoing);
context.startActivityForResult(i, 1);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_check_settings);
mMessageView = (TextView)findViewById(R.id.message);
mProgressBar = (ProgressBar)findViewById(R.id.progress);
((Button)findViewById(R.id.cancel)).setOnClickListener(this);
setMessage(R.string.account_setup_check_settings_retr_info_msg);
mProgressBar.setIndeterminate(true);
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
mCheckIncoming = (boolean)getIntent().getBooleanExtra(EXTRA_CHECK_INCOMING, false);
mCheckOutgoing = (boolean)getIntent().getBooleanExtra(EXTRA_CHECK_OUTGOING, false);
new Thread() {
public void run() {
Store store = null;
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
if (mDestroyed) {
return;
}
if (mCanceled) {
finish();
return;
}
if (mCheckIncoming) {
setMessage(R.string.account_setup_check_settings_check_incoming_msg);
store = Store.getInstance(mAccount.getStoreUri(), getApplication());
store.checkSettings();
}
if (mDestroyed) {
return;
}
if (mCanceled) {
finish();
return;
}
if (mCheckOutgoing) {
setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
Transport transport = Transport.getInstance(mAccount.getTransportUri());
transport.close();
transport.open();
transport.close();
}
if (mDestroyed) {
return;
}
if (mCanceled) {
finish();
return;
}
setResult(RESULT_OK);
finish();
} catch (final AuthenticationFailedException afe) {
showErrorDialog(
R.string.account_setup_failed_dlg_auth_message_fmt,
afe.getMessage() == null ? "" : afe.getMessage());
} catch (final CertificateValidationException cve) {
acceptKeyDialog(
R.string.account_setup_failed_dlg_certificate_message_fmt,
cve);
//cve.getMessage() == null ? "" : cve.getMessage());
} catch (final MessagingException me) {
showErrorDialog(
R.string.account_setup_failed_dlg_server_message_fmt,
me.getMessage() == null ? "" : me.getMessage());
}
}
}.start();
}
@Override
public void onDestroy() {
super.onDestroy();
mDestroyed = true;
mCanceled = true;
}
private void setMessage(final int resId) {
mHandler.post(new Runnable() {
public void run() {
if (mDestroyed) {
return;
}
mMessageView.setText(getString(resId));
}
});
}
private void showErrorDialog(final int msgResId, final Object... args) {
mHandler.post(new Runnable() {
public void run() {
if (mDestroyed) {
return;
}
mProgressBar.setIndeterminate(false);
new AlertDialog.Builder(AccountSetupCheckSettings.this)
.setTitle(getString(R.string.account_setup_failed_dlg_title))
.setMessage(getString(msgResId, args))
.setCancelable(true)
.setPositiveButton(
getString(R.string.account_setup_failed_dlg_edit_details_action),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.show();
}
});
}
private void acceptKeyDialog(final int msgResId, final Object... args) {
mHandler.post(new Runnable() {
public void run() {
if (mDestroyed) {
return;
}
final X509Certificate[] chain = TrustManagerFactory.getLastCertChain();
String exMessage = "Unknown Error";
Exception ex = ((Exception)args[0]);
if (ex != null) {
if (ex.getCause() != null) {
if (ex.getCause().getCause() != null) {
exMessage = ex.getCause().getCause().getMessage();
} else {
exMessage = ex.getCause().getMessage();
}
} else {
exMessage = ex.getMessage();
}
}
mProgressBar.setIndeterminate(false);
StringBuffer chainInfo = new StringBuffer(100);
for (int i = 0; i < chain.length; i++)
{
// display certificate chain information
chainInfo.append("Certificate chain[" + i + "]:\n");
chainInfo.append("Subject: " + chain[i].getSubjectDN().toString() + "\n");
chainInfo.append("Issuer: " + chain[i].getIssuerDN().toString() + "\n");
}
new AlertDialog.Builder(AccountSetupCheckSettings.this)
.setTitle(getString(R.string.account_setup_failed_dlg_invalid_certificate_title))
//.setMessage(getString(R.string.account_setup_failed_dlg_invalid_certificate)
.setMessage(getString(msgResId,exMessage)
+ " " + chainInfo.toString()
)
.setCancelable(true)
.setPositiveButton(
getString(R.string.account_setup_failed_dlg_invalid_certificate_accept),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try {
String alias = mAccount.getUuid();
if (mCheckIncoming) {
alias = alias + ".incoming";
}
if (mCheckOutgoing) {
alias = alias + ".outgoing";
}
TrustManagerFactory.addCertificateChain(alias, chain);
} catch (CertificateException e) {
showErrorDialog(
R.string.account_setup_failed_dlg_certificate_message_fmt,
e.getMessage() == null ? "" : e.getMessage());
}
AccountSetupCheckSettings.actionCheckSettings(AccountSetupCheckSettings.this, mAccount,
mCheckIncoming, mCheckOutgoing);
}
})
.setNegativeButton(
getString(R.string.account_setup_failed_dlg_invalid_certificate_reject),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.show();
}
});
}
public void onActivityResult(int reqCode, int resCode, Intent data) {
setResult(resCode);
finish();
}
private void onCancel() {
mCanceled = true;
setMessage(R.string.account_setup_check_settings_canceling_msg);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.cancel:
onCancel();
break;
}
}
}

View File

@ -1,117 +0,0 @@
package com.fsck.k9.activity.setup;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.KeyEvent;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.TextView;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.k9;
import com.fsck.k9.Utility;
public class AccountSetupComposition extends Activity {
private static final String EXTRA_ACCOUNT = "account";
private Account mAccount;
private EditText mAccountSignature;
private EditText mAccountEmail;
private EditText mAccountAlwaysBcc;
private EditText mAccountName;
private EditText mAccountSentItems;
private EditText mAccountDeletedItems;
public static void actionEditCompositionSettings(Activity context, Account account) {
Intent i = new Intent(context, AccountSetupComposition.class);
i.setAction(Intent.ACTION_EDIT);
i.putExtra(EXTRA_ACCOUNT, account);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
setContentView(R.layout.account_setup_composition);
/*
* If we're being reloaded we override the original account with the one
* we saved
*/
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
mAccount = (Account)savedInstanceState.getSerializable(EXTRA_ACCOUNT);
}
mAccountName = (EditText)findViewById(R.id.account_name);
mAccountName.setText(mAccount.getName());
mAccountEmail = (EditText)findViewById(R.id.account_email);
mAccountEmail.setText(mAccount.getEmail());
mAccountAlwaysBcc = (EditText)findViewById(R.id.account_always_bcc);
mAccountAlwaysBcc.setText(mAccount.getAlwaysBcc());
mAccountSignature = (EditText)findViewById(R.id.account_signature);
mAccountSignature.setText(mAccount.getSignature());
mAccountSentItems = (EditText)findViewById(R.id.account_sent_items);
mAccountSentItems.setText(mAccount.getSentFolderName());
mAccountDeletedItems = (EditText)findViewById(R.id.account_deleted_items);
mAccountDeletedItems.setText(mAccount.getTrashFolderName());
}
@Override
public void onResume() {
super.onResume();
mAccount.refresh(Preferences.getPreferences(this));
}
private void saveSettings() {
mAccount.setEmail(mAccountEmail.getText().toString());
mAccount.setAlwaysBcc(mAccountAlwaysBcc.getText().toString());
mAccount.setName(mAccountName.getText().toString());
mAccount.setSignature(mAccountSignature.getText().toString());
mAccount.setSentFolderName(mAccountSentItems.getText().toString());
mAccount.setTrashFolderName(mAccountDeletedItems.getText().toString());
mAccount.save(Preferences.getPreferences(this));
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
saveSettings();
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(EXTRA_ACCOUNT, mAccount);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mAccount.save(Preferences.getPreferences(this));
finish();
}
}

View File

@ -1,338 +0,0 @@
package com.fsck.k9.activity.setup;
import java.net.URI;
import java.net.URISyntaxException;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.Utility;
public class AccountSetupIncoming extends Activity implements OnClickListener {
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
private static final int popPorts[] = {
110, 995, 995, 110, 110
};
private static final String popSchemes[] = {
"pop3", "pop3+ssl", "pop3+ssl+", "pop3+tls", "pop3+tls+"
};
private static final int imapPorts[] = {
143, 993, 993, 143, 143
};
private static final String imapSchemes[] = {
"imap", "imap+ssl", "imap+ssl+", "imap+tls", "imap+tls+"
};
private static final int webdavPorts[] = {
80, 443, 443, 443, 443
};
private static final String webdavSchemes[] = {
"webdav", "webdav+ssl", "webdav+ssl+", "webdav+tls", "webdav+tls+"
};
private int mAccountPorts[];
private String mAccountSchemes[];
private EditText mUsernameView;
private EditText mPasswordView;
private EditText mServerView;
private EditText mPortView;
private Spinner mSecurityTypeView;
private Spinner mDeletePolicyView;
private EditText mImapPathPrefixView;
private Button mNextButton;
private Account mAccount;
private boolean mMakeDefault;
public static void actionIncomingSettings(Activity context, Account account, boolean makeDefault) {
Intent i = new Intent(context, AccountSetupIncoming.class);
i.putExtra(EXTRA_ACCOUNT, account);
i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
context.startActivity(i);
}
public static void actionEditIncomingSettings(Activity context, Account account) {
Intent i = new Intent(context, AccountSetupIncoming.class);
i.setAction(Intent.ACTION_EDIT);
i.putExtra(EXTRA_ACCOUNT, account);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_incoming);
mUsernameView = (EditText)findViewById(R.id.account_username);
mPasswordView = (EditText)findViewById(R.id.account_password);
TextView serverLabelView = (TextView) findViewById(R.id.account_server_label);
mServerView = (EditText)findViewById(R.id.account_server);
mPortView = (EditText)findViewById(R.id.account_port);
mSecurityTypeView = (Spinner)findViewById(R.id.account_security_type);
mDeletePolicyView = (Spinner)findViewById(R.id.account_delete_policy);
mImapPathPrefixView = (EditText)findViewById(R.id.imap_path_prefix);
mNextButton = (Button)findViewById(R.id.next);
mNextButton.setOnClickListener(this);
SpinnerOption securityTypes[] = {
new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)),
new SpinnerOption(1,
getString(R.string.account_setup_incoming_security_ssl_optional_label)),
new SpinnerOption(2, getString(R.string.account_setup_incoming_security_ssl_label)),
new SpinnerOption(3,
getString(R.string.account_setup_incoming_security_tls_optional_label)),
new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)),
};
SpinnerOption deletePolicies[] = {
new SpinnerOption(0,
getString(R.string.account_setup_incoming_delete_policy_never_label)),
new SpinnerOption(1,
getString(R.string.account_setup_incoming_delete_policy_7days_label)),
new SpinnerOption(2,
getString(R.string.account_setup_incoming_delete_policy_delete_label)),
};
ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this,
android.R.layout.simple_spinner_item, securityTypes);
securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mSecurityTypeView.setAdapter(securityTypesAdapter);
ArrayAdapter<SpinnerOption> deletePoliciesAdapter = new ArrayAdapter<SpinnerOption>(this,
android.R.layout.simple_spinner_item, deletePolicies);
deletePoliciesAdapter
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mDeletePolicyView.setAdapter(deletePoliciesAdapter);
/*
* Updates the port when the user changes the security type. This allows
* us to show a reasonable default which the user can change.
*/
mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) {
updatePortFromSecurityType();
}
public void onNothingSelected(AdapterView<?> arg0) {
}
});
/*
* Calls validateFields() which enables or disables the Next button
* based on the fields' validity.
*/
TextWatcher validationTextWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) {
validateFields();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
mUsernameView.addTextChangedListener(validationTextWatcher);
mPasswordView.addTextChangedListener(validationTextWatcher);
mServerView.addTextChangedListener(validationTextWatcher);
mPortView.addTextChangedListener(validationTextWatcher);
/*
* Only allow digits in the port field.
*/
mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
mMakeDefault = (boolean)getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
/*
* If we're being reloaded we override the original account with the one
* we saved
*/
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
mAccount = (Account)savedInstanceState.getSerializable(EXTRA_ACCOUNT);
}
try {
URI uri = new URI(mAccount.getStoreUri());
String username = null;
String password = null;
if (uri.getUserInfo() != null) {
String[] userInfoParts = uri.getUserInfo().split(":", 2);
username = userInfoParts[0];
if (userInfoParts.length > 1) {
password = userInfoParts[1];
}
}
if (username != null) {
mUsernameView.setText(username);
}
if (password != null) {
mPasswordView.setText(password);
}
if (uri.getScheme().startsWith("pop3")) {
serverLabelView.setText(R.string.account_setup_incoming_pop_server_label);
mAccountPorts = popPorts;
mAccountSchemes = popSchemes;
findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE);
} else if (uri.getScheme().startsWith("imap")) {
serverLabelView.setText(R.string.account_setup_incoming_imap_server_label);
mAccountPorts = imapPorts;
mAccountSchemes = imapSchemes;
findViewById(R.id.account_delete_policy_label).setVisibility(View.GONE);
mDeletePolicyView.setVisibility(View.GONE);
if (uri.getPath() != null && uri.getPath().length() > 0) {
mImapPathPrefixView.setText(uri.getPath().substring(1));
}
} else if (uri.getScheme().startsWith("webdav")) {
serverLabelView.setText(R.string.account_setup_incoming_webdav_server_label);
mAccountPorts = webdavPorts;
mAccountSchemes = webdavSchemes;
/** Hide the unnecessary fields */
findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE);
} else {
throw new Error("Unknown account type: " + mAccount.getStoreUri());
}
for (int i = 0; i < mAccountSchemes.length; i++) {
if (mAccountSchemes[i].equals(uri.getScheme())) {
SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i);
}
}
SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mAccount.getDeletePolicy());
if (uri.getHost() != null) {
mServerView.setText(uri.getHost());
}
if (uri.getPort() != -1) {
mPortView.setText(Integer.toString(uri.getPort()));
} else {
updatePortFromSecurityType();
}
} catch (URISyntaxException use) {
/*
* We should always be able to parse our own settings.
*/
throw new Error(use);
}
validateFields();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(EXTRA_ACCOUNT, mAccount);
}
private void validateFields() {
mNextButton
.setEnabled(Utility.requiredFieldValid(mUsernameView)
&& Utility.requiredFieldValid(mPasswordView)
&& Utility.requiredFieldValid(mServerView)
&& Utility.requiredFieldValid(mPortView));
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
}
private void updatePortFromSecurityType() {
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
mPortView.setText(Integer.toString(mAccountPorts[securityType]));
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (Intent.ACTION_EDIT.equals(getIntent().getAction())) {
mAccount.save(Preferences.getPreferences(this));
finish();
} else {
/*
* Set the username and password for the outgoing settings to the username and
* password the user just set for incoming.
*/
try {
URI oldUri = new URI(mAccount.getTransportUri());
URI uri = new URI(
oldUri.getScheme(),
mUsernameView.getText() + ":" + mPasswordView.getText(),
oldUri.getHost(),
oldUri.getPort(),
null,
null,
null);
mAccount.setTransportUri(uri.toString());
} catch (URISyntaxException use) {
/*
* If we can't set up the URL we just continue. It's only for
* convenience.
*/
}
AccountSetupOutgoing.actionOutgoingSettings(this, mAccount, mMakeDefault);
finish();
}
}
}
private void onNext() {
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
try {
String path = null;
if (mAccountSchemes[securityType].startsWith("imap")) {
path = "/" + mImapPathPrefixView.getText();
}
URI uri = new URI(
mAccountSchemes[securityType],
mUsernameView.getText() + ":" + mPasswordView.getText(),
mServerView.getText().toString(),
Integer.parseInt(mPortView.getText().toString()),
path, // path
null, // query
null);
mAccount.setStoreUri(uri.toString());
} catch (URISyntaxException use) {
/*
* It's unrecoverable if we cannot create a URI from components that
* we validated to be safe.
*/
throw new Error(use);
}
mAccount.setDeletePolicy((Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value);
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.next:
onNext();
break;
}
}
}

View File

@ -1,103 +0,0 @@
package com.fsck.k9.activity.setup;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.TextKeyListener;
import android.text.method.TextKeyListener.Capitalize;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.Utility;
import com.fsck.k9.activity.FolderMessageList;
public class AccountSetupNames extends Activity implements OnClickListener {
private static final String EXTRA_ACCOUNT = "account";
private EditText mDescription;
private EditText mName;
private Account mAccount;
private Button mDoneButton;
public static void actionSetNames(Context context, Account account) {
Intent i = new Intent(context, AccountSetupNames.class);
i.putExtra(EXTRA_ACCOUNT, account);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_names);
mDescription = (EditText)findViewById(R.id.account_description);
mName = (EditText)findViewById(R.id.account_name);
mDoneButton = (Button)findViewById(R.id.done);
mDoneButton.setOnClickListener(this);
TextWatcher validationTextWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) {
validateFields();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
mName.addTextChangedListener(validationTextWatcher);
mName.setKeyListener(TextKeyListener.getInstance(false, Capitalize.WORDS));
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
/*
* Since this field is considered optional, we don't set this here. If
* the user fills in a value we'll reset the current value, otherwise we
* just leave the saved value alone.
*/
// mDescription.setText(mAccount.getDescription());
if (mAccount.getName() != null) {
mName.setText(mAccount.getName());
}
if (!Utility.requiredFieldValid(mName)) {
mDoneButton.setEnabled(false);
}
}
private void validateFields() {
mDoneButton.setEnabled(Utility.requiredFieldValid(mName));
Utility.setCompoundDrawablesAlpha(mDoneButton, mDoneButton.isEnabled() ? 255 : 128);
}
private void onNext() {
if (Utility.requiredFieldValid(mDescription)) {
mAccount.setDescription(mDescription.getText().toString());
}
mAccount.setName(mName.getText().toString());
mAccount.save(Preferences.getPreferences(this));
FolderMessageList.actionHandleAccount(this, mAccount, k9.INBOX);
finish();
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.done:
onNext();
break;
}
}
}

View File

@ -1,130 +0,0 @@
package com.fsck.k9.activity.setup;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.Spinner;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
public class AccountSetupOptions extends Activity implements OnClickListener {
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
private Spinner mCheckFrequencyView;
private Spinner mDisplayCountView;
private CheckBox mDefaultView;
private CheckBox mNotifyView;
private CheckBox mNotifyRingtoneView;
private Account mAccount;
public static void actionOptions(Context context, Account account, boolean makeDefault) {
Intent i = new Intent(context, AccountSetupOptions.class);
i.putExtra(EXTRA_ACCOUNT, account);
i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_options);
mCheckFrequencyView = (Spinner)findViewById(R.id.account_check_frequency);
mDisplayCountView = (Spinner)findViewById(R.id.account_display_count);
mDefaultView = (CheckBox)findViewById(R.id.account_default);
mNotifyView = (CheckBox)findViewById(R.id.account_notify);
mNotifyRingtoneView = (CheckBox)findViewById(R.id.account_notify_ringtone);
findViewById(R.id.next).setOnClickListener(this);
SpinnerOption checkFrequencies[] = {
new SpinnerOption(-1,
getString(R.string.account_setup_options_mail_check_frequency_never)),
new SpinnerOption(5,
getString(R.string.account_setup_options_mail_check_frequency_5min)),
new SpinnerOption(10,
getString(R.string.account_setup_options_mail_check_frequency_10min)),
new SpinnerOption(15,
getString(R.string.account_setup_options_mail_check_frequency_15min)),
new SpinnerOption(30,
getString(R.string.account_setup_options_mail_check_frequency_30min)),
new SpinnerOption(60,
getString(R.string.account_setup_options_mail_check_frequency_1hour)),
};
ArrayAdapter<SpinnerOption> checkFrequenciesAdapter = new ArrayAdapter<SpinnerOption>(this,
android.R.layout.simple_spinner_item, checkFrequencies);
checkFrequenciesAdapter
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mCheckFrequencyView.setAdapter(checkFrequenciesAdapter);
SpinnerOption displayCounts[] = {
new SpinnerOption(10,
getString(R.string.account_setup_options_mail_display_count_10)),
new SpinnerOption(25,
getString(R.string.account_setup_options_mail_display_count_25)),
new SpinnerOption(50,
getString(R.string.account_setup_options_mail_display_count_50)),
new SpinnerOption(100,
getString(R.string.account_setup_options_mail_display_count_100)),
};
ArrayAdapter<SpinnerOption> displayCountsAdapter = new ArrayAdapter<SpinnerOption>(this,
android.R.layout.simple_spinner_item, displayCounts);
displayCountsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mDisplayCountView.setAdapter(displayCountsAdapter);
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
boolean makeDefault = getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
if (mAccount.equals(Preferences.getPreferences(this).getDefaultAccount()) || makeDefault) {
mDefaultView.setChecked(true);
}
mNotifyView.setChecked(mAccount.isNotifyNewMail());
mNotifyRingtoneView.setChecked(mAccount.isNotifyRingtone());
SpinnerOption.setSpinnerOptionValue(mCheckFrequencyView, mAccount
.getAutomaticCheckIntervalMinutes());
SpinnerOption.setSpinnerOptionValue(mDisplayCountView, mAccount
.getDisplayCount());
}
private void onDone() {
mAccount.setDescription(mAccount.getEmail());
mAccount.setNotifyNewMail(mNotifyView.isChecked());
mAccount.setNotifyRingtone(mNotifyRingtoneView.isChecked());
mAccount.setAutomaticCheckIntervalMinutes((Integer)((SpinnerOption)mCheckFrequencyView
.getSelectedItem()).value);
mAccount.setDisplayCount((Integer)((SpinnerOption)mDisplayCountView
.getSelectedItem()).value);
mAccount.save(Preferences.getPreferences(this));
if (mDefaultView.isChecked()) {
Preferences.getPreferences(this).setDefaultAccount(mAccount);
}
k9.setServicesEnabled(this);
AccountSetupNames.actionSetNames(this, mAccount);
finish();
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.next:
onDone();
break;
}
}
}

View File

@ -1,285 +0,0 @@
package com.fsck.k9.activity.setup;
import java.net.URI;
import java.net.URISyntaxException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.Utility;
public class AccountSetupOutgoing extends Activity implements OnClickListener,
OnCheckedChangeListener {
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
private static final int smtpPorts[] = {
25, 465, 465, 25, 25
};
private static final String smtpSchemes[] = {
"smtp", "smtp+ssl", "smtp+ssl+", "smtp+tls", "smtp+tls+"
};
private static final int webdavPorts[] = {
80, 443, 443, 443, 443
};
private static final String webdavSchemes[] = {
"webdav", "webdav+ssl", "webdav+ssl+", "webdav+tls", "webdav+tls+"
};
private EditText mUsernameView;
private EditText mPasswordView;
private EditText mServerView;
private EditText mPortView;
private CheckBox mRequireLoginView;
private ViewGroup mRequireLoginSettingsView;
private Spinner mSecurityTypeView;
private Button mNextButton;
private Account mAccount;
private boolean mMakeDefault;
public static void actionOutgoingSettings(Context context, Account account, boolean makeDefault) {
Intent i = new Intent(context, AccountSetupOutgoing.class);
i.putExtra(EXTRA_ACCOUNT, account);
i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
context.startActivity(i);
}
public static void actionEditOutgoingSettings(Context context, Account account) {
Intent i = new Intent(context, AccountSetupOutgoing.class);
i.setAction(Intent.ACTION_EDIT);
i.putExtra(EXTRA_ACCOUNT, account);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_outgoing);
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
try {
if (new URI(mAccount.getStoreUri()).getScheme().startsWith("webdav")) {
mAccount.setTransportUri(mAccount.getStoreUri());
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, false, true);
}
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mUsernameView = (EditText)findViewById(R.id.account_username);
mPasswordView = (EditText)findViewById(R.id.account_password);
mServerView = (EditText)findViewById(R.id.account_server);
mPortView = (EditText)findViewById(R.id.account_port);
mRequireLoginView = (CheckBox)findViewById(R.id.account_require_login);
mRequireLoginSettingsView = (ViewGroup)findViewById(R.id.account_require_login_settings);
mSecurityTypeView = (Spinner)findViewById(R.id.account_security_type);
mNextButton = (Button)findViewById(R.id.next);
mNextButton.setOnClickListener(this);
mRequireLoginView.setOnCheckedChangeListener(this);
SpinnerOption securityTypes[] = {
new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)),
new SpinnerOption(1,
getString(R.string.account_setup_incoming_security_ssl_optional_label)),
new SpinnerOption(2, getString(R.string.account_setup_incoming_security_ssl_label)),
new SpinnerOption(3,
getString(R.string.account_setup_incoming_security_tls_optional_label)),
new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)),
};
ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this,
android.R.layout.simple_spinner_item, securityTypes);
securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mSecurityTypeView.setAdapter(securityTypesAdapter);
/*
* Updates the port when the user changes the security type. This allows
* us to show a reasonable default which the user can change.
*/
mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) {
updatePortFromSecurityType();
}
public void onNothingSelected(AdapterView<?> arg0) {
}
});
/*
* Calls validateFields() which enables or disables the Next button
* based on the fields' validity.
*/
TextWatcher validationTextWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) {
validateFields();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
mUsernameView.addTextChangedListener(validationTextWatcher);
mPasswordView.addTextChangedListener(validationTextWatcher);
mServerView.addTextChangedListener(validationTextWatcher);
mPortView.addTextChangedListener(validationTextWatcher);
/*
* Only allow digits in the port field.
*/
mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
mMakeDefault = (boolean)getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
/*
* If we're being reloaded we override the original account with the one
* we saved
*/
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
mAccount = (Account)savedInstanceState.getSerializable(EXTRA_ACCOUNT);
}
try {
URI uri = new URI(mAccount.getTransportUri());
String username = null;
String password = null;
if (uri.getUserInfo() != null) {
String[] userInfoParts = uri.getUserInfo().split(":", 2);
username = userInfoParts[0];
if (userInfoParts.length > 1) {
password = userInfoParts[1];
}
}
if (username != null) {
mUsernameView.setText(username);
mRequireLoginView.setChecked(true);
}
if (password != null) {
mPasswordView.setText(password);
}
for (int i = 0; i < smtpSchemes.length; i++) {
if (smtpSchemes[i].equals(uri.getScheme())) {
SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i);
}
}
if (uri.getHost() != null) {
mServerView.setText(uri.getHost());
}
if (uri.getPort() != -1) {
mPortView.setText(Integer.toString(uri.getPort()));
} else {
updatePortFromSecurityType();
}
} catch (URISyntaxException use) {
/*
* We should always be able to parse our own settings.
*/
throw new Error(use);
}
validateFields();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(EXTRA_ACCOUNT, mAccount);
}
private void validateFields() {
mNextButton
.setEnabled(
Utility.requiredFieldValid(mServerView) &&
Utility.requiredFieldValid(mPortView) &&
(!mRequireLoginView.isChecked() ||
(Utility.requiredFieldValid(mUsernameView) &&
Utility.requiredFieldValid(mPasswordView))));
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
}
private void updatePortFromSecurityType() {
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
mPortView.setText(Integer.toString(smtpPorts[securityType]));
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (Intent.ACTION_EDIT.equals(getIntent().getAction())) {
mAccount.save(Preferences.getPreferences(this));
finish();
} else {
AccountSetupOptions.actionOptions(this, mAccount, mMakeDefault);
finish();
}
}
}
private void onNext() {
int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
URI uri;
try {
String userInfo = null;
if (mRequireLoginView.isChecked()) {
userInfo = mUsernameView.getText().toString() + ":"
+ mPasswordView.getText().toString();
}
uri = new URI(smtpSchemes[securityType], userInfo, mServerView.getText().toString(),
Integer.parseInt(mPortView.getText().toString()), null, null, null);
mAccount.setTransportUri(uri.toString());
} catch (URISyntaxException use) {
/*
* It's unrecoverable if we cannot create a URI from components that
* we validated to be safe.
*/
throw new Error(use);
}
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, false, true);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.next:
onNext();
break;
}
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mRequireLoginSettingsView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
validateFields();
}
}

View File

@ -1,33 +0,0 @@
/**
*
*/
package com.fsck.k9.activity.setup;
import android.widget.Spinner;
public class SpinnerOption {
public Object value;
public String label;
public static void setSpinnerOptionValue(Spinner spinner, Object value) {
for (int i = 0, count = spinner.getCount(); i < count; i++) {
SpinnerOption so = (SpinnerOption)spinner.getItemAtPosition(i);
if (so.value.equals(value)) {
spinner.setSelection(i, true);
return;
}
}
}
public SpinnerOption(Object value, String label) {
this.value = value;
this.label = label;
}
@Override
public String toString() {
return label;
}
}

View File

@ -1,788 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.fsck.k9.codec.binary;
import org.apache.commons.codec.BinaryDecoder;
import org.apache.commons.codec.BinaryEncoder;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.EncoderException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
/**
* Provides Base64 encoding and decoding as defined by RFC 2045.
*
* <p>
* This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
* Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
* </p>
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
* @author Apache Software Foundation
* @since 1.0-dev
* @version $Id$
*/
public class Base64 implements BinaryEncoder, BinaryDecoder {
/**
* Chunk size per RFC 2045 section 6.8.
*
* <p>
* The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
* equal signs.
* </p>
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
*/
static final int CHUNK_SIZE = 76;
/**
* Chunk separator per RFC 2045 section 2.1.
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
*/
static final byte[] CHUNK_SEPARATOR = {'\r','\n'};
/**
* This array is a lookup table that translates 6-bit positive integer
* index values into their "Base64 Alphabet" equivalents as specified
* in Table 1 of RFC 2045.
*
* Thanks to "commons" project in ws.apache.org for this code.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
*/
private static final byte[] intToBase64 = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
/**
* Byte used to pad output.
*/
private static final byte PAD = '=';
/**
* This array is a lookup table that translates unicode characters
* drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
* into their 6-bit positive integer equivalents. Characters that
* are not in the Base64 alphabet but fall within the bounds of the
* array are translated to -1.
*
* Thanks to "commons" project in ws.apache.org for this code.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
*/
private static final byte[] base64ToInt = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
/** Mask used to extract 6 bits, used when encoding */
private static final int MASK_6BITS = 0x3f;
/** Mask used to extract 8 bits, used in decoding base64 bytes */
private static final int MASK_8BITS = 0xff;
// The static final fields above are used for the original static byte[] methods on Base64.
// The private member fields below are used with the new streaming approach, which requires
// some state be preserved between calls of encode() and decode().
/**
* Line length for encoding. Not used when decoding. A value of zero or less implies
* no chunking of the base64 encoded data.
*/
private final int lineLength;
/**
* Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
*/
private final byte[] lineSeparator;
/**
* Convenience variable to help us determine when our buffer is going to run out of
* room and needs resizing. <code>decodeSize = 3 + lineSeparator.length;</code>
*/
private final int decodeSize;
/**
* Convenience variable to help us determine when our buffer is going to run out of
* room and needs resizing. <code>encodeSize = 4 + lineSeparator.length;</code>
*/
private final int encodeSize;
/**
* Buffer for streaming.
*/
private byte[] buf;
/**
* Position where next character should be written in the buffer.
*/
private int pos;
/**
* Position where next character should be read from the buffer.
*/
private int readPos;
/**
* Variable tracks how many characters have been written to the current line.
* Only used when encoding. We use it to make sure each encoded line never
* goes beyond lineLength (if lineLength > 0).
*/
private int currentLinePos;
/**
* Writes to the buffer only occur after every 3 reads when encoding, an
* every 4 reads when decoding. This variable helps track that.
*/
private int modulus;
/**
* Boolean flag to indicate the EOF has been reached. Once EOF has been
* reached, this Base64 object becomes useless, and must be thrown away.
*/
private boolean eof;
/**
* Place holder for the 3 bytes we're dealing with for our base64 logic.
* Bitwise operations store and extract the base64 encoding or decoding from
* this variable.
*/
private int x;
/**
* Default constructor: lineLength is 76, and the lineSeparator is CRLF
* when encoding, and all forms can be decoded.
*/
public Base64() {
this(CHUNK_SIZE, CHUNK_SEPARATOR);
}
/**
* <p>
* Consumer can use this constructor to choose a different lineLength
* when encoding (lineSeparator is still CRLF). All forms of data can
* be decoded.
* </p><p>
* Note: lineLengths that aren't multiples of 4 will still essentially
* end up being multiples of 4 in the encoded data.
* </p>
*
* @param lineLength each line of encoded data will be at most this long
* (rounded up to nearest multiple of 4).
* If lineLength <= 0, then the output will not be divided into lines (chunks).
* Ignored when decoding.
*/
public Base64(int lineLength) {
this(lineLength, CHUNK_SEPARATOR);
}
/**
* <p>
* Consumer can use this constructor to choose a different lineLength
* and lineSeparator when encoding. All forms of data can
* be decoded.
* </p><p>
* Note: lineLengths that aren't multiples of 4 will still essentially
* end up being multiples of 4 in the encoded data.
* </p>
* @param lineLength Each line of encoded data will be at most this long
* (rounded up to nearest multiple of 4). Ignored when decoding.
* If <= 0, then output will not be divided into lines (chunks).
* @param lineSeparator Each line of encoded data will end with this
* sequence of bytes.
* If lineLength <= 0, then the lineSeparator is not used.
* @throws IllegalArgumentException The provided lineSeparator included
* some base64 characters. That's not going to work!
*/
public Base64(int lineLength, byte[] lineSeparator) {
this.lineLength = lineLength;
this.lineSeparator = new byte[lineSeparator.length];
System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
if (lineLength > 0) {
this.encodeSize = 4 + lineSeparator.length;
} else {
this.encodeSize = 4;
}
this.decodeSize = encodeSize - 1;
if (containsBase64Byte(lineSeparator)) {
String sep;
try {
sep = new String(lineSeparator, "UTF-8");
} catch (UnsupportedEncodingException uee) {
sep = new String(lineSeparator);
}
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
}
}
/**
* Returns true if this Base64 object has buffered data for reading.
*
* @return true if there is Base64 object still available for reading.
*/
boolean hasData() { return buf != null; }
/**
* Returns the amount of buffered data available for reading.
*
* @return The amount of buffered data available for reading.
*/
int avail() { return buf != null ? pos - readPos : 0; }
/** Doubles our buffer. */
private void resizeBuf() {
if (buf == null) {
buf = new byte[8192];
pos = 0;
readPos = 0;
} else {
byte[] b = new byte[buf.length * 2];
System.arraycopy(buf, 0, b, 0, buf.length);
buf = b;
}
}
/**
* Extracts buffered data into the provided byte[] array, starting
* at position bPos, up to a maximum of bAvail bytes. Returns how
* many bytes were actually extracted.
*
* @param b byte[] array to extract the buffered data into.
* @param bPos position in byte[] array to start extraction at.
* @param bAvail amount of bytes we're allowed to extract. We may extract
* fewer (if fewer are available).
* @return The number of bytes successfully extracted into the provided
* byte[] array.
*/
int readResults(byte[] b, int bPos, int bAvail) {
if (buf != null) {
int len = Math.min(avail(), bAvail);
if (buf != b) {
System.arraycopy(buf, readPos, b, bPos, len);
readPos += len;
if (readPos >= pos) {
buf = null;
}
} else {
// Re-using the original consumer's output array is only
// allowed for one round.
buf = null;
}
return len;
} else {
return eof ? -1 : 0;
}
}
/**
* Small optimization where we try to buffer directly to the consumer's
* output array for one round (if consumer calls this method first!) instead
* of starting our own buffer.
*
* @param out byte[] array to buffer directly to.
* @param outPos Position to start buffering into.
* @param outAvail Amount of bytes available for direct buffering.
*/
void setInitialBuffer(byte[] out, int outPos, int outAvail) {
// We can re-use consumer's original output array under
// special circumstances, saving on some System.arraycopy().
if (out != null && out.length == outAvail) {
buf = out;
pos = outPos;
readPos = outPos;
}
}
/**
* <p>
* Encodes all of the provided data, starting at inPos, for inAvail bytes.
* Must be called at least twice: once with the data to encode, and once
* with inAvail set to "-1" to alert encoder that EOF has been reached,
* so flush last remaining bytes (if not multiple of 3).
* </p><p>
* Thanks to "commons" project in ws.apache.org for the bitwise operations,
* and general approach.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
* </p>
*
* @param in byte[] array of binary data to base64 encode.
* @param inPos Position to start reading data from.
* @param inAvail Amount of bytes available from input for encoding.
*/
void encode(byte[] in, int inPos, int inAvail) {
if (eof) {
return;
}
// inAvail < 0 is how we're informed of EOF in the underlying data we're
// encoding.
if (inAvail < 0) {
eof = true;
if (buf == null || buf.length - pos < encodeSize) {
resizeBuf();
}
switch (modulus) {
case 1:
buf[pos++] = intToBase64[(x >> 2) & MASK_6BITS];
buf[pos++] = intToBase64[(x << 4) & MASK_6BITS];
buf[pos++] = PAD;
buf[pos++] = PAD;
break;
case 2:
buf[pos++] = intToBase64[(x >> 10) & MASK_6BITS];
buf[pos++] = intToBase64[(x >> 4) & MASK_6BITS];
buf[pos++] = intToBase64[(x << 2) & MASK_6BITS];
buf[pos++] = PAD;
break;
}
if (lineLength > 0) {
System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
pos += lineSeparator.length;
}
} else {
for (int i = 0; i < inAvail; i++) {
if (buf == null || buf.length - pos < encodeSize) {
resizeBuf();
}
modulus = (++modulus) % 3;
int b = in[inPos++];
if (b < 0) { b += 256; }
x = (x << 8) + b;
if (0 == modulus) {
buf[pos++] = intToBase64[(x >> 18) & MASK_6BITS];
buf[pos++] = intToBase64[(x >> 12) & MASK_6BITS];
buf[pos++] = intToBase64[(x >> 6) & MASK_6BITS];
buf[pos++] = intToBase64[x & MASK_6BITS];
currentLinePos += 4;
if (lineLength > 0 && lineLength <= currentLinePos) {
System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
pos += lineSeparator.length;
currentLinePos = 0;
}
}
}
}
}
/**
* <p>
* Decodes all of the provided data, starting at inPos, for inAvail bytes.
* Should be called at least twice: once with the data to decode, and once
* with inAvail set to "-1" to alert decoder that EOF has been reached.
* The "-1" call is not necessary when decoding, but it doesn't hurt, either.
* </p><p>
* Ignores all non-base64 characters. This is how chunked (e.g. 76 character)
* data is handled, since CR and LF are silently ignored, but has implications
* for other bytes, too. This method subscribes to the garbage-in, garbage-out
* philosophy: it will not check the provided data for validity.
* </p><p>
* Thanks to "commons" project in ws.apache.org for the bitwise operations,
* and general approach.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
* </p>
* @param in byte[] array of ascii data to base64 decode.
* @param inPos Position to start reading data from.
* @param inAvail Amount of bytes available from input for encoding.
*/
void decode(byte[] in, int inPos, int inAvail) {
if (eof) {
return;
}
if (inAvail < 0) {
eof = true;
}
for (int i = 0; i < inAvail; i++) {
if (buf == null || buf.length - pos < decodeSize) {
resizeBuf();
}
byte b = in[inPos++];
if (b == PAD) {
x = x << 6;
switch (modulus) {
case 2:
x = x << 6;
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
break;
case 3:
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
buf[pos++] = (byte) ((x >> 8) & MASK_8BITS);
break;
}
// WE'RE DONE!!!!
eof = true;
return;
} else {
if (b >= 0 && b < base64ToInt.length) {
int result = base64ToInt[b];
if (result >= 0) {
modulus = (++modulus) % 4;
x = (x << 6) + result;
if (modulus == 0) {
buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
buf[pos++] = (byte) ((x >> 8) & MASK_8BITS);
buf[pos++] = (byte) (x & MASK_8BITS);
}
}
}
}
}
}
/**
* Returns whether or not the <code>octet</code> is in the base 64 alphabet.
*
* @param octet
* The value to test
* @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
*/
public static boolean isBase64(byte octet) {
return octet == PAD || (octet >= 0 && octet < base64ToInt.length && base64ToInt[octet] != -1);
}
/**
* Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
* Currently the method treats whitespace as valid.
*
* @param arrayOctet
* byte array to test
* @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is
* empty; false, otherwise
*/
public static boolean isArrayByteBase64(byte[] arrayOctet) {
for (int i = 0; i < arrayOctet.length; i++) {
if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
return false;
}
}
return true;
}
/*
* Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
*
* @param arrayOctet
* byte array to test
* @return <code>true</code> if any byte is a valid character in the Base64 alphabet; false herwise
*/
private static boolean containsBase64Byte(byte[] arrayOctet) {
for (int i = 0; i < arrayOctet.length; i++) {
if (isBase64(arrayOctet[i])) {
return true;
}
}
return false;
}
/**
* Encodes binary data using the base64 algorithm but does not chunk the output.
*
* @param binaryData
* binary data to encode
* @return Base64 characters
*/
public static byte[] encodeBase64(byte[] binaryData) {
return encodeBase64(binaryData, false);
}
/**
* Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
*
* @param binaryData
* binary data to encode
* @return Base64 characters chunked in 76 character blocks
*/
public static byte[] encodeBase64Chunked(byte[] binaryData) {
return encodeBase64(binaryData, true);
}
/**
* Decodes an Object using the base64 algorithm. This method is provided in order to satisfy the requirements of the
* Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[].
*
* @param pObject
* Object to decode
* @return An object (of type byte[]) containing the binary data which corresponds to the byte[] supplied.
* @throws DecoderException
* if the parameter supplied is not of type byte[]
*/
public Object decode(Object pObject) throws DecoderException {
if (!(pObject instanceof byte[])) {
throw new DecoderException("Parameter supplied to Base64 decode is not a byte[]");
}
return decode((byte[]) pObject);
}
/**
* Decodes a byte[] containing containing characters in the Base64 alphabet.
*
* @param pArray
* A byte array containing Base64 character data
* @return a byte array containing binary data
*/
public byte[] decode(byte[] pArray) {
return decodeBase64(pArray);
}
/**
* Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
*
* @param binaryData
* Array containing binary data to encode.
* @param isChunked
* if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
* @return Base64-encoded data.
* @throws IllegalArgumentException
* Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
*/
public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {
if (binaryData == null || binaryData.length == 0) {
return binaryData;
}
Base64 b64 = isChunked ? new Base64() : new Base64(0);
long len = (binaryData.length * 4) / 3;
long mod = len % 4;
if (mod != 0) {
len += 4 - mod;
}
if (isChunked) {
len += (1 + (len / CHUNK_SIZE)) * CHUNK_SEPARATOR.length;
}
if (len > Integer.MAX_VALUE) {
throw new IllegalArgumentException(
"Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE);
}
byte[] buf = new byte[(int) len];
b64.setInitialBuffer(buf, 0, buf.length);
b64.encode(binaryData, 0, binaryData.length);
b64.encode(binaryData, 0, -1); // Notify encoder of EOF.
// Encoder might have resized, even though it was unnecessary.
if (b64.buf != buf) {
b64.readResults(buf, 0, buf.length);
}
return buf;
}
/**
* Decodes Base64 data into octets
*
* @param base64Data Byte array containing Base64 data
* @return Array containing decoded data.
*/
public static byte[] decodeBase64(byte[] base64Data) {
if (base64Data == null || base64Data.length == 0) {
return base64Data;
}
Base64 b64 = new Base64();
long len = (base64Data.length * 3) / 4;
byte[] buf = new byte[(int) len];
b64.setInitialBuffer(buf, 0, buf.length);
b64.decode(base64Data, 0, base64Data.length);
b64.decode(base64Data, 0, -1); // Notify decoder of EOF.
// We have no idea what the line-length was, so we
// cannot know how much of our array wasn't used.
byte[] result = new byte[b64.pos];
b64.readResults(result, 0, result.length);
return result;
}
/**
* Discards any whitespace from a base-64 encoded block.
*
* @param data
* The base-64 encoded data to discard the whitespace from.
* @return The data, less whitespace (see RFC 2045).
* @deprecated This method is no longer needed
*/
static byte[] discardWhitespace(byte[] data) {
byte groomedData[] = new byte[data.length];
int bytesCopied = 0;
for (int i = 0; i < data.length; i++) {
switch (data[i]) {
case ' ' :
case '\n' :
case '\r' :
case '\t' :
break;
default :
groomedData[bytesCopied++] = data[i];
}
}
byte packedData[] = new byte[bytesCopied];
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
return packedData;
}
/**
* Check if a byte value is whitespace or not.
*
* @param byteToCheck the byte to check
* @return true if byte is whitespace, false otherwise
*/
private static boolean isWhiteSpace(byte byteToCheck){
switch (byteToCheck) {
case ' ' :
case '\n' :
case '\r' :
case '\t' :
return true;
default :
return false;
}
}
/**
* Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any
* characters outside of the base64 alphabet are to be ignored in base64 encoded data."
*
* @param data
* The base-64 encoded data to groom
* @return The data, less non-base64 characters (see RFC 2045).
*/
static byte[] discardNonBase64(byte[] data) {
byte groomedData[] = new byte[data.length];
int bytesCopied = 0;
for (int i = 0; i < data.length; i++) {
if (isBase64(data[i])) {
groomedData[bytesCopied++] = data[i];
}
}
byte packedData[] = new byte[bytesCopied];
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
return packedData;
}
// Implementation of the Encoder Interface
/**
* Encodes an Object using the base64 algorithm. This method is provided in order to satisfy the requirements of the
* Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[].
*
* @param pObject
* Object to encode
* @return An object (of type byte[]) containing the base64 encoded data which corresponds to the byte[] supplied.
* @throws EncoderException
* if the parameter supplied is not of type byte[]
*/
public Object encode(Object pObject) throws EncoderException {
if (!(pObject instanceof byte[])) {
throw new EncoderException("Parameter supplied to Base64 encode is not a byte[]");
}
return encode((byte[]) pObject);
}
/**
* Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet.
*
* @param pArray
* a byte array containing binary data
* @return A byte array containing only Base64 character data
*/
public byte[] encode(byte[] pArray) {
return encodeBase64(pArray, false);
}
// Implementation of integer encoding used for crypto
/**
* Decode a byte64-encoded integer according to crypto
* standards such as W3C's XML-Signature
*
* @param pArray a byte array containing base64 character data
* @return A BigInteger
*/
public static BigInteger decodeInteger(byte[] pArray) {
return new BigInteger(1, decodeBase64(pArray));
}
/**
* Encode to a byte64-encoded integer according to crypto
* standards such as W3C's XML-Signature
*
* @param bigInt a BigInteger
* @return A byte array containing base64 character data
* @throws NullPointerException if null is passed in
*/
public static byte[] encodeInteger(BigInteger bigInt) {
if(bigInt == null) {
throw new NullPointerException("encodeInteger called with null parameter");
}
return encodeBase64(toIntegerBytes(bigInt), false);
}
/**
* Returns a byte-array representation of a <code>BigInteger</code>
* without sign bit.
*
* @param bigInt <code>BigInteger</code> to be converted
* @return a byte array representation of the BigInteger parameter
*/
static byte[] toIntegerBytes(BigInteger bigInt) {
int bitlen = bigInt.bitLength();
// round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
byte[] bigBytes = bigInt.toByteArray();
if(((bigInt.bitLength() % 8) != 0) &&
(((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
return bigBytes;
}
// set up params for copying everything but sign bit
int startSrc = 0;
int len = bigBytes.length;
// if bigInt is exactly byte-aligned, just skip signbit in copy
if((bigInt.bitLength() % 8) == 0) {
startSrc = 1;
len--;
}
int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
byte[] resizedBytes = new byte[bitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
return resizedBytes;
}
}

View File

@ -1,179 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.fsck.k9.codec.binary;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Provides Base64 encoding and decoding in a streaming fashion (unlimited size).
* When encoding the default lineLength is 76 characters and the default
* lineEnding is CRLF, but these can be overridden by using the appropriate
* constructor.
* <p>
* The default behaviour of the Base64OutputStream is to ENCODE, whereas the
* default behaviour of the Base64InputStream is to DECODE. But this behaviour
* can be overridden by using a different constructor.
* </p><p>
* This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
* Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
* </p>
*
* @author Apache Software Foundation
* @version $Id $
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
* @since 1.0-dev
*/
public class Base64OutputStream extends FilterOutputStream {
private final boolean doEncode;
private final Base64 base64;
private final byte[] singleByte = new byte[1];
/**
* Creates a Base64OutputStream such that all data written is Base64-encoded
* to the original provided OutputStream.
*
* @param out OutputStream to wrap.
*/
public Base64OutputStream(OutputStream out) {
this(out, true);
}
/**
* Creates a Base64OutputStream such that all data written is either
* Base64-encoded or Base64-decoded to the original provided OutputStream.
*
* @param out OutputStream to wrap.
* @param doEncode true if we should encode all data written to us,
* false if we should decode.
*/
public Base64OutputStream(OutputStream out, boolean doEncode) {
super(out);
this.doEncode = doEncode;
this.base64 = new Base64();
}
/**
* Creates a Base64OutputStream such that all data written is either
* Base64-encoded or Base64-decoded to the original provided OutputStream.
*
* @param out OutputStream to wrap.
* @param doEncode true if we should encode all data written to us,
* false if we should decode.
* @param lineLength If doEncode is true, each line of encoded
* data will contain lineLength characters.
* If lineLength <=0, the encoded data is not divided into lines.
* If doEncode is false, lineLength is ignored.
* @param lineSeparator If doEncode is true, each line of encoded
* data will be terminated with this byte sequence (e.g. \r\n).
* If lineLength <= 0, the lineSeparator is not used.
* If doEncode is false lineSeparator is ignored.
*/
public Base64OutputStream(OutputStream out, boolean doEncode, int lineLength, byte[] lineSeparator) {
super(out);
this.doEncode = doEncode;
this.base64 = new Base64(lineLength, lineSeparator);
}
/**
* Writes the specified <code>byte</code> to this output stream.
*/
public void write(int i) throws IOException {
singleByte[0] = (byte) i;
write(singleByte, 0, 1);
}
/**
* Writes <code>len</code> bytes from the specified
* <code>b</code> array starting at <code>offset</code> to
* this output stream.
*
* @param b source byte array
* @param offset where to start reading the bytes
* @param len maximum number of bytes to write
*
* @throws IOException if an I/O error occurs.
* @throws NullPointerException if the byte array parameter is null
* @throws IndexOutOfBoundsException if offset, len or buffer size are invalid
*/
public void write(byte b[], int offset, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (offset < 0 || len < 0 || offset + len < 0) {
throw new IndexOutOfBoundsException();
} else if (offset > b.length || offset + len > b.length) {
throw new IndexOutOfBoundsException();
} else if (len > 0) {
if (doEncode) {
base64.encode(b, offset, len);
} else {
base64.decode(b, offset, len);
}
flush(false);
}
}
/**
* Flushes this output stream and forces any buffered output bytes
* to be written out to the stream. If propogate is true, the wrapped
* stream will also be flushed.
*
* @param propogate boolean flag to indicate whether the wrapped
* OutputStream should also be flushed.
* @throws IOException if an I/O error occurs.
*/
private void flush(boolean propogate) throws IOException {
int avail = base64.avail();
if (avail > 0) {
byte[] buf = new byte[avail];
int c = base64.readResults(buf, 0, avail);
if (c > 0) {
out.write(buf, 0, c);
}
}
if (propogate) {
out.flush();
}
}
/**
* Flushes this output stream and forces any buffered output bytes
* to be written out to the stream.
*
* @throws IOException if an I/O error occurs.
*/
public void flush() throws IOException {
flush(true);
}
/**
* Closes this output stream, flushing any remaining bytes that must be encoded. The
* underlying stream is flushed but not closed.
*/
public void close() throws IOException {
// Notify encoder of EOF (-1).
if (doEncode) {
base64.encode(singleByte, 0, -1);
} else {
base64.decode(singleByte, 0, -1);
}
flush();
}
}

View File

@ -1,177 +0,0 @@
package com.fsck.k9;
import java.io.File;
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Config;
import android.util.Log;
import com.fsck.k9.activity.MessageCompose;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.service.BootReceiver;
import com.fsck.k9.service.MailService;
public class k9 extends Application {
public static Application app = null;
public static File tempDirectory;
public static final String LOG_TAG = "k9";
/**
* If this is enabled there will be additional logging information sent to
* Log.d, including protocol dumps.
*/
public static boolean DEBUG = false;
/**
* If this is enabled than logging that normally hides sensitive information
* like passwords will show that information.
*/
public static boolean DEBUG_SENSITIVE = false;
/**
* The MIME type(s) of attachments we're willing to send. At the moment it is not possible
* to open a chooser with a list of filter types, so the chooser is only opened with the first
* item in the list. The entire list will be used to filter down attachments that are added
* with Intent.ACTION_SEND.
*/
public static final String[] ACCEPTABLE_ATTACHMENT_SEND_TYPES = new String[] {
"*/*",
};
/**
* The MIME type(s) of attachments we're willing to view.
*/
public static final String[] ACCEPTABLE_ATTACHMENT_VIEW_TYPES = new String[] {
"image/*",
"audio/*",
"text/*",
};
/**
* The MIME type(s) of attachments we're not willing to view.
*/
public static final String[] UNACCEPTABLE_ATTACHMENT_VIEW_TYPES = new String[] {
"image/gif",
};
/**
* The MIME type(s) of attachments we're willing to download to SD.
*/
public static final String[] ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
"*/*",
};
/**
* The MIME type(s) of attachments we're not willing to download to SD.
*/
public static final String[] UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
"image/gif",
};
/**
* The special name "INBOX" is used throughout the application to mean "Whatever folder
* the server refers to as the user's Inbox. Placed here to ease use.
*/
public static final String INBOX = "INBOX";
/**
* 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 int DEFAULT_VISIBLE_LIMIT = 25;
/**
* Number of additioanl messages to load when a user selectes "Load more messages..."
*/
public static int VISIBLE_LIMIT_INCREMENT = 25;
/**
* The maximum size of an attachment we're willing to download (either View or Save)
* Attachments that are base64 encoded (most) will be about 1.375x their actual size
* so we should probably factor that in. A 5MB attachment will generally be around
* 6.8MB downloaded but only 5MB saved.
*/
public static final int MAX_ATTACHMENT_DOWNLOAD_SIZE = (5 * 1024 * 1024);
/**
* 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.
*/
public static void setServicesEnabled(Context context) {
setServicesEnabled(context, Preferences.getPreferences(context).getAccounts().length > 0);
}
public static void setServicesEnabled(Context context, boolean enabled) {
PackageManager pm = context.getPackageManager();
if (!enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
/*
* If no accounts now exist but the service is still enabled we're about to disable it
* so we'll reschedule to kill off any existing alarms.
*/
MailService.actionReschedule(context);
}
pm.setComponentEnabledSetting(
new ComponentName(context, MessageCompose.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(
new ComponentName(context, BootReceiver.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
pm.setComponentEnabledSetting(
new ComponentName(context, MailService.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
if (enabled && pm.getComponentEnabledSetting(new ComponentName(context, MailService.class)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
/*
* And now if accounts do exist then we've just enabled the service and we want to
* schedule alarms for the new accounts.
*/
MailService.actionReschedule(context);
}
}
@Override
public void onCreate() {
super.onCreate();
app = this;
Preferences prefs = Preferences.getPreferences(this);
DEBUG = prefs.geteEnableDebugLogging();
DEBUG_SENSITIVE = prefs.getEnableSensitiveLogging();
MessagingController.getInstance(this).resetVisibleLimits(prefs.getAccounts());
/*
* We have to give MimeMessage a temp directory because File.createTempFile(String, String)
* doesn't work in Android and MimeMessage does not have access to a Context.
*/
BinaryTempFileBody.setTempDirectory(getCacheDir());
/*
* Enable background sync of messages
*/
setServicesEnabled(this);
}
}

View File

@ -1,215 +0,0 @@
package com.fsck.k9.mail;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.james.mime4j.field.address.AddressList;
import org.apache.james.mime4j.field.address.Mailbox;
import org.apache.james.mime4j.field.address.MailboxList;
import org.apache.james.mime4j.field.address.NamedMailbox;
import org.apache.james.mime4j.field.address.parser.ParseException;
import android.util.Config;
import android.util.Log;
import com.fsck.k9.k9;
import com.fsck.k9.Utility;
import com.fsck.k9.mail.internet.MimeUtility;
public class Address {
String mAddress;
String mPersonal;
public Address(String address, String personal) {
this.mAddress = address;
this.mPersonal = personal;
}
public Address(String address) {
this.mAddress = address;
}
public String getAddress() {
return mAddress;
}
public void setAddress(String address) {
this.mAddress = address;
}
public String getPersonal() {
return mPersonal;
}
public void setPersonal(String personal) {
this.mPersonal = personal;
}
/**
* Parse a comma separated list of addresses in RFC-822 format and return an
* array of Address objects.
*
* @param addressList
* @return An array of 0 or more Addresses.
*/
public static Address[] parse(String addressList) {
ArrayList<Address> addresses = new ArrayList<Address>();
if (addressList == null) {
return new Address[] {};
}
try {
MailboxList parsedList = AddressList.parse(addressList).flatten();
for (int i = 0, count = parsedList.size(); i < count; i++) {
org.apache.james.mime4j.field.address.Address address = parsedList.get(i);
if (address instanceof NamedMailbox) {
NamedMailbox namedMailbox = (NamedMailbox)address;
addresses.add(new Address(namedMailbox.getLocalPart() + "@"
+ namedMailbox.getDomain(), namedMailbox.getName()));
} else if (address instanceof Mailbox) {
Mailbox mailbox = (Mailbox)address;
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain()));
} else {
Log.e(k9.LOG_TAG, "Unknown address type from Mime4J: "
+ address.getClass().toString());
}
}
} catch (ParseException pe) {
}
return addresses.toArray(new Address[] {});
}
@Override
public boolean equals(Object o) {
if (o instanceof Address) {
return getAddress().equals(((Address) o).getAddress());
}
return super.equals(o);
}
public String toString() {
if (mPersonal != null) {
if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
return Utility.quoteString(mPersonal) + " <" + mAddress + ">";
} else {
return mPersonal + " <" + mAddress + ">";
}
} else {
return mAddress;
}
}
public static String toString(Address[] addresses) {
if (addresses == null) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < addresses.length; i++) {
sb.append(addresses[i].toString());
if (i < addresses.length - 1) {
sb.append(',');
}
}
return sb.toString();
}
/**
* Returns either the personal portion of the Address or the address portion if the personal
* is not available.
* @return
*/
public String toFriendly() {
if (mPersonal != null && mPersonal.length() > 0) {
return mPersonal;
}
else {
return mAddress;
}
}
public static String toFriendly(Address[] addresses) {
if (addresses == null) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < addresses.length; i++) {
sb.append(addresses[i].toFriendly());
if (i < addresses.length - 1) {
sb.append(',');
}
}
return sb.toString();
}
/**
* Unpacks an address list previously packed with packAddressList()
* @param list
* @return
*/
public static Address[] unpack(String addressList) {
if (addressList == null) {
return new Address[] { };
}
ArrayList<Address> addresses = new ArrayList<Address>();
int length = addressList.length();
int pairStartIndex = 0;
int pairEndIndex = 0;
int addressEndIndex = 0;
while (pairStartIndex < length) {
pairEndIndex = addressList.indexOf(',', pairStartIndex);
if (pairEndIndex == -1) {
pairEndIndex = length;
}
addressEndIndex = addressList.indexOf(';', pairStartIndex);
String address = null;
String personal = null;
if (addressEndIndex == -1 || addressEndIndex > pairEndIndex) {
address = Utility.fastUrlDecode(addressList.substring(pairStartIndex, pairEndIndex));
}
else {
address = Utility.fastUrlDecode(addressList.substring(pairStartIndex, addressEndIndex));
personal = Utility.fastUrlDecode(addressList.substring(addressEndIndex + 1, pairEndIndex));
}
addresses.add(new Address(address, personal));
pairStartIndex = pairEndIndex + 1;
}
return addresses.toArray(new Address[] { });
}
/**
* Packs an address list into a String that is very quick to read
* and parse. Packed lists can be unpacked with unpackAddressList()
* The packed list is a comma seperated list of:
* URLENCODE(address)[;URLENCODE(personal)]
* @param list
* @return
*/
public static String pack(Address[] addresses) {
if (addresses == null) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0, count = addresses.length; i < count; i++) {
Address address = addresses[i];
try {
sb.append(URLEncoder.encode(address.getAddress(), "UTF-8"));
if (address.getPersonal() != null) {
sb.append(';');
sb.append(URLEncoder.encode(address.getPersonal(), "UTF-8"));
}
if (i < count - 1) {
sb.append(',');
}
}
catch (UnsupportedEncodingException uee) {
return null;
}
}
return sb.toString();
}
}

View File

@ -1,14 +0,0 @@
package com.fsck.k9.mail;
public class AuthenticationFailedException extends MessagingException {
public static final long serialVersionUID = -1;
public AuthenticationFailedException(String message) {
super(message);
}
public AuthenticationFailedException(String message, Throwable throwable) {
super(message, throwable);
}
}

View File

@ -1,11 +0,0 @@
package com.fsck.k9.mail;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public interface Body {
public InputStream getInputStream() throws MessagingException;
public void writeTo(OutputStream out) throws IOException, MessagingException;
}

View File

@ -1,10 +0,0 @@
package com.fsck.k9.mail;
public abstract class BodyPart implements Part {
protected Multipart mParent;
public Multipart getParent() {
return mParent;
}
}

View File

@ -1,14 +0,0 @@
package com.fsck.k9.mail;
public class CertificateValidationException extends MessagingException {
public static final long serialVersionUID = -1;
public CertificateValidationException(String message) {
super(message);
}
public CertificateValidationException(String message, Throwable throwable) {
super(message, throwable);
}
}

View File

@ -1,57 +0,0 @@
package com.fsck.k9.mail;
import java.util.ArrayList;
/**
* <pre>
* A FetchProfile is a list of items that should be downloaded in bulk for a set of messages.
* FetchProfile can contain the following objects:
* FetchProfile.Item: Described below.
* Message: Indicates that the body of the entire message should be fetched.
* Synonymous with FetchProfile.Item.BODY.
* Part: Indicates that the given Part should be fetched. The provider
* is expected have previously created the given BodyPart and stored
* any information it needs to download the content.
* </pre>
*/
public class FetchProfile extends ArrayList {
/**
* Default items available for pre-fetching. It should be expected that any
* item fetched by using these items could potentially include all of the
* previous items.
*/
public enum Item {
/**
* Download the flags of the message.
*/
FLAGS,
/**
* Download the envelope of the message. This should include at minimum
* the size and the following headers: date, subject, from, content-type, to, cc
*/
ENVELOPE,
/**
* Download the structure of the message. This maps directly to IMAP's BODYSTRUCTURE
* and may map to other providers.
* The provider should, if possible, fill in a properly formatted MIME structure in
* the message without actually downloading any message data. If the provider is not
* capable of this operation it should specifically set the body of the message to null
* so that upper levels can detect that a full body download is needed.
*/
STRUCTURE,
/**
* A sane portion of the entire message, cut off at a provider determined limit.
* This should generaly be around 50kB.
*/
BODY_SANE,
/**
* The entire message.
*/
BODY,
}
}

View File

@ -1,48 +0,0 @@
package com.fsck.k9.mail;
/**
* Flags that can be applied to Messages.
*/
public enum Flag {
DELETED,
SEEN,
ANSWERED,
FLAGGED,
DRAFT,
RECENT,
/*
* The following flags are for internal library use only.
* TODO Eventually we should creates a Flags class that extends ArrayList that allows
* these flags and Strings to represent user defined flags. At that point the below
* flags should become user defined flags.
*/
/**
* Delete and remove from the LocalStore immediately.
*/
X_DESTROYED,
/**
* Sending of an unsent message failed. It will be retried. Used to show status.
*/
X_SEND_FAILED,
/**
* Sending of an unsent message is in progress.
*/
X_SEND_IN_PROGRESS,
/**
* Indicates that a message is fully downloaded from the server and can be viewed normally.
* This does not include attachments, which are never downloaded fully.
*/
X_DOWNLOADED_FULL,
/**
* Indicates that a message is partially downloaded from the server and can be viewed but
* more content is available on the server.
* This does not include attachments, which are never downloaded fully.
*/
X_DOWNLOADED_PARTIAL,
}

View File

@ -1,107 +0,0 @@
package com.fsck.k9.mail;
public abstract class Folder {
public enum OpenMode {
READ_WRITE, READ_ONLY,
}
public enum FolderType {
HOLDS_FOLDERS, HOLDS_MESSAGES,
}
/**
* Forces an open of the MailProvider. If the provider is already open this
* function returns without doing anything.
*
* @param mode READ_ONLY or READ_WRITE
*/
public abstract void open(OpenMode mode) throws MessagingException;
/**
* Forces a close of the MailProvider. Any further access will attempt to
* reopen the MailProvider.
*
* @param expunge If true all deleted messages will be expunged.
*/
public abstract void close(boolean expunge) throws MessagingException;
/**
* @return True if further commands are not expected to have to open the
* connection.
*/
public abstract boolean isOpen();
/**
* Get the mode the folder was opened with. This may be different than the mode the open
* was requested with.
* @return
*/
public abstract OpenMode getMode() throws MessagingException;
public abstract boolean create(FolderType type) throws MessagingException;
/**
* Create a new folder with a specified display limit. Not abstract to allow
* remote folders to not override or worry about this call if they don't care to.
*/
public boolean create(FolderType type, int displayLimit) throws MessagingException {
return create(type);
}
public abstract boolean exists() throws MessagingException;
/**
* @return A count of the messages in the selected folder.
*/
public abstract int getMessageCount() throws MessagingException;
public abstract int getUnreadMessageCount() throws MessagingException;
public abstract Message getMessage(String uid) throws MessagingException;
public abstract Message[] getMessages(int start, int end, MessageRetrievalListener listener)
throws MessagingException;
/**
* Fetches the given list of messages. The specified listener is notified as
* each fetch completes. Messages are downloaded as (as) lightweight (as
* possible) objects to be filled in with later requests. In most cases this
* means that only the UID is downloaded.
*
* @param uids
* @param listener
*/
public abstract Message[] getMessages(MessageRetrievalListener listener)
throws MessagingException;
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException;
public abstract void appendMessages(Message[] messages) throws MessagingException;
public abstract void copyMessages(Message[] msgs, Folder folder) throws MessagingException;
public abstract void setFlags(Message[] messages, Flag[] flags, boolean value)
throws MessagingException;
public abstract Message[] expunge() throws MessagingException;
public abstract void fetch(Message[] messages, FetchProfile fp,
MessageRetrievalListener listener) throws MessagingException;
public abstract void delete(boolean recurse) throws MessagingException;
public abstract String getName();
public abstract Flag[] getPermanentFlags() throws MessagingException;
public boolean supportsFetchingFlags() {
return true;
}//isFlagSupported
@Override
public String toString() {
return getName();
}
}

View File

@ -1,118 +0,0 @@
package com.fsck.k9.mail;
import java.util.Date;
import java.util.HashSet;
public abstract class Message implements Part, Body {
public enum RecipientType {
TO, CC, BCC,
}
protected String mUid;
protected HashSet<Flag> mFlags = new HashSet<Flag>();
protected Date mInternalDate;
protected Folder mFolder;
public String getUid() {
return mUid;
}
public void setUid(String uid) {
this.mUid = uid;
}
public Folder getFolder() {
return mFolder;
}
public abstract String getSubject() throws MessagingException;
public abstract void setSubject(String subject) throws MessagingException;
public Date getInternalDate() {
return mInternalDate;
}
public void setInternalDate(Date internalDate) {
this.mInternalDate = internalDate;
}
public abstract Date getReceivedDate() throws MessagingException;
public abstract Date getSentDate() throws MessagingException;
public abstract void setSentDate(Date sentDate) throws MessagingException;
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
public abstract void setRecipients(RecipientType type, Address[] addresses)
throws MessagingException;
public void setRecipient(RecipientType type, Address address) throws MessagingException {
setRecipients(type, new Address[] {
address
});
}
public abstract Address[] getFrom() throws MessagingException;
public abstract void setFrom(Address from) throws MessagingException;
public abstract Address[] getReplyTo() throws MessagingException;
public abstract void setReplyTo(Address[] from) throws MessagingException;
public abstract Body getBody() throws MessagingException;
public abstract String getContentType() throws MessagingException;
public abstract void addHeader(String name, String value) throws MessagingException;
public abstract void setHeader(String name, String value) throws MessagingException;
public abstract String[] getHeader(String name) throws MessagingException;
public abstract void removeHeader(String name) throws MessagingException;
public abstract void setBody(Body body) throws MessagingException;
public boolean isMimeType(String mimeType) throws MessagingException {
return getContentType().startsWith(mimeType);
}
/*
* TODO Refactor Flags at some point to be able to store user defined flags.
*/
public Flag[] getFlags() {
return mFlags.toArray(new Flag[] {});
}
public void setFlag(Flag flag, boolean set) throws MessagingException {
if (set) {
mFlags.add(flag);
} else {
mFlags.remove(flag);
}
}
/**
* This method calls setFlag(Flag, boolean)
* @param flags
* @param set
*/
public void setFlags(Flag[] flags, boolean set) throws MessagingException {
for (Flag flag : flags) {
setFlag(flag, set);
}
}
public boolean isSet(Flag flag) {
return mFlags.contains(flag);
}
public abstract void saveChanges() throws MessagingException;
}

View File

@ -1,19 +0,0 @@
package com.fsck.k9.mail;
import java.util.Comparator;
public class MessageDateComparator implements Comparator<Message> {
public int compare(Message o1, Message o2) {
try {
if (o1.getSentDate() == null) {
return 1;
} else if (o2.getSentDate() == null) {
return -1;
} else
return o2.getSentDate().compareTo(o1.getSentDate());
} catch (Exception e) {
return 0;
}
}
}

View File

@ -1,8 +0,0 @@
package com.fsck.k9.mail;
public interface MessageRetrievalListener {
public void messageStarted(String uid, int number, int ofTotal);
public void messageFinished(Message message, int number, int ofTotal);
}

View File

@ -1,14 +0,0 @@
package com.fsck.k9.mail;
public class MessagingException extends Exception {
public static final long serialVersionUID = -1;
public MessagingException(String message) {
super(message);
}
public MessagingException(String message, Throwable throwable) {
super(message, throwable);
}
}

View File

@ -1,48 +0,0 @@
package com.fsck.k9.mail;
import java.util.ArrayList;
public abstract class Multipart implements Body {
protected Part mParent;
protected ArrayList<BodyPart> mParts = new ArrayList<BodyPart>();
protected String mContentType;
public void addBodyPart(BodyPart part) throws MessagingException {
mParts.add(part);
}
public void addBodyPart(BodyPart part, int index) throws MessagingException {
mParts.add(index, part);
}
public BodyPart getBodyPart(int index) throws MessagingException {
return mParts.get(index);
}
public String getContentType() throws MessagingException {
return mContentType;
}
public int getCount() throws MessagingException {
return mParts.size();
}
public boolean removeBodyPart(BodyPart part) throws MessagingException {
return mParts.remove(part);
}
public void removeBodyPart(int index) throws MessagingException {
mParts.remove(index);
}
public Part getParent() throws MessagingException {
return mParent;
}
public void setParent(Part parent) throws MessagingException {
this.mParent = parent;
}
}

View File

@ -1,14 +0,0 @@
package com.fsck.k9.mail;
public class NoSuchProviderException extends MessagingException {
public static final long serialVersionUID = -1;
public NoSuchProviderException(String message) {
super(message);
}
public NoSuchProviderException(String message, Throwable throwable) {
super(message, throwable);
}
}

View File

@ -1,31 +0,0 @@
package com.fsck.k9.mail;
import java.io.IOException;
import java.io.OutputStream;
public interface Part {
public void addHeader(String name, String value) throws MessagingException;
public void removeHeader(String name) throws MessagingException;
public void setHeader(String name, String value) throws MessagingException;
public Body getBody() throws MessagingException;
public String getContentType() throws MessagingException;
public String getDisposition() throws MessagingException;
public String[] getHeader(String name) throws MessagingException;
public int getSize() throws MessagingException;
public boolean isMimeType(String mimeType) throws MessagingException;
public String getMimeType() throws MessagingException;
public void setBody(Body body) throws MessagingException;
public void writeTo(OutputStream out) throws IOException, MessagingException;
}

View File

@ -1,80 +0,0 @@
package com.fsck.k9.mail;
import java.util.HashMap;
import android.app.Application;
import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.WebDavStore;
/**
* Store is the access point for an email message store. It's location can be
* local or remote and no specific protocol is defined. Store is intended to
* loosely model in combination the JavaMail classes javax.mail.Store and
* javax.mail.Folder along with some additional functionality to improve
* performance on mobile devices. Implementations of this class should focus on
* making as few network connections as possible.
*/
public abstract class Store {
/**
* A global suggestion to Store implementors on how much of the body
* should be returned on FetchProfile.Item.BODY_SANE requests.
*/
public static final int FETCH_BODY_SANE_SUGGESTED_SIZE = (50 * 1024);
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
protected static final int SOCKET_READ_TIMEOUT = 60000;
private static HashMap<String, Store> mStores = new HashMap<String, Store>();
/**
* Get an instance of a mail store. The URI is parsed as a standard URI and
* the scheme is used to determine which protocol will be used. The
* following schemes are currently recognized: imap - IMAP with no
* connection security. Ex: imap://username:password@host/ imap+tls - IMAP
* with TLS connection security, if the server supports it. Ex:
* imap+tls://username:password@host imap+tls+ - IMAP with required TLS
* connection security. Connection fails if TLS is not available. Ex:
* imap+tls+://username:password@host imap+ssl+ - IMAP with required SSL
* connection security. Connection fails if SSL is not available. Ex:
* imap+ssl+://username:password@host
*
* @param uri The URI of the store.
* @return
* @throws MessagingException
*/
public synchronized static Store getInstance(String uri, Application application) throws MessagingException {
Store store = mStores.get(uri);
if (store == null) {
if (uri.startsWith("imap")) {
store = new ImapStore(uri);
} else if (uri.startsWith("pop3")) {
store = new Pop3Store(uri);
} else if (uri.startsWith("local")) {
store = new LocalStore(uri, application);
} else if (uri.startsWith("webdav")) {
store = new WebDavStore(uri);
}
if (store != null) {
mStores.put(uri, store);
}
}
if (store == null) {
throw new MessagingException("Unable to locate an applicable Store for " + uri);
}
return store;
}
public abstract Folder getFolder(String name) throws MessagingException;
public abstract Folder[] getPersonalNamespaces() throws MessagingException;
public abstract void checkSettings() throws MessagingException;
}

View File

@ -1,25 +0,0 @@
package com.fsck.k9.mail;
import com.fsck.k9.mail.transport.SmtpTransport;
import com.fsck.k9.mail.transport.WebDavTransport;
public abstract class Transport {
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
public synchronized static Transport getInstance(String uri) throws MessagingException {
if (uri.startsWith("smtp")) {
return new SmtpTransport(uri);
} else if (uri.startsWith("webdav")) {
return new WebDavTransport(uri);
} else {
throw new MessagingException("Unable to locate an applicable Transport for " + uri);
}
}
public abstract void open() throws MessagingException;
public abstract void sendMessage(Message message) throws MessagingException;
public abstract void close() throws MessagingException;
}

View File

@ -1,77 +0,0 @@
package com.fsck.k9.mail.internet;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import android.util.Config;
import android.util.Log;
import com.fsck.k9.k9;
import com.fsck.k9.codec.binary.Base64OutputStream;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
/**
* A Body that is backed by a temp file. The Body exposes a getOutputStream method that allows
* the user to write to the temp file. After the write the body is available via getInputStream
* and writeTo one time. After writeTo is called, or the InputStream returned from
* getInputStream is closed the file is deleted and the Body should be considered disposed of.
*/
public class BinaryTempFileBody implements Body {
private static File mTempDirectory;
private File mFile;
public static void setTempDirectory(File tempDirectory) {
mTempDirectory = tempDirectory;
}
public BinaryTempFileBody() throws IOException {
if (mTempDirectory == null) {
throw new
RuntimeException("setTempDirectory has not been called on BinaryTempFileBody!");
}
}
public OutputStream getOutputStream() throws IOException {
mFile = File.createTempFile("body", null, mTempDirectory);
mFile.deleteOnExit();
return new FileOutputStream(mFile);
}
public InputStream getInputStream() throws MessagingException {
try {
return new BinaryTempFileBodyInputStream(new FileInputStream(mFile));
}
catch (IOException ioe) {
throw new MessagingException("Unable to open body", ioe);
}
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
InputStream in = getInputStream();
Base64OutputStream base64Out = new Base64OutputStream(out);
IOUtils.copy(in, base64Out);
base64Out.close();
mFile.delete();
}
class BinaryTempFileBodyInputStream extends FilterInputStream {
public BinaryTempFileBodyInputStream(InputStream in) {
super(in);
}
@Override
public void close() throws IOException {
super.close();
mFile.delete();
}
}
}

View File

@ -1,121 +0,0 @@
package com.fsck.k9.mail.internet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.MessagingException;
/**
* TODO this is a close approximation of Message, need to update along with
* Message.
*/
public class MimeBodyPart extends BodyPart {
protected MimeHeader mHeader = new MimeHeader();
protected Body mBody;
protected int mSize;
public MimeBodyPart() throws MessagingException {
this(null);
}
public MimeBodyPart(Body body) throws MessagingException {
this(body, null);
}
public MimeBodyPart(Body body, String mimeType) throws MessagingException {
if (mimeType != null) {
setHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
}
setBody(body);
}
protected String getFirstHeader(String name) throws MessagingException {
return mHeader.getFirstHeader(name);
}
public void addHeader(String name, String value) throws MessagingException {
mHeader.addHeader(name, value);
}
public void setHeader(String name, String value) throws MessagingException {
mHeader.setHeader(name, value);
}
public String[] getHeader(String name) throws MessagingException {
return mHeader.getHeader(name);
}
public void removeHeader(String name) throws MessagingException {
mHeader.removeHeader(name);
}
public Body getBody() throws MessagingException {
return mBody;
}
public void setBody(Body body) throws MessagingException {
this.mBody = body;
if (body instanceof com.fsck.k9.mail.Multipart) {
com.fsck.k9.mail.Multipart multipart = ((com.fsck.k9.mail.Multipart)body);
multipart.setParent(this);
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
}
else if (body instanceof TextBody) {
String contentType = String.format("%s;\n charset=utf-8", getMimeType());
String name = MimeUtility.getHeaderParameter(getContentType(), "name");
if (name != null) {
contentType += String.format(";\n name=\"%s\"", name);
}
setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
}
}
public String getContentType() throws MessagingException {
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
if (contentType == null) {
return "text/plain";
} else {
return contentType;
}
}
public String getDisposition() throws MessagingException {
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
if (contentDisposition == null) {
return null;
} else {
return contentDisposition;
}
}
public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null);
}
public boolean isMimeType(String mimeType) throws MessagingException {
return getMimeType().equals(mimeType);
}
public int getSize() throws MessagingException {
return mSize;
}
/**
* Write the MimeMessage out in MIME format.
*/
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
mHeader.writeTo(out);
writer.write("\r\n");
writer.flush();
if (mBody != null) {
mBody.writeTo(out);
}
}
}

View File

@ -1,105 +0,0 @@
package com.fsck.k9.mail.internet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import com.fsck.k9.Utility;
import com.fsck.k9.mail.MessagingException;
public class MimeHeader {
/**
* Application specific header that contains Store specific information about an attachment.
* In IMAP this contains the IMAP BODYSTRUCTURE part id so that the ImapStore can later
* retrieve the attachment at will from the server.
* The info is recorded from this header on LocalStore.appendMessages and is put back
* into the MIME data by LocalStore.fetch.
*/
public static final String HEADER_ANDROID_ATTACHMENT_STORE_DATA = "X-Android-Attachment-StoreData";
public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
/**
* Fields that should be omitted when writing the header using writeTo()
*/
private static final String[] writeOmitFields = {
// HEADER_ANDROID_ATTACHMENT_DOWNLOADED,
// HEADER_ANDROID_ATTACHMENT_ID,
HEADER_ANDROID_ATTACHMENT_STORE_DATA
};
protected ArrayList<Field> mFields = new ArrayList<Field>();
public void clear() {
mFields.clear();
}
public String getFirstHeader(String name) throws MessagingException {
String[] header = getHeader(name);
if (header == null) {
return null;
}
return header[0];
}
public void addHeader(String name, String value) throws MessagingException {
mFields.add(new Field(name, MimeUtility.foldAndEncode(value)));
}
public void setHeader(String name, String value) throws MessagingException {
if (name == null || value == null) {
return;
}
removeHeader(name);
addHeader(name, value);
}
public String[] getHeader(String name) throws MessagingException {
ArrayList<String> values = new ArrayList<String>();
for (Field field : mFields) {
if (field.name.equalsIgnoreCase(name)) {
values.add(field.value);
}
}
if (values.size() == 0) {
return null;
}
return values.toArray(new String[] {});
}
public void removeHeader(String name) throws MessagingException {
ArrayList<Field> removeFields = new ArrayList<Field>();
for (Field field : mFields) {
if (field.name.equalsIgnoreCase(name)) {
removeFields.add(field);
}
}
mFields.removeAll(removeFields);
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
for (Field field : mFields) {
if (!Utility.arrayContains(writeOmitFields, field.name)) {
writer.write(field.name + ": " + field.value + "\r\n");
}
}
writer.flush();
}
class Field {
String name;
String value;
public Field(String name, String value) {
this.name = name;
this.value = value;
}
}
}

View File

@ -1,424 +0,0 @@
package com.fsck.k9.mail.internet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Stack;
import org.apache.james.mime4j.BodyDescriptor;
import org.apache.james.mime4j.ContentHandler;
import org.apache.james.mime4j.EOLConvertingInputStream;
import org.apache.james.mime4j.MimeStreamParser;
import org.apache.james.mime4j.field.DateTimeField;
import org.apache.james.mime4j.field.Field;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
/**
* An implementation of Message that stores all of it's metadata in RFC 822 and
* RFC 2045 style headers.
*/
public class MimeMessage extends Message {
protected MimeHeader mHeader = new MimeHeader();
protected Address[] mFrom;
protected Address[] mTo;
protected Address[] mCc;
protected Address[] mBcc;
protected Address[] mReplyTo;
protected Date mSentDate;
protected SimpleDateFormat mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
protected Body mBody;
protected int mSize;
public MimeMessage() {
/*
* Every new messages gets a Message-ID
*/
try {
setHeader("Message-ID", generateMessageId());
}
catch (MessagingException me) {
throw new RuntimeException("Unable to create MimeMessage", me);
}
}
private String generateMessageId() {
StringBuffer sb = new StringBuffer();
sb.append("<");
for (int i = 0; i < 24; i++) {
sb.append(Integer.toString((int)(Math.random() * 35), 36));
}
sb.append(".");
sb.append(Long.toString(System.currentTimeMillis()));
sb.append("@email.android.com>");
return sb.toString();
}
/**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
*
* @param in
* @throws IOException
* @throws MessagingException
*/
public MimeMessage(InputStream in) throws IOException, MessagingException {
parse(in);
}
protected void parse(InputStream in) throws IOException, MessagingException {
mHeader.clear();
mBody = null;
mBcc = null;
mTo = null;
mFrom = null;
mSentDate = null;
MimeStreamParser parser = new MimeStreamParser();
parser.setContentHandler(new MimeMessageBuilder());
parser.parse(new EOLConvertingInputStream(in));
}
public Date getReceivedDate() throws MessagingException {
return null;
}
public Date getSentDate() throws MessagingException {
if (mSentDate == null) {
try {
DateTimeField field = (DateTimeField)Field.parse("Date: "
+ MimeUtility.unfoldAndDecode(getFirstHeader("Date")));
mSentDate = field.getDate();
} catch (Exception e) {
}
}
return mSentDate;
}
public void setSentDate(Date sentDate) throws MessagingException {
setHeader("Date", mDateFormat.format(sentDate));
this.mSentDate = sentDate;
}
public String getContentType() throws MessagingException {
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
if (contentType == null) {
return "text/plain";
} else {
return contentType;
}
}
public String getDisposition() throws MessagingException {
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
if (contentDisposition == null) {
return null;
} else {
return contentDisposition;
}
}
public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null);
}
public int getSize() throws MessagingException {
return mSize;
}
/**
* Returns a list of the given recipient type from this message. If no addresses are
* found the method returns an empty array.
*/
public Address[] getRecipients(RecipientType type) throws MessagingException {
if (type == RecipientType.TO) {
if (mTo == null) {
mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To")));
}
return mTo;
} else if (type == RecipientType.CC) {
if (mCc == null) {
mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC")));
}
return mCc;
} else if (type == RecipientType.BCC) {
if (mBcc == null) {
mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC")));
}
return mBcc;
} else {
throw new MessagingException("Unrecognized recipient type.");
}
}
public void setRecipients(RecipientType type, Address[] addresses) throws MessagingException {
if (type == RecipientType.TO) {
if (addresses == null || addresses.length == 0) {
removeHeader("To");
this.mTo = null;
} else {
setHeader("To", Address.toString(addresses));
this.mTo = addresses;
}
} else if (type == RecipientType.CC) {
if (addresses == null || addresses.length == 0) {
removeHeader("CC");
this.mCc = null;
} else {
setHeader("CC", Address.toString(addresses));
this.mCc = addresses;
}
} else if (type == RecipientType.BCC) {
if (addresses == null || addresses.length == 0) {
removeHeader("BCC");
this.mBcc = null;
} else {
setHeader("BCC", Address.toString(addresses));
this.mBcc = addresses;
}
} else {
throw new MessagingException("Unrecognized recipient type.");
}
}
/**
* Returns the unfolded, decoded value of the Subject header.
*/
public String getSubject() throws MessagingException {
return MimeUtility.unfoldAndDecode(getFirstHeader("Subject"));
}
public void setSubject(String subject) throws MessagingException {
setHeader("Subject", subject);
}
public Address[] getFrom() throws MessagingException {
if (mFrom == null) {
String list = MimeUtility.unfold(getFirstHeader("From"));
if (list == null || list.length() == 0) {
list = MimeUtility.unfold(getFirstHeader("Sender"));
}
mFrom = Address.parse(list);
}
return mFrom;
}
public void setFrom(Address from) throws MessagingException {
if (from != null) {
setHeader("From", from.toString());
this.mFrom = new Address[] {
from
};
} else {
this.mFrom = null;
}
}
public Address[] getReplyTo() throws MessagingException {
if (mReplyTo == null) {
mReplyTo = Address.parse(MimeUtility.unfold(getFirstHeader("Reply-to")));
}
return mReplyTo;
}
public void setReplyTo(Address[] replyTo) throws MessagingException {
if (replyTo == null || replyTo.length == 0) {
removeHeader("Reply-to");
mReplyTo = null;
} else {
setHeader("Reply-to", Address.toString(replyTo));
mReplyTo = replyTo;
}
}
public void saveChanges() throws MessagingException {
throw new MessagingException("saveChanges not yet implemented");
}
public Body getBody() throws MessagingException {
return mBody;
}
public void setBody(Body body) throws MessagingException {
this.mBody = body;
if (body instanceof com.fsck.k9.mail.Multipart) {
com.fsck.k9.mail.Multipart multipart = ((com.fsck.k9.mail.Multipart)body);
multipart.setParent(this);
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
setHeader("MIME-Version", "1.0");
}
else if (body instanceof TextBody) {
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n charset=utf-8",
getMimeType()));
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
}
}
protected String getFirstHeader(String name) throws MessagingException {
return mHeader.getFirstHeader(name);
}
public void addHeader(String name, String value) throws MessagingException {
mHeader.addHeader(name, value);
}
public void setHeader(String name, String value) throws MessagingException {
mHeader.setHeader(name, value);
}
public String[] getHeader(String name) throws MessagingException {
return mHeader.getHeader(name);
}
public void removeHeader(String name) throws MessagingException {
mHeader.removeHeader(name);
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
mHeader.writeTo(out);
writer.write("\r\n");
writer.flush();
if (mBody != null) {
mBody.writeTo(out);
}
}
public InputStream getInputStream() throws MessagingException {
return null;
}
class MimeMessageBuilder implements ContentHandler {
private Stack stack = new Stack();
public MimeMessageBuilder() {
}
private void expect(Class c) {
if (!c.isInstance(stack.peek())) {
throw new IllegalStateException("Internal stack error: " + "Expected '"
+ c.getName() + "' found '" + stack.peek().getClass().getName() + "'");
}
}
public void startMessage() {
if (stack.isEmpty()) {
stack.push(MimeMessage.this);
} else {
expect(Part.class);
try {
MimeMessage m = new MimeMessage();
((Part)stack.peek()).setBody(m);
stack.push(m);
} catch (MessagingException me) {
throw new Error(me);
}
}
}
public void endMessage() {
expect(MimeMessage.class);
stack.pop();
}
public void startHeader() {
expect(Part.class);
}
public void field(String fieldData) {
expect(Part.class);
try {
String[] tokens = fieldData.split(":", 2);
((Part)stack.peek()).addHeader(tokens[0], tokens[1].trim());
} catch (MessagingException me) {
throw new Error(me);
}
}
public void endHeader() {
expect(Part.class);
}
public void startMultipart(BodyDescriptor bd) {
expect(Part.class);
Part e = (Part)stack.peek();
try {
MimeMultipart multiPart = new MimeMultipart(e.getContentType());
e.setBody(multiPart);
stack.push(multiPart);
} catch (MessagingException me) {
throw new Error(me);
}
}
public void body(BodyDescriptor bd, InputStream in) throws IOException {
expect(Part.class);
Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding());
try {
((Part)stack.peek()).setBody(body);
} catch (MessagingException me) {
throw new Error(me);
}
}
public void endMultipart() {
stack.pop();
}
public void startBodyPart() {
expect(MimeMultipart.class);
try {
MimeBodyPart bodyPart = new MimeBodyPart();
((MimeMultipart)stack.peek()).addBodyPart(bodyPart);
stack.push(bodyPart);
} catch (MessagingException me) {
throw new Error(me);
}
}
public void endBodyPart() {
expect(BodyPart.class);
stack.pop();
}
public void epilogue(InputStream is) throws IOException {
expect(MimeMultipart.class);
StringBuffer sb = new StringBuffer();
int b;
while ((b = is.read()) != -1) {
sb.append((char)b);
}
// ((Multipart) stack.peek()).setEpilogue(sb.toString());
}
public void preamble(InputStream is) throws IOException {
expect(MimeMultipart.class);
StringBuffer sb = new StringBuffer();
int b;
while ((b = is.read()) != -1) {
sb.append((char)b);
}
try {
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
} catch (MessagingException me) {
throw new Error(me);
}
}
public void raw(InputStream is) throws IOException {
throw new UnsupportedOperationException("Not supported");
}
}
}

View File

@ -1,95 +0,0 @@
package com.fsck.k9.mail.internet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
public class MimeMultipart extends Multipart {
protected String mPreamble;
protected String mContentType;
protected String mBoundary;
protected String mSubType;
public MimeMultipart() throws MessagingException {
mBoundary = generateBoundary();
setSubType("mixed");
}
public MimeMultipart(String contentType) throws MessagingException {
this.mContentType = contentType;
try {
mSubType = MimeUtility.getHeaderParameter(contentType, null).split("/")[1];
mBoundary = MimeUtility.getHeaderParameter(contentType, "boundary");
if (mBoundary == null) {
throw new MessagingException("MultiPart does not contain boundary: " + contentType);
}
} catch (Exception e) {
throw new MessagingException(
"Invalid MultiPart Content-Type; must contain subtype and boundary. ("
+ contentType + ")", e);
}
}
public String generateBoundary() {
StringBuffer sb = new StringBuffer();
sb.append("----");
for (int i = 0; i < 30; i++) {
sb.append(Integer.toString((int)(Math.random() * 35), 36));
}
return sb.toString().toUpperCase();
}
public String getPreamble() throws MessagingException {
return mPreamble;
}
public void setPreamble(String preamble) throws MessagingException {
this.mPreamble = preamble;
}
public String getContentType() throws MessagingException {
return mContentType;
}
public void setSubType(String subType) throws MessagingException {
this.mSubType = subType;
mContentType = String.format("multipart/%s; boundary=\"%s\"", subType, mBoundary);
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
if (mPreamble != null) {
writer.write(mPreamble + "\r\n");
}
if(mParts.size() == 0){
writer.write("--" + mBoundary + "\r\n");
}
for (int i = 0, count = mParts.size(); i < count; i++) {
BodyPart bodyPart = (BodyPart)mParts.get(i);
writer.write("--" + mBoundary + "\r\n");
writer.flush();
bodyPart.writeTo(out);
writer.write("\r\n");
}
writer.write("--" + mBoundary + "--\r\n");
writer.flush();
}
public InputStream getInputStream() throws MessagingException {
return null;
}
}

View File

@ -1,311 +0,0 @@
package com.fsck.k9.mail.internet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.decoder.Base64InputStream;
import org.apache.james.mime4j.decoder.DecoderUtil;
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
import android.util.Log;
import com.fsck.k9.k9;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
public class MimeUtility {
public static String unfold(String s) {
if (s == null) {
return null;
}
return s.replaceAll("\r|\n", "");
}
public static String decode(String s) {
if (s == null) {
return null;
}
return DecoderUtil.decodeEncodedWords(s);
}
public static String unfoldAndDecode(String s) {
return decode(unfold(s));
}
// TODO implement proper foldAndEncode
public static String foldAndEncode(String s) {
return s;
}
/**
* Returns the named parameter of a header field. If name is null the first
* parameter is returned, or if there are no additional parameters in the
* field the entire field is returned. Otherwise the named parameter is
* searched for in a case insensitive fashion and returned. If the parameter
* cannot be found the method returns null.
*
* @param header
* @param name
* @return
*/
public static String getHeaderParameter(String header, String name) {
if (header == null) {
return null;
}
header = header.replaceAll("\r|\n", "");
String[] parts = header.split(";");
if (name == null) {
return parts[0];
}
for (String part : parts) {
if (part.trim().toLowerCase().startsWith(name.toLowerCase())) {
String parameter = part.split("=", 2)[1].trim();
if (parameter.startsWith("\"") && parameter.endsWith("\"")) {
return parameter.substring(1, parameter.length() - 1);
}
else {
return parameter;
}
}
}
return null;
}
public static Part findFirstPartByMimeType(Part part, String mimeType)
throws MessagingException {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
for (int i = 0, count = multipart.getCount(); i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
Part ret = findFirstPartByMimeType(bodyPart, mimeType);
if (ret != null) {
return ret;
}
}
}
else if (part.getMimeType().equalsIgnoreCase(mimeType)) {
return part;
}
return null;
}
public static Part findPartByContentId(Part part, String contentId) throws Exception {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
for (int i = 0, count = multipart.getCount(); i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
Part ret = findPartByContentId(bodyPart, contentId);
if (ret != null) {
return ret;
}
}
}
String[] header = part.getHeader("Content-ID");
if (header != null) {
for (String s : header) {
if (s.equals(contentId)) {
return part;
}
}
}
return null;
}
/**
* Reads the Part's body and returns a String based on any charset conversion that needed
* to be done.
* @param part
* @return
* @throws IOException
*/
public static String getTextFromPart(Part part) {
Charset mCharsetConverter;
try {
if (part != null && part.getBody() != null) {
InputStream in = part.getBody().getInputStream();
String mimeType = part.getMimeType();
if (mimeType != null && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
/*
* Now we read the part into a buffer for further processing. Because
* the stream is now wrapped we'll remove any transfer encoding at this point.
*/
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
byte[] bytes = out.toByteArray();
in.close();
out.close();
String charset = getHeaderParameter(part.getContentType(), "charset");
/*
* We've got a text part, so let's see if it needs to be processed further.
*/
if (charset != null) {
/*
* See if there is conversion from the MIME charset to the Java one.
*/
mCharsetConverter = Charset.forName(charset);
charset = mCharsetConverter.name();
}
if (charset != null) {
/*
* We've got a charset encoding, so decode using it.
*/
return new String(bytes, 0, bytes.length, charset);
}
else {
/*
* No encoding, so use us-ascii, which is the standard.
*/
return new String(bytes, 0, bytes.length, "ASCII");
}
}
}
}
catch (Exception e) {
/*
* If we are not able to process the body there's nothing we can do about it. Return
* null and let the upper layers handle the missing content.
*/
Log.e(k9.LOG_TAG, "Unable to getTextFromPart", e);
}
return null;
}
/**
* Returns true if the given mimeType matches the matchAgainst specification.
* @param mimeType A MIME type to check.
* @param matchAgainst A MIME type to check against. May include wildcards such as image/* or
* * /*.
* @return
*/
public static boolean mimeTypeMatches(String mimeType, String matchAgainst) {
return mimeType.matches(matchAgainst.replaceAll("\\*", "\\.\\*"));
}
/**
* Returns true if the given mimeType matches any of the matchAgainst specifications.
* @param mimeType A MIME type to check.
* @param matchAgainst An array of MIME types to check against. May include wildcards such
* as image/* or * /*.
* @return
*/
public static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) {
for (String matchType : matchAgainst) {
if (mimeType.matches(matchType.replaceAll("\\*", "\\.\\*"))) {
return true;
}
}
return false;
}
/**
* Removes any content transfer encoding from the stream and returns a Body.
*/
public static Body decodeBody(InputStream in, String contentTransferEncoding)
throws IOException {
/*
* We'll remove any transfer encoding by wrapping the stream.
*/
if (contentTransferEncoding != null) {
contentTransferEncoding =
MimeUtility.getHeaderParameter(contentTransferEncoding, null);
if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) {
in = new QuotedPrintableInputStream(in);
}
else if ("base64".equalsIgnoreCase(contentTransferEncoding)) {
in = new Base64InputStream(in);
}
}
BinaryTempFileBody tempBody = new BinaryTempFileBody();
OutputStream out = tempBody.getOutputStream();
IOUtils.copy(in, out);
out.close();
return tempBody;
}
/**
* An unfortunately named method that makes decisions about a Part (usually a Message)
* as to which of it's children will be "viewable" and which will be attachments.
* The method recursively sorts the viewables and attachments into seperate
* lists for further processing.
* @param part
* @param viewables
* @param attachments
* @throws MessagingException
*/
public static void collectParts(Part part, ArrayList<Part> viewables,
ArrayList<Part> attachments) throws MessagingException {
String disposition = part.getDisposition();
String dispositionType = null;
String dispositionFilename = null;
if (disposition != null) {
dispositionType = MimeUtility.getHeaderParameter(disposition, null);
dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
}
/*
* A best guess that this part is intended to be an attachment and not inline.
*/
boolean attachment = ("attachment".equalsIgnoreCase(dispositionType))
|| (dispositionFilename != null)
&& (!"inline".equalsIgnoreCase(dispositionType));
/*
* If the part is Multipart but not alternative it's either mixed or
* something we don't know about, which means we treat it as mixed
* per the spec. We just process it's pieces recursively.
*/
if (part.getBody() instanceof Multipart) {
Multipart mp = (Multipart)part.getBody();
for (int i = 0; i < mp.getCount(); i++) {
collectParts(mp.getBodyPart(i), viewables, attachments);
}
}
/*
* If the part is an embedded message we just continue to process
* it, pulling any viewables or attachments into the running list.
*/
else if (part.getBody() instanceof Message) {
Message message = (Message)part.getBody();
collectParts(message, viewables, attachments);
}
/*
* If the part is HTML and it got this far it's part of a mixed (et
* al) and should be rendered inline.
*/
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/html"))) {
viewables.add(part);
}
/*
* If the part is plain text and it got this far it's part of a
* mixed (et al) and should be rendered inline.
*/
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/plain"))) {
viewables.add(part);
}
/*
* Finally, if it's nothing else we will include it as an attachment.
*/
else {
attachments.add(part);
}
}
}

View File

@ -1,47 +0,0 @@
package com.fsck.k9.mail.internet;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import com.fsck.k9.codec.binary.Base64;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
public class TextBody implements Body {
String mBody;
public TextBody(String body) {
this.mBody = body;
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
byte[] bytes = mBody.getBytes("UTF-8");
out.write(Base64.encodeBase64Chunked(bytes));
}
/**
* Get the text of the body in it's unencoded format.
* @return
*/
public String getText() {
return mBody;
}
/**
* Returns an InputStream that reads this body's text in UTF-8 format.
*/
public InputStream getInputStream() throws MessagingException {
try {
byte[] b = mBody.getBytes("UTF-8");
return new ByteArrayInputStream(b);
}
catch (UnsupportedEncodingException usee) {
return null;
}
}
}

View File

@ -1,356 +0,0 @@
/**
*
*/
package com.fsck.k9.mail.store;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import android.util.Config;
import android.util.Log;
import com.fsck.k9.k9;
import com.fsck.k9.FixedLengthInputStream;
import com.fsck.k9.PeekableInputStream;
import com.fsck.k9.mail.MessagingException;
public class ImapResponseParser {
SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z");
PeekableInputStream mIn;
InputStream mActiveLiteral;
public ImapResponseParser(PeekableInputStream in) {
this.mIn = in;
}
/**
* Reads the next response available on the stream and returns an
* ImapResponse object that represents it.
*
* @return
* @throws IOException
*/
public ImapResponse readResponse() throws IOException {
ImapResponse response = new ImapResponse();
if (mActiveLiteral != null) {
while (mActiveLiteral.read() != -1)
;
mActiveLiteral = null;
}
int ch = mIn.peek();
if (ch == '*') {
parseUntaggedResponse();
readTokens(response);
} else if (ch == '+') {
response.mCommandContinuationRequested =
parseCommandContinuationRequest();
readTokens(response);
} else {
response.mTag = parseTaggedResponse();
readTokens(response);
}
if (Config.LOGD) {
if (k9.DEBUG) {
Log.d(k9.LOG_TAG, "<<< " + response.toString());
}
}
return response;
}
private void readTokens(ImapResponse response) throws IOException {
response.clear();
Object token;
while ((token = readToken()) != null) {
if (response != null) {
response.add(token);
}
if (mActiveLiteral != null) {
break;
}
}
response.mCompleted = token == null;
}
/**
* Reads the next token of the response. The token can be one of: String -
* for NIL, QUOTED, NUMBER, ATOM. InputStream - for LITERAL.
* InputStream.available() returns the total length of the stream.
* ImapResponseList - for PARENTHESIZED LIST. Can contain any of the above
* elements including List.
*
* @return The next token in the response or null if there are no more
* tokens.
* @throws IOException
*/
public Object readToken() throws IOException {
while (true) {
Object token = parseToken();
if (token == null || !token.equals(")")) {
return token;
}
}
}
private Object parseToken() throws IOException {
if (mActiveLiteral != null) {
while (mActiveLiteral.read() != -1)
;
mActiveLiteral = null;
}
while (true) {
int ch = mIn.peek();
if (ch == '(') {
return parseList();
} else if (ch == ')') {
expect(')');
return ")";
} else if (ch == '"') {
return parseQuoted();
} else if (ch == '{') {
mActiveLiteral = parseLiteral();
return mActiveLiteral;
} else if (ch == ' ') {
expect(' ');
} else if (ch == '\r') {
expect('\r');
expect('\n');
return null;
} else if (ch == '\n') {
expect('\n');
return null;
} else if (ch == '\t') {
expect('\t');
} else {
return parseAtom();
}
}
}
private boolean parseCommandContinuationRequest() throws IOException {
expect('+');
expect(' ');
return true;
}
// * OK [UIDNEXT 175] Predicted next UID
private void parseUntaggedResponse() throws IOException {
expect('*');
expect(' ');
}
// 3 OK [READ-WRITE] Select completed.
private String parseTaggedResponse() throws IOException {
String tag = readStringUntil(' ');
return tag;
}
private ImapList parseList() throws IOException {
expect('(');
ImapList list = new ImapList();
Object token;
while (true) {
token = parseToken();
if (token == null) {
break;
} else if (token instanceof InputStream) {
list.add(token);
break;
} else if (token.equals(")")) {
break;
} else {
list.add(token);
}
}
return list;
}
private String parseAtom() throws IOException {
StringBuffer sb = new StringBuffer();
int ch;
while (true) {
ch = mIn.peek();
if (ch == -1) {
throw new IOException("parseAtom(): end of stream reached");
} else if (ch == '(' || ch == ')' || ch == '{' || ch == ' ' ||
// docs claim that flags are \ atom but atom isn't supposed to
// contain
// * and some falgs contain *
// ch == '%' || ch == '*' ||
ch == '%' ||
// TODO probably should not allow \ and should recognize
// it as a flag instead
// ch == '"' || ch == '\' ||
ch == '"' || (ch >= 0x00 && ch <= 0x1f) || ch == 0x7f) {
if (sb.length() == 0) {
throw new IOException(String.format("parseAtom(): (%04x %c)", (int)ch, ch));
}
return sb.toString();
} else {
sb.append((char)mIn.read());
}
}
}
/**
* A { has been read, read the rest of the size string, the space and then
* notify the listener with an InputStream.
*
* @param mListener
* @throws IOException
*/
private InputStream parseLiteral() throws IOException {
expect('{');
int size = Integer.parseInt(readStringUntil('}'));
expect('\r');
expect('\n');
FixedLengthInputStream fixed = new FixedLengthInputStream(mIn, size);
return fixed;
}
/**
* A " has been read, read to the end of the quoted string and notify the
* listener.
*
* @param mListener
* @throws IOException
*/
private String parseQuoted() throws IOException {
expect('"');
return readStringUntil('"');
}
private String readStringUntil(char end) throws IOException {
StringBuffer sb = new StringBuffer();
int ch;
while ((ch = mIn.read()) != -1) {
if (ch == end) {
return sb.toString();
} else {
sb.append((char)ch);
}
}
throw new IOException("readQuotedString(): end of stream reached");
}
private int expect(char ch) throws IOException {
int d;
if ((d = mIn.read()) != ch) {
throw new IOException(String.format("Expected %04x (%c) but got %04x (%c)", (int)ch,
ch, d, (char)d));
}
return d;
}
/**
* Represents an IMAP LIST response and is also the base class for the
* ImapResponse.
*/
public class ImapList extends ArrayList<Object> {
public ImapList getList(int index) {
return (ImapList)get(index);
}
public String getString(int index) {
return (String)get(index);
}
public InputStream getLiteral(int index) {
return (InputStream)get(index);
}
public int getNumber(int index) {
return Integer.parseInt(getString(index));
}
public Date getDate(int index) throws MessagingException {
try {
return mDateTimeFormat.parse(getString(index));
} catch (ParseException pe) {
throw new MessagingException("Unable to parse IMAP datetime", pe);
}
}
public Object getKeyedValue(Object key) {
for (int i = 0, count = size(); i < count; i++) {
if (get(i).equals(key)) {
return get(i + 1);
}
}
return null;
}
public ImapList getKeyedList(Object key) {
return (ImapList)getKeyedValue(key);
}
public String getKeyedString(Object key) {
return (String)getKeyedValue(key);
}
public InputStream getKeyedLiteral(Object key) {
return (InputStream)getKeyedValue(key);
}
public int getKeyedNumber(Object key) {
return Integer.parseInt(getKeyedString(key));
}
public Date getKeyedDate(Object key) throws MessagingException {
try {
String value = getKeyedString(key);
if (value == null) {
return null;
}
return mDateTimeFormat.parse(value);
} catch (ParseException pe) {
throw new MessagingException("Unable to parse IMAP datetime", pe);
}
}
}
/**
* Represents a single response from the IMAP server. Tagged responses will
* have a non-null tag. Untagged responses will have a null tag. The object
* will contain all of the available tokens at the time the response is
* received. In general, it will either contain all of the tokens of the
* response or all of the tokens up until the first LITERAL. If the object
* does not contain the entire response the caller must call more() to
* continue reading the response until more returns false.
*/
public class ImapResponse extends ImapList {
private boolean mCompleted;
boolean mCommandContinuationRequested;
String mTag;
public boolean more() throws IOException {
if (mCompleted) {
return false;
}
readTokens(this);
return true;
}
public String getAlertText() {
if (size() > 1 && "[ALERT]".equals(getString(1))) {
StringBuffer sb = new StringBuffer();
for (int i = 2, count = size(); i < count; i++) {
sb.append(get(i).toString());
sb.append(' ');
}
return sb.toString();
} else {
return null;
}
}
public String toString() {
return "#" + mTag + "# " + super.toString();
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,897 +0,0 @@
package com.fsck.k9.mail.store;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.SSLException;
import android.util.Config;
import android.util.Log;
import com.fsck.k9.k9;
import com.fsck.k9.Utility;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.Folder.OpenMode;
import com.fsck.k9.mail.internet.MimeMessage;
public class Pop3Store extends Store {
public static final int CONNECTION_SECURITY_NONE = 0;
public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED };
private String mHost;
private int mPort;
private String mUsername;
private String mPassword;
private int mConnectionSecurity;
private HashMap<String, Folder> mFolders = new HashMap<String, Folder>();
private Pop3Capabilities mCapabilities;
// /**
// * Detected latency, used for usage scaling.
// * Usage scaling occurs when it is neccesary to get information about
// * messages that could result in large data loads. This value allows
// * the code that loads this data to decide between using large downloads
// * (high latency) or multiple round trips (low latency) to accomplish
// * the same thing.
// * Default is Integer.MAX_VALUE implying massive latency so that the large
// * download method is used by default until latency data is collected.
// */
// private int mLatencyMs = Integer.MAX_VALUE;
//
// /**
// * Detected throughput, used for usage scaling.
// * Usage scaling occurs when it is neccesary to get information about
// * messages that could result in large data loads. This value allows
// * the code that loads this data to decide between using large downloads
// * (high latency) or multiple round trips (low latency) to accomplish
// * the same thing.
// * Default is Integer.MAX_VALUE implying massive bandwidth so that the
// * large download method is used by default until latency data is
// * collected.
// */
// private int mThroughputKbS = Integer.MAX_VALUE;
/**
* pop3://user:password@server:port CONNECTION_SECURITY_NONE
* pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
* pop3+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
* pop3+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
* pop3+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
*
* @param _uri
*/
public Pop3Store(String _uri) throws MessagingException {
URI uri;
try {
uri = new URI(_uri);
} catch (URISyntaxException use) {
throw new MessagingException("Invalid Pop3Store URI", use);
}
String scheme = uri.getScheme();
if (scheme.equals("pop3")) {
mConnectionSecurity = CONNECTION_SECURITY_NONE;
mPort = 110;
} else if (scheme.equals("pop3+tls")) {
mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL;
mPort = 110;
} else if (scheme.equals("pop3+tls+")) {
mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED;
mPort = 110;
} else if (scheme.equals("pop3+ssl+")) {
mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED;
mPort = 995;
} else if (scheme.equals("pop3+ssl")) {
mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL;
mPort = 995;
} else {
throw new MessagingException("Unsupported protocol");
}
mHost = uri.getHost();
if (uri.getPort() != -1) {
mPort = uri.getPort();
}
if (uri.getUserInfo() != null) {
String[] userInfoParts = uri.getUserInfo().split(":", 2);
mUsername = userInfoParts[0];
if (userInfoParts.length > 1) {
mPassword = userInfoParts[1];
}
}
}
@Override
public Folder getFolder(String name) throws MessagingException {
Folder folder = mFolders.get(name);
if (folder == null) {
folder = new Pop3Folder(name);
mFolders.put(folder.getName(), folder);
}
return folder;
}
@Override
public Folder[] getPersonalNamespaces() throws MessagingException {
return new Folder[] {
getFolder("INBOX"),
};
}
@Override
public void checkSettings() throws MessagingException {
Pop3Folder folder = new Pop3Folder("INBOX");
folder.open(OpenMode.READ_WRITE);
if (!mCapabilities.uidl) {
/*
* Run an additional test to see if UIDL is supported on the server. If it's not we
* can't service this account.
*/
try{
/*
* If the server doesn't support UIDL it will return a - response, which causes
* executeSimpleCommand to throw a MessagingException, exiting this method.
*/
folder.executeSimpleCommand("UIDL");
}
catch (IOException ioe) {
throw new MessagingException(null, ioe);
}
}
folder.close(false);
}
class Pop3Folder extends Folder {
private Socket mSocket;
private InputStream mIn;
private OutputStream mOut;
private HashMap<String, Pop3Message> mUidToMsgMap = new HashMap<String, Pop3Message>();
private HashMap<Integer, Pop3Message> mMsgNumToMsgMap = new HashMap<Integer, Pop3Message>();
private HashMap<String, Integer> mUidToMsgNumMap = new HashMap<String, Integer>();
private String mName;
private int mMessageCount;
public Pop3Folder(String name) {
this.mName = name;
if (mName.equalsIgnoreCase("INBOX")) {
mName = "INBOX";
}
}
@Override
public synchronized void open(OpenMode mode) throws MessagingException {
if (isOpen()) {
return;
}
if (!mName.equalsIgnoreCase("INBOX")) {
throw new MessagingException("Folder does not exist");
}
try {
SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
SSLContext sslContext = SSLContext.getInstance("TLS");
final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mHost, secure)
}, new SecureRandom());
mSocket = sslContext.getSocketFactory().createSocket();
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
} else {
mSocket = new Socket();
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
}
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
// Eat the banner
executeSimpleCommand(null);
mCapabilities = getCapabilities();
if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL
|| mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) {
if (mCapabilities.stls) {
writeLine("STLS");
SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mHost, secure)
}, new SecureRandom());
mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort,
true);
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
} else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) {
throw new MessagingException("TLS not supported but required");
}
}
try {
executeSimpleCommand("USER " + mUsername);
executeSimpleCommand("PASS " + mPassword);
} catch (MessagingException me) {
throw new AuthenticationFailedException(null, me);
}
} catch (SSLException e) {
throw new CertificateValidationException(e.getMessage(), e);
} catch (GeneralSecurityException gse) {
throw new MessagingException(
"Unable to open connection to POP server due to security error.", gse);
} catch (IOException ioe) {
throw new MessagingException("Unable to open connection to POP server.", ioe);
}
try {
String response = executeSimpleCommand("STAT");
String[] parts = response.split(" ");
mMessageCount = Integer.parseInt(parts[1]);
}
catch (IOException ioe) {
throw new MessagingException("Unable to STAT folder.", ioe);
}
mUidToMsgMap.clear();
mMsgNumToMsgMap.clear();
mUidToMsgNumMap.clear();
}
public boolean isOpen() {
return (mIn != null && mOut != null && mSocket != null && mSocket.isConnected() && !mSocket
.isClosed());
}
@Override
public OpenMode getMode() throws MessagingException {
return OpenMode.READ_ONLY;
}
@Override
public void close(boolean expunge) {
try {
executeSimpleCommand("QUIT");
}
catch (Exception e) {
/*
* QUIT may fail if the connection is already closed. We don't care. It's just
* being friendly.
*/
}
closeIO();
}
private void closeIO() {
try {
mIn.close();
} catch (Exception e) {
/*
* May fail if the connection is already closed.
*/
}
try {
mOut.close();
} catch (Exception e) {
/*
* May fail if the connection is already closed.
*/
}
try {
mSocket.close();
} catch (Exception e) {
/*
* May fail if the connection is already closed.
*/
}
mIn = null;
mOut = null;
mSocket = null;
}
@Override
public String getName() {
return mName;
}
@Override
public boolean create(FolderType type) throws MessagingException {
return false;
}
@Override
public boolean exists() throws MessagingException {
return mName.equalsIgnoreCase("INBOX");
}
@Override
public int getMessageCount() {
return mMessageCount;
}
@Override
public int getUnreadMessageCount() throws MessagingException {
return -1;
}
@Override
public Message getMessage(String uid) throws MessagingException {
Pop3Message message = mUidToMsgMap.get(uid);
if (message == null) {
message = new Pop3Message(uid, this);
}
return message;
}
@Override
public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
throws MessagingException {
if (start < 1 || end < 1 || end < start) {
throw new MessagingException(String.format("Invalid message set %d %d",
start, end));
}
try {
indexMsgNums(start, end);
} catch (IOException ioe) {
throw new MessagingException("getMessages", ioe);
}
ArrayList<Message> messages = new ArrayList<Message>();
int i = 0;
for (int msgNum = start; msgNum <= end; msgNum++) {
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
if (listener != null) {
listener.messageStarted(message.getUid(), i++, (end - start) + 1);
}
messages.add(message);
if (listener != null) {
listener.messageFinished(message, i++, (end - start) + 1);
}
}
return messages.toArray(new Message[messages.size()]);
}
/**
* Ensures that the given message set (from start to end inclusive)
* has been queried so that uids are available in the local cache.
* @param start
* @param end
* @throws MessagingException
* @throws IOException
*/
private void indexMsgNums(int start, int end)
throws MessagingException, IOException {
int unindexedMessageCount = 0;
for (int msgNum = start; msgNum <= end; msgNum++) {
if (mMsgNumToMsgMap.get(msgNum) == null) {
unindexedMessageCount++;
}
}
if (unindexedMessageCount == 0) {
return;
}
if (unindexedMessageCount < 50 && mMessageCount > 5000) {
/*
* In extreme cases we'll do a UIDL command per message instead of a bulk
* download.
*/
for (int msgNum = start; msgNum <= end; msgNum++) {
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
if (message == null) {
String response = executeSimpleCommand("UIDL " + msgNum);
int uidIndex = response.lastIndexOf(' ');
String msgUid = response.substring(uidIndex + 1);
message = new Pop3Message(msgUid, this);
indexMessage(msgNum, message);
}
}
}
else {
String response = executeSimpleCommand("UIDL");
while ((response = readLine()) != null) {
if (response.equals(".")) {
break;
}
String[] uidParts = response.split(" ");
Integer msgNum = Integer.valueOf(uidParts[0]);
String msgUid = uidParts[1];
if (msgNum >= start && msgNum <= end) {
Pop3Message message = mMsgNumToMsgMap.get(msgNum);
if (message == null) {
message = new Pop3Message(msgUid, this);
indexMessage(msgNum, message);
}
}
}
}
}
private void indexUids(ArrayList<String> uids)
throws MessagingException, IOException {
HashSet<String> unindexedUids = new HashSet<String>();
for (String uid : uids) {
if (mUidToMsgMap.get(uid) == null) {
unindexedUids.add(uid);
}
}
if (unindexedUids.size() == 0) {
return;
}
/*
* If we are missing uids in the cache the only sure way to
* get them is to do a full UIDL list. A possible optimization
* would be trying UIDL for the latest X messages and praying.
*/
String response = executeSimpleCommand("UIDL");
while ((response = readLine()) != null) {
if (response.equals(".")) {
break;
}
String[] uidParts = response.split(" ");
Integer msgNum = Integer.valueOf(uidParts[0]);
String msgUid = uidParts[1];
if (unindexedUids.contains(msgUid)) {
if (Config.LOGD) {
Pop3Message message = mUidToMsgMap.get(msgUid);
if (message == null) {
message = new Pop3Message(msgUid, this);
}
indexMessage(msgNum, message);
}
}
}
}
private void indexMessage(int msgNum, Pop3Message message) {
mMsgNumToMsgMap.put(msgNum, message);
mUidToMsgMap.put(message.getUid(), message);
mUidToMsgNumMap.put(message.getUid(), msgNum);
}
@Override
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
throw new UnsupportedOperationException("Pop3Folder.getMessage(MessageRetrievalListener)");
}
@Override
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException {
throw new UnsupportedOperationException("Pop3Folder.getMessage(MessageRetrievalListener)");
}
/**
* Fetch the items contained in the FetchProfile into the given set of
* Messages in as efficient a manner as possible.
* @param messages
* @param fp
* @throws MessagingException
*/
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
throws MessagingException {
if (messages == null || messages.length == 0) {
return;
}
ArrayList<String> uids = new ArrayList<String>();
for (Message message : messages) {
uids.add(message.getUid());
}
try {
indexUids(uids);
}
catch (IOException ioe) {
throw new MessagingException("fetch", ioe);
}
try {
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
/*
* We pass the listener only if there are other things to do in the
* FetchProfile. Since fetchEnvelop works in bulk and eveything else
* works one at a time if we let fetchEnvelope send events the
* event would get sent twice.
*/
fetchEnvelope(messages, fp.size() == 1 ? listener : null);
}
}
catch (IOException ioe) {
throw new MessagingException("fetch", ioe);
}
for (int i = 0, count = messages.length; i < count; i++) {
Message message = messages[i];
if (!(message instanceof Pop3Message)) {
throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
}
Pop3Message pop3Message = (Pop3Message)message;
try {
if (listener != null && !fp.contains(FetchProfile.Item.ENVELOPE)) {
listener.messageStarted(pop3Message.getUid(), i, count);
}
if (fp.contains(FetchProfile.Item.BODY)) {
fetchBody(pop3Message, -1);
}
else if (fp.contains(FetchProfile.Item.BODY_SANE)) {
/*
* To convert the suggested download size we take the size
* divided by the maximum line size (76).
*/
fetchBody(pop3Message,
FETCH_BODY_SANE_SUGGESTED_SIZE / 76);
}
else if (fp.contains(FetchProfile.Item.STRUCTURE)) {
/*
* If the user is requesting STRUCTURE we are required to set the body
* to null since we do not support the function.
*/
pop3Message.setBody(null);
}
if (listener != null && !fp.contains(FetchProfile.Item.ENVELOPE)) {
listener.messageFinished(message, i, count);
}
} catch (IOException ioe) {
throw new MessagingException("Unable to fetch message", ioe);
}
}
}
private void fetchEnvelope(Message[] messages,
MessageRetrievalListener listener) throws IOException, MessagingException {
int unsizedMessages = 0;
for (Message message : messages) {
if (message.getSize() == -1) {
unsizedMessages++;
}
}
if (unsizedMessages == 0) {
return;
}
if (unsizedMessages < 50 && mMessageCount > 5000) {
/*
* In extreme cases we'll do a command per message instead of a bulk request
* to hopefully save some time and bandwidth.
*/
for (int i = 0, count = messages.length; i < count; i++) {
Message message = messages[i];
if (!(message instanceof Pop3Message)) {
throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
}
Pop3Message pop3Message = (Pop3Message)message;
if (listener != null) {
listener.messageStarted(pop3Message.getUid(), i, count);
}
String response = executeSimpleCommand(String.format("LIST %d",
mUidToMsgNumMap.get(pop3Message.getUid())));
String[] listParts = response.split(" ");
int msgNum = Integer.parseInt(listParts[1]);
int msgSize = Integer.parseInt(listParts[2]);
pop3Message.setSize(msgSize);
if (listener != null) {
listener.messageFinished(pop3Message, i, count);
}
}
}
else {
HashSet<String> msgUidIndex = new HashSet<String>();
for (Message message : messages) {
msgUidIndex.add(message.getUid());
}
int i = 0, count = messages.length;
String response = executeSimpleCommand("LIST");
while ((response = readLine()) != null) {
if (response.equals(".")) {
break;
}
String[] listParts = response.split(" ");
int msgNum = Integer.parseInt(listParts[0]);
int msgSize = Integer.parseInt(listParts[1]);
Pop3Message pop3Message = mMsgNumToMsgMap.get(msgNum);
if (pop3Message != null && msgUidIndex.contains(pop3Message.getUid())) {
if (listener != null) {
listener.messageStarted(pop3Message.getUid(), i, count);
}
pop3Message.setSize(msgSize);
if (listener != null) {
listener.messageFinished(pop3Message, i, count);
}
i++;
}
}
}
}
/**
* Fetches the body of the given message, limiting the stored data
* to the specified number of lines. If lines is -1 the entire message
* is fetched. This is implemented with RETR for lines = -1 or TOP
* for any other value. If the server does not support TOP it is
* emulated with RETR and extra lines are thrown away.
* @param message
* @param lines
*/
private void fetchBody(Pop3Message message, int lines)
throws IOException, MessagingException {
String response = null;
if (lines == -1 || !mCapabilities.top) {
response = executeSimpleCommand(String.format("RETR %d",
mUidToMsgNumMap.get(message.getUid())));
}
else {
response = executeSimpleCommand(String.format("TOP %d %d",
mUidToMsgNumMap.get(message.getUid()),
lines));
}
if (response != null) {
try {
message.parse(new Pop3ResponseInputStream(mIn));
}
catch (MessagingException me) {
/*
* If we're only downloading headers it's possible
* we'll get a broken MIME message which we're not
* real worried about. If we've downloaded the body
* and can't parse it we need to let the user know.
*/
if (lines == -1) {
throw me;
}
}
}
}
@Override
public Flag[] getPermanentFlags() throws MessagingException {
return PERMANENT_FLAGS;
}
public void appendMessages(Message[] messages) throws MessagingException {
}
public void delete(boolean recurse) throws MessagingException {
}
public Message[] expunge() throws MessagingException {
return null;
}
public void setFlags(Message[] messages, Flag[] flags, boolean value)
throws MessagingException {
if (!value || !Utility.arrayContains(flags, Flag.DELETED)) {
/*
* The only flagging we support is setting the Deleted flag.
*/
return;
}
try {
for (Message message : messages) {
executeSimpleCommand(String.format("DELE %s",
mUidToMsgNumMap.get(message.getUid())));
}
}
catch (IOException ioe) {
throw new MessagingException("setFlags()", ioe);
}
}
@Override
public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {
throw new UnsupportedOperationException("copyMessages is not supported in POP3");
}
// private boolean isRoundTripModeSuggested() {
// long roundTripMethodMs =
// (uncachedMessageCount * 2 * mLatencyMs);
// long bulkMethodMs =
// (mMessageCount * 58) / (mThroughputKbS * 1024 / 8) * 1000;
// }
private String readLine() throws IOException {
StringBuffer sb = new StringBuffer();
int d = mIn.read();
if (d == -1) {
throw new IOException("End of stream reached while trying to read line.");
}
do {
if (((char)d) == '\r') {
continue;
} else if (((char)d) == '\n') {
break;
} else {
sb.append((char)d);
}
} while ((d = mIn.read()) != -1);
String ret = sb.toString();
if (Config.LOGD) {
if (k9.DEBUG) {
Log.d(k9.LOG_TAG, "<<< " + ret);
}
}
return ret;
}
private void writeLine(String s) throws IOException {
if (Config.LOGD) {
if (k9.DEBUG) {
Log.d(k9.LOG_TAG, ">>> " + s);
}
}
mOut.write(s.getBytes());
mOut.write('\r');
mOut.write('\n');
mOut.flush();
}
private Pop3Capabilities getCapabilities() throws IOException, MessagingException {
Pop3Capabilities capabilities = new Pop3Capabilities();
try {
String response = executeSimpleCommand("CAPA");
while ((response = readLine()) != null) {
if (response.equals(".")) {
break;
}
if (response.equalsIgnoreCase("STLS")){
capabilities.stls = true;
}
else if (response.equalsIgnoreCase("UIDL")) {
capabilities.uidl = true;
}
else if (response.equalsIgnoreCase("PIPELINING")) {
capabilities.pipelining = true;
}
else if (response.equalsIgnoreCase("USER")) {
capabilities.user = true;
}
else if (response.equalsIgnoreCase("TOP")) {
capabilities.top = true;
}
}
}
catch (MessagingException me) {
/*
* The server may not support the CAPA command, so we just eat this Exception
* and allow the empty capabilities object to be returned.
*/
}
return capabilities;
}
private String executeSimpleCommand(String command) throws IOException, MessagingException {
try {
open(OpenMode.READ_WRITE);
if (command != null) {
writeLine(command);
}
String response = readLine();
if (response.length() > 1 && response.charAt(0) == '-') {
throw new MessagingException(response);
}
return response;
}
catch (IOException e) {
closeIO();
throw e;
}
}
@Override
public boolean supportsFetchingFlags() {
return false;
}//isFlagSupported
@Override
public boolean equals(Object o) {
if (o instanceof Pop3Folder) {
return ((Pop3Folder) o).mName.equals(mName);
}
return super.equals(o);
}
}//Pop3Folder
class Pop3Message extends MimeMessage {
public Pop3Message(String uid, Pop3Folder folder) throws MessagingException {
mUid = uid;
mFolder = folder;
mSize = -1;
}
public void setSize(int size) {
mSize = size;
}
protected void parse(InputStream in) throws IOException, MessagingException {
super.parse(in);
}
@Override
public void setFlag(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set);
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
}
}
class Pop3Capabilities {
public boolean stls;
public boolean top;
public boolean user;
public boolean uidl;
public boolean pipelining;
public String toString() {
return String.format("STLS %b, TOP %b, USER %b, UIDL %b, PIPELINING %b",
stls,
top,
user,
uidl,
pipelining);
}
}
class Pop3ResponseInputStream extends InputStream {
InputStream mIn;
boolean mStartOfLine = true;
boolean mFinished;
public Pop3ResponseInputStream(InputStream in) {
mIn = in;
}
@Override
public int read() throws IOException {
if (mFinished) {
return -1;
}
int d = mIn.read();
if (mStartOfLine && d == '.') {
d = mIn.read();
if (d == '\r') {
mFinished = true;
mIn.read();
return -1;
}
}
mStartOfLine = (d == '\n');
return d;
}
}
}

View File

@ -1,213 +0,0 @@
package com.fsck.k9.mail.store;
import android.util.Log;
import android.app.Application;
import android.content.Context;
import android.net.http.DomainNameChecker;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.KeyStoreException;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.TrustManager;
import com.fsck.k9.k9;
public final class TrustManagerFactory {
private static final String LOG_TAG = "TrustManagerFactory";
private static X509TrustManager defaultTrustManager;
private static X509TrustManager unsecureTrustManager;
private static X509TrustManager localTrustManager;
private static SecureX509TrustManager secureTrustManager;
private static X509Certificate[] lastCertChain = null;
private static File keyStoreFile;
private static KeyStore keyStore;
private static class SimpleX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
private static class SecureX509TrustManager implements X509TrustManager {
private static String mHost;
private static SecureX509TrustManager me;
private SecureX509TrustManager() {
}
public static X509TrustManager getInstance(String host) {
mHost = host;
if (me == null) {
me = new SecureX509TrustManager();
}
return me;
}
public static void setHost(String host){
mHost = host;
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
defaultTrustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
TrustManagerFactory.setLastCertChain(chain);
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
localTrustManager.checkServerTrusted(new X509Certificate[] {chain[0]}, authType);
}
if (!DomainNameChecker.match(chain[0], mHost)) {
try {
String dn = chain[0].getSubjectDN().toString();
if ((dn != null) && (dn.equalsIgnoreCase(keyStore.getCertificateAlias(chain[0])))) {
return;
}
} catch (KeyStoreException e) {
throw new CertificateException("Certificate cannot be verified; KeyStore Exception: " + e);
}
throw new CertificateException("Certificate domain name does not match "
+ mHost);
}
}
public X509Certificate[] getAcceptedIssuers() {
return defaultTrustManager.getAcceptedIssuers();
}
}
static {
try {
javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509");
Application app = k9.app;
keyStoreFile = new File(app.getDir("KeyStore", Context.MODE_PRIVATE) + File.separator + "KeyStore.bks");
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
//TODO: read store from disk.
java.io.FileInputStream fis;
try {
fis = new java.io.FileInputStream(keyStoreFile);
} catch (FileNotFoundException e1) {
fis = null;
}
try {
keyStore.load(fis, "".toCharArray());
} catch (IOException e) {
Log.e(LOG_TAG, "KeyStore IOException while initializing TrustManagerFactory ", e);
keyStore = null;
} catch (CertificateException e) {
Log.e(LOG_TAG, "KeyStore CertificateException while initializing TrustManagerFactory ", e);
keyStore = null;
}
tmf.init(keyStore);
TrustManager[] tms = tmf.getTrustManagers();
if (tms != null) {
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
localTrustManager = (X509TrustManager)tm;
break;
}
}
}
tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509");
tmf.init((KeyStore)null);
tms = tmf.getTrustManagers();
if (tms != null) {
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
defaultTrustManager = (X509TrustManager) tm;
break;
}
}
}
} catch (NoSuchAlgorithmException e) {
Log.e(LOG_TAG, "Unable to get X509 Trust Manager ", e);
} catch (KeyStoreException e) {
Log.e(LOG_TAG, "Key Store exception while initializing TrustManagerFactory ", e);
}
unsecureTrustManager = new SimpleX509TrustManager();
}
private TrustManagerFactory() {
}
public static X509TrustManager get(String host, boolean secure) {
return secure ? SecureX509TrustManager.getInstance(host) :
unsecureTrustManager;
}
public static KeyStore getKeyStore() {
return keyStore;
}
public static void setLastCertChain(X509Certificate[] chain) {
lastCertChain = chain;
}
public static X509Certificate[] getLastCertChain() {
return lastCertChain;
}
public static void addCertificateChain(String alias, X509Certificate[] chain) throws CertificateException {
try {
javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509");
for (int i = 0; i < chain.length; i++)
{
keyStore.setCertificateEntry
(chain[i].getSubjectDN().toString(), chain[i]);
}
tmf.init(keyStore);
TrustManager[] tms = tmf.getTrustManagers();
if (tms != null) {
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
localTrustManager = (X509TrustManager) tm;
break;
}
}
}
java.io.FileOutputStream keyStoreStream;
try {
keyStoreStream = new java.io.FileOutputStream(keyStoreFile);
keyStore.store(keyStoreStream, "".toCharArray());
keyStoreStream.close();
} catch (FileNotFoundException e) {
throw new CertificateException("Unable to write KeyStore: " + e.getMessage());
} catch (CertificateException e) {
throw new CertificateException("Unable to write KeyStore: " + e.getMessage());
} catch (IOException e) {
throw new CertificateException("Unable to write KeyStore: " + e.getMessage());
}
} catch (NoSuchAlgorithmException e) {
Log.e(LOG_TAG, "Unable to get X509 Trust Manager ", e);
} catch (KeyStoreException e) {
Log.e(LOG_TAG, "Key Store exception while initializing TrustManagerFactory ", e);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
package com.fsck.k9.mail.transport;
import java.io.IOException;
import java.io.OutputStream;
/**
* A simple OutputStream that does nothing but count how many bytes are written to it and
* makes that count available to callers.
*/
public class CountingOutputStream extends OutputStream {
private long mCount;
public CountingOutputStream() {
}
public long getCount() {
return mCount;
}
@Override
public void write(int oneByte) throws IOException {
mCount++;
}
}

View File

@ -1,33 +0,0 @@
package com.fsck.k9.mail.transport;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class EOLConvertingOutputStream extends FilterOutputStream {
int lastChar;
public EOLConvertingOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int oneByte) throws IOException {
if (oneByte == '\n') {
if (lastChar != '\r') {
super.write('\r');
}
}
super.write(oneByte);
lastChar = oneByte;
}
@Override
public void flush() throws IOException {
if (lastChar == '\r') {
super.write('\n');
lastChar = '\n';
}
super.flush();
}
}

View File

@ -1,376 +0,0 @@
package com.fsck.k9.mail.transport;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.SSLException;
import android.util.Config;
import android.util.Log;
import com.fsck.k9.k9;
import com.fsck.k9.PeekableInputStream;
import com.fsck.k9.codec.binary.Base64;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.store.TrustManagerFactory;
public class SmtpTransport extends Transport {
public static final int CONNECTION_SECURITY_NONE = 0;
public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
String mHost;
int mPort;
String mUsername;
String mPassword;
int mConnectionSecurity;
boolean mSecure;
Socket mSocket;
PeekableInputStream mIn;
OutputStream mOut;
/**
* smtp://user:password@server:port CONNECTION_SECURITY_NONE
* smtp+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
* smtp+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
* smtp+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
* smtp+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
*
* @param _uri
*/
public SmtpTransport(String _uri) throws MessagingException {
URI uri;
try {
uri = new URI(_uri);
} catch (URISyntaxException use) {
throw new MessagingException("Invalid SmtpTransport URI", use);
}
String scheme = uri.getScheme();
if (scheme.equals("smtp")) {
mConnectionSecurity = CONNECTION_SECURITY_NONE;
mPort = 25;
} else if (scheme.equals("smtp+tls")) {
mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL;
mPort = 25;
} else if (scheme.equals("smtp+tls+")) {
mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED;
mPort = 25;
} else if (scheme.equals("smtp+ssl+")) {
mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED;
mPort = 465;
} else if (scheme.equals("smtp+ssl")) {
mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL;
mPort = 465;
} else {
throw new MessagingException("Unsupported protocol");
}
mHost = uri.getHost();
if (uri.getPort() != -1) {
mPort = uri.getPort();
}
if (uri.getUserInfo() != null) {
String[] userInfoParts = uri.getUserInfo().split(":", 2);
mUsername = userInfoParts[0];
if (userInfoParts.length > 1) {
mPassword = userInfoParts[1];
}
}
}
public void open() throws MessagingException {
try {
SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mHost, secure)
}, new SecureRandom());
mSocket = sslContext.getSocketFactory().createSocket();
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
mSecure = true;
} else {
mSocket = new Socket();
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
}
mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), 1024));
mOut = mSocket.getOutputStream();
// Eat the banner
executeSimpleCommand(null);
String localHost = "localhost.localdomain";
try {
InetAddress localAddress = InetAddress.getLocalHost();
if (! localAddress.isLoopbackAddress()) {
// The loopback address will resolve to 'localhost'
// some mail servers only accept qualified hostnames, so make sure
// never to override "localhost.localdomain" with "localhost"
// TODO - this is a hack. but a better hack than what was there before
localHost = localAddress.getHostName();
}
} catch (Exception e) {
if (Config.LOGD) {
if (k9.DEBUG) {
Log.d(k9.LOG_TAG, "Unable to look up localhost");
}
}
}
String result = executeSimpleCommand("EHLO " + localHost);
/*
* TODO may need to add code to fall back to HELO I switched it from
* using HELO on non STARTTLS connections because of AOL's mail
* server. It won't let you use AUTH without EHLO.
* We should really be paying more attention to the capabilities
* and only attempting auth if it's available, and warning the user
* if not.
*/
if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL
|| mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) {
if (result.contains("-STARTTLS")) {
executeSimpleCommand("STARTTLS");
SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mHost, secure)
}, new SecureRandom());
mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort,
true);
mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(),
1024));
mOut = mSocket.getOutputStream();
mSecure = true;
/*
* Now resend the EHLO. Required by RFC2487 Sec. 5.2, and more specifically,
* Exim.
*/
result = executeSimpleCommand("EHLO " + localHost);
} else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) {
throw new MessagingException("TLS not supported but required");
}
}
/*
* result contains the results of the EHLO in concatenated form
*/
boolean authLoginSupported = result.matches(".*AUTH.*LOGIN.*$");
boolean authPlainSupported = result.matches(".*AUTH.*PLAIN.*$");
if (mUsername != null && mUsername.length() > 0 && mPassword != null
&& mPassword.length() > 0) {
if (authPlainSupported) {
saslAuthPlain(mUsername, mPassword);
}
else if (authLoginSupported) {
saslAuthLogin(mUsername, mPassword);
}
else {
throw new MessagingException("No valid authentication mechanism found.");
}
}
} catch (SSLException e) {
throw new CertificateValidationException(e.getMessage(), e);
} catch (GeneralSecurityException gse) {
throw new MessagingException(
"Unable to open connection to SMTP server due to security error.", gse);
} catch (IOException ioe) {
throw new MessagingException("Unable to open connection to SMTP server.", ioe);
}
}
public void sendMessage(Message message) throws MessagingException {
close();
open();
Address[] from = message.getFrom();
try {
executeSimpleCommand("MAIL FROM: " + "<" + from[0].getAddress() + ">");
for (Address address : message.getRecipients(RecipientType.TO)) {
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
}
for (Address address : message.getRecipients(RecipientType.CC)) {
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
}
for (Address address : message.getRecipients(RecipientType.BCC)) {
executeSimpleCommand("RCPT TO: " + "<" + address.getAddress() + ">");
}
message.setRecipients(RecipientType.BCC, null);
executeSimpleCommand("DATA");
// TODO byte stuffing
message.writeTo(
new EOLConvertingOutputStream(
new BufferedOutputStream(mOut, 1024)));
executeSimpleCommand("\r\n.");
} catch (IOException ioe) {
throw new MessagingException("Unable to send message", ioe);
}
}
public void close() {
try {
mIn.close();
} catch (Exception e) {
}
try {
mOut.close();
} catch (Exception e) {
}
try {
mSocket.close();
} catch (Exception e) {
}
mIn = null;
mOut = null;
mSocket = null;
}
private String readLine() throws IOException {
StringBuffer sb = new StringBuffer();
int d;
while ((d = mIn.read()) != -1) {
if (((char)d) == '\r') {
continue;
} else if (((char)d) == '\n') {
break;
} else {
sb.append((char)d);
}
}
String ret = sb.toString();
if (Config.LOGD) {
if (k9.DEBUG) {
Log.d(k9.LOG_TAG, "<<< " + ret);
}
}
return ret;
}
private void writeLine(String s) throws IOException {
if (Config.LOGD) {
if (k9.DEBUG) {
Log.d(k9.LOG_TAG, ">>> " + s);
}
}
mOut.write(s.getBytes());
mOut.write('\r');
mOut.write('\n');
mOut.flush();
}
private String executeSimpleCommand(String command) throws IOException, MessagingException {
if (command != null) {
writeLine(command);
}
String line = readLine();
String result = line;
while (line.length() >= 4 && line.charAt(3) == '-') {
line = readLine();
result += line.substring(3);
}
char c = result.charAt(0);
if ((c == '4') || (c == '5')) {
throw new MessagingException(result);
}
return result;
}
// C: AUTH LOGIN
// S: 334 VXNlcm5hbWU6
// C: d2VsZG9u
// S: 334 UGFzc3dvcmQ6
// C: dzNsZDBu
// S: 235 2.0.0 OK Authenticated
//
// Lines 2-5 of the conversation contain base64-encoded information. The same conversation, with base64 strings decoded, reads:
//
//
// C: AUTH LOGIN
// S: 334 Username:
// C: weldon
// S: 334 Password:
// C: w3ld0n
// S: 235 2.0.0 OK Authenticated
private void saslAuthLogin(String username, String password) throws MessagingException,
AuthenticationFailedException, IOException {
try {
executeSimpleCommand("AUTH LOGIN");
executeSimpleCommand(new String(Base64.encodeBase64(username.getBytes())));
executeSimpleCommand(new String(Base64.encodeBase64(password.getBytes())));
}
catch (MessagingException me) {
if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3') {
throw new AuthenticationFailedException("AUTH LOGIN failed (" + me.getMessage()
+ ")");
}
throw me;
}
}
private void saslAuthPlain(String username, String password) throws MessagingException,
AuthenticationFailedException, IOException {
byte[] data = ("\000" + username + "\000" + password).getBytes();
data = new Base64().encode(data);
try {
executeSimpleCommand("AUTH PLAIN " + new String(data));
}
catch (MessagingException me) {
if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3') {
throw new AuthenticationFailedException("AUTH PLAIN failed (" + me.getMessage()
+ ")");
}
throw me;
}
}
}

View File

@ -1,29 +0,0 @@
package com.fsck.k9.mail.transport;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import com.fsck.k9.k9;
import android.util.Config;
import android.util.Log;
public class StatusOutputStream extends FilterOutputStream {
private long mCount = 0;
public StatusOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int oneByte) throws IOException {
super.write(oneByte);
mCount++;
if (Config.LOGV) {
if (mCount % 1024 == 0) {
Log.v(k9.LOG_TAG, "# " + mCount);
}
}
}
}

View File

@ -1,189 +0,0 @@
package com.fsck.k9.mail.transport;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.SSLException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import android.util.Config;
import android.util.Log;
import com.fsck.k9.k9;
import com.fsck.k9.PeekableInputStream;
import com.fsck.k9.codec.binary.Base64;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.store.TrustManagerFactory;
import com.fsck.k9.mail.store.WebDavStore;
import com.fsck.k9.mail.store.WebDavStore.HttpGeneric;
import com.fsck.k9.mail.store.WebDavStore.ParsedDataSet;
import com.fsck.k9.mail.store.WebDavStore.WebDavHandler;
public class WebDavTransport extends Transport {
public static final int CONNECTION_SECURITY_NONE = 0;
public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
String host;
int mPort;
private int mConnectionSecurity;
private String mUsername; /* Stores the username for authentications */
private String mPassword; /* Stores the password for authentications */
private String mUrl; /* Stores the base URL for the server */
boolean mSecure;
Socket mSocket;
PeekableInputStream mIn;
OutputStream mOut;
private WebDavStore store;
/**
* webdav://user:password@server:port CONNECTION_SECURITY_NONE
* webdav+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
* webdav+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
* webdav+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
* webdav+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
*
* @param _uri
*/
public WebDavTransport(String _uri) throws MessagingException {
store = new WebDavStore(_uri);
Log.d(k9.LOG_TAG, ">>> New WebDavTransport creation complete");
}
public void open() throws MessagingException {
Log.d(k9.LOG_TAG, ">>> open called on WebDavTransport ");
if (store.needAuth()) {
store.authenticate();
}
if (store.getAuthCookies() == null) {
return;
}
}
// public void sendMessage(Message message) throws MessagingException {
// Address[] from = message.getFrom();
//
// }
public void close() {
}
public String generateTempURI(String subject) {
String encodedSubject = URLEncoder.encode(subject);
return store.getUrl() + "/Exchange/" + store.getAlias() + "/drafts/" + encodedSubject + ".eml";
}
public String generateSendURI() {
return store.getUrl() + "/Exchange/" + store.getAlias() + "/##DavMailSubmissionURI##/";
}
public void sendMessage(Message message) throws MessagingException {
Log.d(k9.LOG_TAG, ">>> sendMessage called.");
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGeneric httpmethod;
HttpResponse response;
HttpEntity responseEntity;
StringEntity bodyEntity;
int statusCode;
String subject;
ByteArrayOutputStream out;
try {
try {
subject = message.getSubject();
} catch (MessagingException e) {
Log.e(k9.LOG_TAG, "MessagingException while retrieving Subject: " + e);
subject = "";
}
try {
out = new ByteArrayOutputStream(message.getSize());
} catch (MessagingException e) {
Log.e(k9.LOG_TAG, "MessagingException while getting size of message: " + e);
out = new ByteArrayOutputStream();
}
open();
message.writeTo(
new EOLConvertingOutputStream(
new BufferedOutputStream(out, 1024)));
httpclient.setCookieStore(store.getAuthCookies());
bodyEntity = new StringEntity(out.toString(), "UTF-8");
bodyEntity.setContentType("message/rfc822");
httpmethod = store.new HttpGeneric(generateTempURI(subject));
httpmethod.setMethod("PUT");
httpmethod.setEntity(bodyEntity);
response = httpclient.execute(httpmethod);
statusCode = response.getStatusLine().getStatusCode();
if (statusCode < 200 ||
statusCode > 300) {
throw new IOException("Error sending message, status code was " + statusCode);
}
//responseEntity = response.getEntity();
//DefaultHttpClient movehttpclient = new DefaultHttpClient();
//HttpGeneric movehttpmethod;
//HttpResponse moveresponse;
//HttpEntity moveresponseEntity;
httpmethod = store.new HttpGeneric(generateTempURI(subject));
httpmethod.setMethod("MOVE");
httpmethod.setHeader("Destination", generateSendURI());
response = httpclient.execute(httpmethod);
statusCode = response.getStatusLine().getStatusCode();
if (statusCode < 200 ||
statusCode > 300) {
throw new IOException("Error sending message, status code was " + statusCode);
}
} catch (UnsupportedEncodingException uee) {
Log.e(k9.LOG_TAG, "UnsupportedEncodingException in getMessageCount() " + uee);
} catch (IOException ioe) {
Log.e(k9.LOG_TAG, "IOException in getMessageCount() " + ioe);
throw new MessagingException("Unable to send message", ioe);
}
Log.d(k9.LOG_TAG, ">>> getMessageCount finished");
}
}

View File

@ -1,272 +0,0 @@
package com.fsck.k9.provider;
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.util.List;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.util.Config;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.Utility;
import com.fsck.k9.mail.internet.MimeUtility;
/*
* A simple ContentProvider that allows file access to Email's attachments.
*/
public class AttachmentProvider extends ContentProvider {
public static final Uri CONTENT_URI = Uri.parse( "content://com.fsck.k9.attachmentprovider");
private static final String FORMAT_RAW = "RAW";
private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
public static class AttachmentProviderColumns {
public static final String _ID = "_id";
public static final String DATA = "_data";
public static final String DISPLAY_NAME = "_display_name";
public static final String SIZE = "_size";
}
public static Uri getAttachmentUri(Account account, long id) {
return CONTENT_URI.buildUpon()
.appendPath(account.getUuid() + ".db")
.appendPath(Long.toString(id))
.appendPath(FORMAT_RAW)
.build();
}
public static Uri getAttachmentThumbnailUri(Account account, long id, int width, int height) {
return CONTENT_URI.buildUpon()
.appendPath(account.getUuid() + ".db")
.appendPath(Long.toString(id))
.appendPath(FORMAT_THUMBNAIL)
.appendPath(Integer.toString(width))
.appendPath(Integer.toString(height))
.build();
}
public static Uri getAttachmentUri(String db, long id) {
return CONTENT_URI.buildUpon()
.appendPath(db)
.appendPath(Long.toString(id))
.appendPath(FORMAT_RAW)
.build();
}
@Override
public boolean onCreate() {
/*
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
* on startup we'll clean up any .tmp files from the last run.
*/
File[] files = getContext().getCacheDir().listFiles();
for (File file : files) {
if (file.getName().endsWith(".tmp")) {
file.delete();
}
}
return true;
}
@Override
public String getType(Uri uri) {
List<String> segments = uri.getPathSegments();
String dbName = segments.get(0);
String id = segments.get(1);
String format = segments.get(2);
if (FORMAT_THUMBNAIL.equals(format)) {
return "image/png";
}
else {
String path = getContext().getDatabasePath(dbName).getAbsolutePath();
SQLiteDatabase db = null;
Cursor cursor = null;
try {
db = SQLiteDatabase.openDatabase(path, null, 0);
cursor = db.query(
"attachments",
new String[] { "mime_type" },
"id = ?",
new String[] { id },
null,
null,
null);
cursor.moveToFirst();
String type = cursor.getString(0);
cursor.close();
db.close();
return type;
}
finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}
}
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
List<String> segments = uri.getPathSegments();
String dbName = segments.get(0);
String id = segments.get(1);
String format = segments.get(2);
if (FORMAT_THUMBNAIL.equals(format)) {
int width = Integer.parseInt(segments.get(3));
int height = Integer.parseInt(segments.get(4));
String filename = "thmb_" + dbName + "_" + id;
File dir = getContext().getCacheDir();
File file = new File(dir, filename);
if (!file.exists()) {
Uri attachmentUri = getAttachmentUri(dbName, Long.parseLong(id));
String type = getType(attachmentUri);
try {
FileInputStream in = new FileInputStream(
new File(getContext().getDatabasePath(dbName + "_att"), id));
Bitmap thumbnail = createThumbnail(type, in);
thumbnail = thumbnail.createScaledBitmap(thumbnail, width, height, true);
FileOutputStream out = new FileOutputStream(file);
thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
in.close();
}
catch (IOException ioe) {
return null;
}
}
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
else {
return ParcelFileDescriptor.open(
new File(getContext().getDatabasePath(dbName + "_att"), id),
ParcelFileDescriptor.MODE_READ_ONLY);
}
}
@Override
public int delete(Uri uri, String arg1, String[] arg2) {
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
if (projection == null) {
projection =
new String[] {
AttachmentProviderColumns._ID,
AttachmentProviderColumns.DATA,
};
}
List<String> segments = uri.getPathSegments();
String dbName = segments.get(0);
String id = segments.get(1);
String format = segments.get(2);
String path = getContext().getDatabasePath(dbName).getAbsolutePath();
String name = null;
int size = -1;
SQLiteDatabase db = null;
Cursor cursor = null;
try {
db = SQLiteDatabase.openDatabase(path, null, 0);
cursor = db.query(
"attachments",
new String[] { "name", "size" },
"id = ?",
new String[] { id },
null,
null,
null);
if (!cursor.moveToFirst()) {
return null;
}
name = cursor.getString(0);
size = cursor.getInt(1);
}
finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}
MatrixCursor ret = new MatrixCursor(projection);
Object[] values = new Object[projection.length];
for (int i = 0, count = projection.length; i < count; i++) {
String column = projection[i];
if (AttachmentProviderColumns._ID.equals(column)) {
values[i] = id;
}
else if (AttachmentProviderColumns.DATA.equals(column)) {
values[i] = uri.toString();
}
else if (AttachmentProviderColumns.DISPLAY_NAME.equals(column)) {
values[i] = name;
}
else if (AttachmentProviderColumns.SIZE.equals(column)) {
values[i] = size;
}
}
ret.addRow(values);
return ret;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
private Bitmap createThumbnail(String type, InputStream data) {
if(MimeUtility.mimeTypeMatches(type, "image/*")) {
return createImageThumbnail(data);
}
return null;
}
private Bitmap createImageThumbnail(InputStream data) {
try {
Bitmap bitmap = BitmapFactory.decodeStream(data);
return bitmap;
}
catch (OutOfMemoryError oome) {
/*
* Improperly downloaded images, corrupt bitmaps and the like can commonly
* cause OOME due to invalid allocation sizes. We're happy with a null bitmap in
* that case. If the system is really out of memory we'll know about it soon
* enough.
*/
return null;
}
catch (Exception e) {
return null;
}
}
}

View File

@ -1,22 +0,0 @@
package com.fsck.k9.service;
import com.fsck.k9.MessagingController;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class BootReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
MailService.actionReschedule(context);
}
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
MailService.actionCancel(context);
}
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
MailService.actionReschedule(context);
}
}
}

View File

@ -1,193 +0,0 @@
package com.fsck.k9.service;
import java.util.HashMap;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Config;
import android.util.Log;
import android.text.TextUtils;
import android.net.Uri;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.MessagingController;
import com.fsck.k9.MessagingListener;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.Accounts;
import com.fsck.k9.activity.FolderMessageList;
/**
*/
public class MailService extends Service {
private static final String ACTION_CHECK_MAIL = "com.fsck.k9.intent.action.MAIL_SERVICE_WAKEUP";
private static final String ACTION_RESCHEDULE = "com.fsck.k9.intent.action.MAIL_SERVICE_RESCHEDULE";
private static final String ACTION_CANCEL = "com.fsck.k9.intent.action.MAIL_SERVICE_CANCEL";
private Listener mListener = new Listener();
private int mStartId;
public static void actionReschedule(Context context) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESCHEDULE);
context.startService(i);
}
public static void actionCancel(Context context) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_CANCEL);
context.startService(i);
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
this.mStartId = startId;
MessagingController.getInstance(getApplication()).addListener(mListener);
if (ACTION_CHECK_MAIL.equals(intent.getAction())) {
if (Config.LOGV) {
Log.v(k9.LOG_TAG, "***** MailService *****: checking mail");
}
MessagingController.getInstance(getApplication()).checkMail(this, null, mListener);
}
else if (ACTION_CANCEL.equals(intent.getAction())) {
if (Config.LOGV) {
Log.v(k9.LOG_TAG, "***** MailService *****: cancel");
}
cancel();
stopSelf(startId);
}
else if (ACTION_RESCHEDULE.equals(intent.getAction())) {
if (Config.LOGV) {
Log.v(k9.LOG_TAG, "***** MailService *****: reschedule");
}
reschedule();
stopSelf(startId);
}
}
@Override
public void onDestroy() {
super.onDestroy();
MessagingController.getInstance(getApplication()).removeListener(mListener);
}
private void cancel() {
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent();
i.setClassName("com.fsck.k9", "com.fsck.k9.service.MailService");
i.setAction(ACTION_CHECK_MAIL);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
alarmMgr.cancel(pi);
}
private void reschedule() {
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent();
i.setClassName("com.fsck.k9", "com.fsck.k9.service.MailService");
i.setAction(ACTION_CHECK_MAIL);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
int shortestInterval = -1;
for (Account account : Preferences.getPreferences(this).getAccounts()) {
if (account.getAutomaticCheckIntervalMinutes() != -1
&& (account.getAutomaticCheckIntervalMinutes() < shortestInterval || shortestInterval == -1)) {
shortestInterval = account.getAutomaticCheckIntervalMinutes();
}
}
if (shortestInterval == -1) {
alarmMgr.cancel(pi);
}
else {
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()
+ (shortestInterval * (60 * 1000)), pi);
}
}
public IBinder onBind(Intent intent) {
return null;
}
class Listener extends MessagingListener {
HashMap<Account, Integer> accountsWithNewMail = new HashMap<Account, Integer>();
@Override
public void checkMailStarted(Context context, Account account) {
accountsWithNewMail.clear();
}
@Override
public void checkMailFailed(Context context, Account account, String reason) {
reschedule();
stopSelf(mStartId);
}
@Override
public void synchronizeMailboxFinished(
Account account,
String folder,
int totalMessagesInMailbox,
int numNewMessages) {
if (account.isNotifyNewMail() && numNewMessages > 0) {
accountsWithNewMail.put(account, numNewMessages);
}
}
@Override
public void checkMailFinished(Context context, Account account) {
NotificationManager notifMgr = (NotificationManager)context
.getSystemService(Context.NOTIFICATION_SERVICE);
if (accountsWithNewMail.size() > 0) {
Notification notif = new Notification(R.drawable.stat_notify_email_generic,
getString(R.string.notification_new_title), System.currentTimeMillis());
boolean vibrate = false;
String ringtone = null;
if (accountsWithNewMail.size() > 1) {
for (Account account1 : accountsWithNewMail.keySet()) {
if (account1.isVibrate()) vibrate = true;
if (account1.isNotifyRingtone()) ringtone = account1.getRingtone();
}
Intent i = new Intent(context, Accounts.class);
PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
getString(R.string.notification_new_multi_account_fmt,
accountsWithNewMail.size()), pi);
} else {
Account account1 = accountsWithNewMail.keySet().iterator().next();
int totalNewMails = accountsWithNewMail.get(account1);
Intent i = FolderMessageList.actionHandleAccountIntent(context, account1, k9.INBOX);
PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
getString(R.string.notification_new_one_account_fmt, totalNewMails,
account1.getDescription()), pi);
vibrate = account1.isVibrate();
if (account1.isNotifyRingtone()) ringtone = account1.getRingtone();
}
notif.defaults = Notification.DEFAULT_LIGHTS;
notif.sound = TextUtils.isEmpty(ringtone) ? null : Uri.parse(ringtone);
if (vibrate) {
notif.defaults |= Notification.DEFAULT_VIBRATE;
}
notifMgr.notify(1, notif);
}
reschedule();
stopSelf(mStartId);
}
}
}