mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-23 09:52:16 -05:00
merge extra crap
This commit is contained in:
parent
9cf84ee2c8
commit
d55e335f48
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
@ -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) {
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
|
||||
package com.fsck.k9.mail;
|
||||
|
||||
public abstract class BodyPart implements Part {
|
||||
protected Multipart mParent;
|
||||
|
||||
public Multipart getParent() {
|
||||
return mParent;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
@ -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++;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user