Better handling for untyped attachments

* IMAP/POP rely on sender to set mime type of attachments
* Which doesn't always work, because senders don't always provide it
* Remap using filename extensions, when needed
* This is applied as late as possible - in the MessageView, and in
  the content provider getType().  No changes to how we write databases,
  and no change to existing attachment rows.

Bug: 2356638
Change-Id: Ie69e3fd12f406aac803583f9d1299a8af4fba010
This commit is contained in:
Andrew Stadler 2010-04-30 15:01:10 -07:00
parent 14812a50a8
commit 80ebde2897
3 changed files with 123 additions and 10 deletions

View File

@ -1004,15 +1004,11 @@ public class MessageView extends Activity implements OnClickListener {
AttachmentInfo attachmentInfo = new AttachmentInfo();
attachmentInfo.size = attachment.mSize;
attachmentInfo.contentType = attachment.mMimeType;
attachmentInfo.contentType =
AttachmentProvider.inferMimeType(attachment.mFileName, attachment.mMimeType);
attachmentInfo.name = attachment.mFileName;
attachmentInfo.attachmentId = attachment.mId;
// TODO: remove this when EAS writes mime types
if (attachmentInfo.contentType == null || attachmentInfo.contentType.length() == 0) {
attachmentInfo.contentType = "application/octet-stream";
}
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(R.layout.message_view_attachment, null);

View File

@ -34,6 +34,8 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
@ -75,8 +77,12 @@ public class AttachmentProvider extends ContentProvider {
public static final String SIZE = "_size";
}
private String[] PROJECTION_MIME_TYPE = new String[] { AttachmentColumns.MIME_TYPE };
private String[] PROJECTION_QUERY = new String[] { AttachmentColumns.FILENAME,
private static final String[] MIME_TYPE_PROJECTION = new String[] {
AttachmentColumns.MIME_TYPE, AttachmentColumns.FILENAME };
private static final int MIME_TYPE_COLUMN_MIME_TYPE = 0;
private static final int MIME_TYPE_COLUMN_FILENAME = 1;
private static final String[] PROJECTION_QUERY = new String[] { AttachmentColumns.FILENAME,
AttachmentColumns.SIZE, AttachmentColumns.CONTENT_URI };
public static Uri getAttachmentUri(long accountId, long id) {
@ -152,11 +158,14 @@ public class AttachmentProvider extends ContentProvider {
return "image/png";
} else {
uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
Cursor c = getContext().getContentResolver().query(uri, PROJECTION_MIME_TYPE,
Cursor c = getContext().getContentResolver().query(uri, MIME_TYPE_PROJECTION,
null, null, null);
try {
if (c.moveToFirst()) {
return c.getString(0);
String mimeType = c.getString(MIME_TYPE_COLUMN_MIME_TYPE);
String fileName = c.getString(MIME_TYPE_COLUMN_FILENAME);
mimeType = inferMimeType(fileName, mimeType);
return mimeType;
}
} finally {
c.close();
@ -165,6 +174,48 @@ public class AttachmentProvider extends ContentProvider {
}
}
/**
* Helper to convert unknown or unmapped attachments to something useful based on filename
* extensions. Imperfect, but helps.
*
* If the given mime type is non-empty and anything other than "application/octet-stream",
* just return it. (This is the most common case.)
* If the filename has a recognizable extension and it converts to a mime type, return that.
* If the filename has an unrecognized extension, return "application/extension"
* Otherwise return "application/octet-stream".
*
* @param fileName The given filename
* @param mimeType The given mime type
* @return A likely mime type for the attachment
*/
public static String inferMimeType(String fileName, String mimeType) {
// If the given mime type appears to be non-empty and non-generic - return it
if (!TextUtils.isEmpty(mimeType) &&
!"application/octet-stream".equalsIgnoreCase(mimeType)) {
return mimeType;
}
// Try to find an extension in the filename
if (!TextUtils.isEmpty(fileName)) {
int lastDot = fileName.lastIndexOf('.');
String extension = null;
if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
extension = fileName.substring(lastDot + 1).toLowerCase();
}
if (!TextUtils.isEmpty(extension)) {
// Extension found. Look up mime type, or synthesize if none found.
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mimeType == null) {
mimeType = "application/" + extension;
}
return mimeType;
}
}
// Fallback case - no good guess could be made.
return "application/octet-stream";
}
/**
* Open an attachment file. There are two "modes" - "raw", which returns an actual file,
* and "thumbnail", which attempts to generate a thumbnail image.

View File

@ -199,6 +199,9 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
long attachment1Id = 1;
long attachment2Id = 2;
long attachment3Id = 3;
long attachment4Id = 4;
long attachment5Id = 5;
long attachment6Id = 6;
Uri attachment1Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment1Id);
@ -223,6 +226,21 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
newAttachment3.mMimeType = "text/plain";
attachment3Id = addAttachmentToDb(account1, newAttachment3);
Attachment newAttachment4 = ProviderTestUtils.setupAttachment(message1Id, "file4.doc", 100,
false, mMockContext);
newAttachment4.mMimeType = "application/octet-stream";
attachment4Id = addAttachmentToDb(account1, newAttachment4);
Attachment newAttachment5 = ProviderTestUtils.setupAttachment(message1Id, "file5.xyz", 100,
false, mMockContext);
newAttachment5.mMimeType = "application/octet-stream";
attachment5Id = addAttachmentToDb(account1, newAttachment5);
Attachment newAttachment6 = ProviderTestUtils.setupAttachment(message1Id, "file6", 100,
false, mMockContext);
newAttachment6.mMimeType = "";
attachment6Id = addAttachmentToDb(account1, newAttachment6);
// Check the returned filetypes
Uri uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment2Id);
type = mMockResolver.getType(uri);
@ -230,6 +248,15 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment3Id);
type = mMockResolver.getType(uri);
assertEquals("text/plain", type);
uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment4Id);
type = mMockResolver.getType(uri);
assertEquals("application/msword", type);
uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment5Id);
type = mMockResolver.getType(uri);
assertEquals("application/xyz", type);
uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment6Id);
type = mMockResolver.getType(uri);
assertEquals("application/octet-stream", type);
// Check the returned filetypes for the thumbnails
uri = AttachmentProvider.getAttachmentThumbnailUri(account1.mId, attachment2Id, 62, 62);
@ -240,6 +267,45 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
assertEquals("image/png", type);
}
/**
* Test static inferMimeType()
* From the method doc:
* If the given mime type is non-empty and anything other than "application/octet-stream",
* just return it. (This is the most common case.)
* If the filename has a recognizable extension and it converts to a mime type, return that.
* If the filename has an unrecognized extension, return "application/extension"
* Otherwise return "application/octet-stream".
*/
public void testInferMimeType() {
final String DEFAULT = "application/octet-stream";
final String FILE_PDF = "myfile.false.pdf";
final String FILE_ABC = "myfile.false.abc";
final String FILE_NO_EXT = "myfile";
// If the given mime type is non-empty and anything other than "application/octet-stream",
// just return it. (This is the most common case.)
assertEquals("mime/type", AttachmentProvider.inferMimeType(null, "mime/type"));
assertEquals("mime/type", AttachmentProvider.inferMimeType("", "mime/type"));
assertEquals("mime/type", AttachmentProvider.inferMimeType(FILE_PDF, "mime/type"));
// If the filename has a recognizable extension and it converts to a mime type, return that.
assertEquals("application/pdf", AttachmentProvider.inferMimeType(FILE_PDF, null));
assertEquals("application/pdf", AttachmentProvider.inferMimeType(FILE_PDF, ""));
assertEquals("application/pdf", AttachmentProvider.inferMimeType(FILE_PDF, DEFAULT));
// If the filename has an unrecognized extension, return "application/extension"
assertEquals("application/abc", AttachmentProvider.inferMimeType(FILE_ABC, null));
assertEquals("application/abc", AttachmentProvider.inferMimeType(FILE_ABC, ""));
assertEquals("application/abc", AttachmentProvider.inferMimeType(FILE_ABC, DEFAULT));
// Otherwise return "application/octet-stream".
assertEquals(DEFAULT, AttachmentProvider.inferMimeType(FILE_NO_EXT, null));
assertEquals(DEFAULT, AttachmentProvider.inferMimeType(FILE_NO_EXT, ""));
assertEquals(DEFAULT, AttachmentProvider.inferMimeType(FILE_NO_EXT, DEFAULT));
assertEquals(DEFAULT, AttachmentProvider.inferMimeType(null, null));
assertEquals(DEFAULT, AttachmentProvider.inferMimeType("", ""));
}
/**
* test openFile()
* - regular file