mirror of
https://github.com/moparisthebest/k-9
synced 2024-12-25 00:58:50 -05:00
Allow emoji input in subjects.
Signed-off-by: HIRANO Takahito <hiranotaka@zng.info>
This commit is contained in:
parent
2ec5a712a2
commit
580d19ec17
@ -362,6 +362,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
|
|||||||
mCcView = (MultiAutoCompleteTextView)findViewById(R.id.cc);
|
mCcView = (MultiAutoCompleteTextView)findViewById(R.id.cc);
|
||||||
mBccView = (MultiAutoCompleteTextView)findViewById(R.id.bcc);
|
mBccView = (MultiAutoCompleteTextView)findViewById(R.id.bcc);
|
||||||
mSubjectView = (EditText)findViewById(R.id.subject);
|
mSubjectView = (EditText)findViewById(R.id.subject);
|
||||||
|
mSubjectView.getInputExtras(true).putBoolean("allowEmoji", true);
|
||||||
|
|
||||||
EditText upperSignature = (EditText)findViewById(R.id.upper_signature);
|
EditText upperSignature = (EditText)findViewById(R.id.upper_signature);
|
||||||
EditText lowerSignature = (EditText)findViewById(R.id.lower_signature);
|
EditText lowerSignature = (EditText)findViewById(R.id.lower_signature);
|
||||||
|
189
src/com/fsck/k9/mail/internet/EncoderUtil.java
Normal file
189
src/com/fsck/k9/mail/internet/EncoderUtil.java
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
|
||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.apache.james.mime4j.util.CharsetUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static methods for encoding header field values. This includes encoded-words
|
||||||
|
* as defined in <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC 2047</a>
|
||||||
|
* or display-names of an e-mail address, for example.
|
||||||
|
*
|
||||||
|
* This class is copied from the org.apache.james.mime4j.decoder.EncoderUtil class. It's modified here in order to
|
||||||
|
* encode emoji characters in the Subject headers. The method to decode emoji depends on the MimeMessage class because
|
||||||
|
* it has to be determined with the sender address.
|
||||||
|
*/
|
||||||
|
public class EncoderUtil {
|
||||||
|
private static final BitSet Q_RESTRICTED_CHARS = initChars("=_?\"#$%&'(),.:;<>@[\\]^`{|}~");
|
||||||
|
|
||||||
|
private static final int MAX_USED_CHARACTERS = 50;
|
||||||
|
|
||||||
|
private static final String ENC_WORD_PREFIX = "=?";
|
||||||
|
private static final String ENC_WORD_SUFFIX = "?=";
|
||||||
|
|
||||||
|
private static final int ENCODED_WORD_MAX_LENGTH = 75; // RFC 2047
|
||||||
|
|
||||||
|
private static BitSet initChars(String specials) {
|
||||||
|
BitSet bs = new BitSet(128);
|
||||||
|
for (char ch = 33; ch < 127; ch++) {
|
||||||
|
if (specials.indexOf(ch) == -1) {
|
||||||
|
bs.set(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects one of the two encodings specified in RFC 2047.
|
||||||
|
*/
|
||||||
|
public enum Encoding {
|
||||||
|
/** The B encoding (identical to base64 defined in RFC 2045). */
|
||||||
|
B,
|
||||||
|
/** The Q encoding (similar to quoted-printable defined in RFC 2045). */
|
||||||
|
Q
|
||||||
|
}
|
||||||
|
|
||||||
|
private EncoderUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the specified text into an encoded word or a sequence of encoded
|
||||||
|
* words separated by space. The text is separated into a sequence of
|
||||||
|
* encoded words if it does not fit in a single one.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* text to encode.
|
||||||
|
* @param charset
|
||||||
|
* the Java charset that should be used to encode the specified
|
||||||
|
* string into a byte array. A suitable charset is detected
|
||||||
|
* automatically if this parameter is <code>null</code>.
|
||||||
|
* @return the encoded word (or sequence of encoded words if the given text
|
||||||
|
* does not fit in a single encoded word).
|
||||||
|
*/
|
||||||
|
public static String encodeEncodedWord(String text, Charset charset) {
|
||||||
|
if (text == null)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
|
||||||
|
if (charset == null)
|
||||||
|
charset = determineCharset(text);
|
||||||
|
|
||||||
|
String mimeCharset = MimeUtility.getExternalCharset(charset.name());
|
||||||
|
|
||||||
|
byte[] bytes = encode(text, charset);
|
||||||
|
|
||||||
|
Encoding encoding = determineEncoding(bytes);
|
||||||
|
|
||||||
|
if (encoding == Encoding.B) {
|
||||||
|
String prefix = ENC_WORD_PREFIX + mimeCharset + "?B?";
|
||||||
|
return encodeB(prefix, text, charset, bytes);
|
||||||
|
} else {
|
||||||
|
String prefix = ENC_WORD_PREFIX + mimeCharset + "?Q?";
|
||||||
|
return encodeQ(prefix, text, charset, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeB(String prefix, String text, Charset charset, byte[] bytes) {
|
||||||
|
int encodedLength = bEncodedLength(bytes);
|
||||||
|
|
||||||
|
int totalLength = prefix.length() + encodedLength
|
||||||
|
+ ENC_WORD_SUFFIX.length();
|
||||||
|
if (totalLength <= ENCODED_WORD_MAX_LENGTH) {
|
||||||
|
return prefix + org.apache.james.mime4j.codec.EncoderUtil.encodeB(bytes) + ENC_WORD_SUFFIX;
|
||||||
|
} else {
|
||||||
|
String part1 = text.substring(0, text.length() / 2);
|
||||||
|
byte[] bytes1 = encode(part1, charset);
|
||||||
|
String word1 = encodeB(prefix, part1, charset, bytes1);
|
||||||
|
|
||||||
|
String part2 = text.substring(text.length() / 2);
|
||||||
|
byte[] bytes2 = encode(part2, charset);
|
||||||
|
String word2 = encodeB(prefix, part2, charset, bytes2);
|
||||||
|
|
||||||
|
return word1 + " " + word2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int bEncodedLength(byte[] bytes) {
|
||||||
|
return (bytes.length + 2) / 3 * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeQ(String prefix, String text, Charset charset, byte[] bytes) {
|
||||||
|
int encodedLength = qEncodedLength(bytes);
|
||||||
|
|
||||||
|
int totalLength = prefix.length() + encodedLength
|
||||||
|
+ ENC_WORD_SUFFIX.length();
|
||||||
|
if (totalLength <= ENCODED_WORD_MAX_LENGTH) {
|
||||||
|
return prefix + org.apache.james.mime4j.codec.EncoderUtil.encodeQ(bytes, org.apache.james.mime4j.codec.EncoderUtil.Usage.WORD_ENTITY) + ENC_WORD_SUFFIX;
|
||||||
|
} else {
|
||||||
|
String part1 = text.substring(0, text.length() / 2);
|
||||||
|
byte[] bytes1 = encode(part1, charset);
|
||||||
|
String word1 = encodeQ(prefix, part1, charset, bytes1);
|
||||||
|
|
||||||
|
String part2 = text.substring(text.length() / 2);
|
||||||
|
byte[] bytes2 = encode(part2, charset);
|
||||||
|
String word2 = encodeQ(prefix, part2, charset, bytes2);
|
||||||
|
|
||||||
|
return word1 + " " + word2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int qEncodedLength(byte[] bytes) {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int idx = 0; idx < bytes.length; idx++) {
|
||||||
|
int v = bytes[idx] & 0xff;
|
||||||
|
if (v == 32) {
|
||||||
|
count++;
|
||||||
|
} else if (!Q_RESTRICTED_CHARS.get(v)) {
|
||||||
|
count += 3;
|
||||||
|
} else {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] encode(String text, Charset charset) {
|
||||||
|
ByteBuffer buffer = charset.encode(text);
|
||||||
|
byte[] bytes = new byte[buffer.limit()];
|
||||||
|
buffer.get(bytes);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Charset determineCharset(String text) {
|
||||||
|
// it is an important property of iso-8859-1 that it directly maps
|
||||||
|
// unicode code points 0000 to 00ff to byte values 00 to ff.
|
||||||
|
boolean ascii = true;
|
||||||
|
final int len = text.length();
|
||||||
|
for (int index = 0; index < len; index++) {
|
||||||
|
char ch = text.charAt(index);
|
||||||
|
if (ch > 0xff) {
|
||||||
|
return CharsetUtil.UTF_8;
|
||||||
|
}
|
||||||
|
if (ch > 0x7f) {
|
||||||
|
ascii = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ascii ? CharsetUtil.US_ASCII : CharsetUtil.ISO_8859_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Encoding determineEncoding(byte[] bytes) {
|
||||||
|
if (bytes.length == 0)
|
||||||
|
return Encoding.Q;
|
||||||
|
|
||||||
|
int qEncoded = 0;
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
int v = bytes[i] & 0xff;
|
||||||
|
if (v != 32 && !Q_RESTRICTED_CHARS.get(v)) {
|
||||||
|
qEncoded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int percentage = qEncoded * 100 / bytes.length;
|
||||||
|
return percentage > 30 ? Encoding.B : Encoding.Q;
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,12 @@
|
|||||||
package com.fsck.k9.mail.internet;
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
import com.fsck.k9.helper.Utility;
|
import com.fsck.k9.helper.Utility;
|
||||||
import org.apache.james.mime4j.codec.EncoderUtil;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class MimeHeader {
|
public class MimeHeader {
|
||||||
@ -37,6 +37,7 @@ public class MimeHeader {
|
|||||||
};
|
};
|
||||||
|
|
||||||
protected ArrayList<Field> mFields = new ArrayList<Field>();
|
protected ArrayList<Field> mFields = new ArrayList<Field>();
|
||||||
|
private String mCharset = null;
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
mFields.clear();
|
mFields.clear();
|
||||||
@ -100,10 +101,7 @@ public class MimeHeader {
|
|||||||
String v = field.value;
|
String v = field.value;
|
||||||
|
|
||||||
if (hasToBeEncoded(v)) {
|
if (hasToBeEncoded(v)) {
|
||||||
v = EncoderUtil.encodeEncodedWord(
|
v = EncoderUtil.encodeEncodedWord(field.value, Charset.forName(mCharset));
|
||||||
field.value,
|
|
||||||
EncoderUtil.Usage.WORD_ENTITY
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.write(field.name + ": " + v + "\r\n");
|
writer.write(field.name + ": " + v + "\r\n");
|
||||||
@ -143,4 +141,9 @@ public class MimeHeader {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCharset(String charset)
|
||||||
|
{
|
||||||
|
mCharset = charset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -420,6 +420,7 @@ public class MimeMessage extends Message {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCharset(String charset) throws MessagingException {
|
public void setCharset(String charset) throws MessagingException {
|
||||||
|
mHeader.setCharset(charset);
|
||||||
if (mBody instanceof Multipart) {
|
if (mBody instanceof Multipart) {
|
||||||
((Multipart)mBody).setCharset(charset);
|
((Multipart)mBody).setCharset(charset);
|
||||||
} else if (mBody instanceof TextBody) {
|
} else if (mBody instanceof TextBody) {
|
||||||
|
Loading…
Reference in New Issue
Block a user