AttachmentProvider.java revision 625451ed25747d2637bd45274b0ec1f7d1bbb93c
196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/*
296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * you may not use this file except in compliance with the License.
696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * You may obtain a copy of the License at
796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
1096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
1196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
1296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * See the License for the specific language governing permissions and
1496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * limitations under the License.
1596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */
1696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
1796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpackage com.android.email.provider;
1896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
199ef6f645f57d869a600113f555389b5d5e368c21Mihai Predaimport com.android.email.Email;
20c0033f24a26a08c47aa38d957f42cf63cfa3c345Andrew Stadlerimport com.android.email.mail.internet.MimeUtility;
213d25a519abf676f050b546d34401a277aea5de40Andrew Stadlerimport com.android.email.provider.EmailContent.Attachment;
223d25a519abf676f050b546d34401a277aea5de40Andrew Stadlerimport com.android.email.provider.EmailContent.AttachmentColumns;
2371754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadlerimport com.android.email.provider.EmailContent.Message;
2471754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadlerimport com.android.email.provider.EmailContent.MessageColumns;
2596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
2696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.content.ContentProvider;
277436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Predaimport android.content.ContentResolver;
283d25a519abf676f050b546d34401a277aea5de40Andrew Stadlerimport android.content.ContentUris;
2996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.content.ContentValues;
30a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadlerimport android.content.Context;
3196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.database.Cursor;
3296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.database.MatrixCursor;
3396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.graphics.Bitmap;
3496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.graphics.BitmapFactory;
3596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.net.Uri;
361ec89e6290df7853990517ebe869bdb212d7e337Marc Blankimport android.os.Binder;
3796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.os.ParcelFileDescriptor;
3880ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadlerimport android.text.TextUtils;
39625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadlerimport android.util.Log;
4080ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadlerimport android.webkit.MimeTypeMap;
4196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
42c0033f24a26a08c47aa38d957f42cf63cfa3c345Andrew Stadlerimport java.io.File;
43c0033f24a26a08c47aa38d957f42cf63cfa3c345Andrew Stadlerimport java.io.FileNotFoundException;
44c0033f24a26a08c47aa38d957f42cf63cfa3c345Andrew Stadlerimport java.io.FileOutputStream;
45c0033f24a26a08c47aa38d957f42cf63cfa3c345Andrew Stadlerimport java.io.IOException;
46c0033f24a26a08c47aa38d957f42cf63cfa3c345Andrew Stadlerimport java.io.InputStream;
47c0033f24a26a08c47aa38d957f42cf63cfa3c345Andrew Stadlerimport java.util.List;
4896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
4996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/*
5096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * A simple ContentProvider that allows file access to Email's attachments.
511d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki *
523d25a519abf676f050b546d34401a277aea5de40Andrew Stadler * The URI scheme is as follows.  For raw file access:
533d25a519abf676f050b546d34401a277aea5de40Andrew Stadler *   content://com.android.email.attachmentprovider/acct#/attach#/RAW
541d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki *
553d25a519abf676f050b546d34401a277aea5de40Andrew Stadler * And for access to thumbnails:
563d25a519abf676f050b546d34401a277aea5de40Andrew Stadler *   content://com.android.email.attachmentprovider/acct#/attach#/THUMBNAIL/width#/height#
57a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler *
58a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler * The on-disk (storage) schema is as follows.
591d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki *
60a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler * Attachments are stored at:  <database-path>/account#.db_att/item#
61a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler * Thumbnails are stored at:   <cache-path>/thmb_account#_item#
621d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki *
63a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler * Using the standard application context, account #10 and attachment # 20, this would be:
64a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler *      /data/data/com.android.email/databases/10.db_att/20
65a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler *      /data/data/com.android.email/cache/thmb_10_20
6696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */
6796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpublic class AttachmentProvider extends ContentProvider {
68301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler
69301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler    public static final String AUTHORITY = "com.android.email.attachmentprovider";
70301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler    public static final Uri CONTENT_URI = Uri.parse( "content://" + AUTHORITY);
7196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
7296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private static final String FORMAT_RAW = "RAW";
7396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
7496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
7596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public static class AttachmentProviderColumns {
7696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        public static final String _ID = "_id";
7796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        public static final String DATA = "_data";
7896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        public static final String DISPLAY_NAME = "_display_name";
7996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        public static final String SIZE = "_size";
8096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
8196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
8280ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    private static final String[] MIME_TYPE_PROJECTION = new String[] {
8380ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            AttachmentColumns.MIME_TYPE, AttachmentColumns.FILENAME };
8480ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    private static final int MIME_TYPE_COLUMN_MIME_TYPE = 0;
8580ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    private static final int MIME_TYPE_COLUMN_FILENAME = 1;
8680ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler
8780ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    private static final String[] PROJECTION_QUERY = new String[] { AttachmentColumns.FILENAME,
883d25a519abf676f050b546d34401a277aea5de40Andrew Stadler            AttachmentColumns.SIZE, AttachmentColumns.CONTENT_URI };
893d25a519abf676f050b546d34401a277aea5de40Andrew Stadler
903d25a519abf676f050b546d34401a277aea5de40Andrew Stadler    public static Uri getAttachmentUri(long accountId, long id) {
9196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return CONTENT_URI.buildUpon()
923d25a519abf676f050b546d34401a277aea5de40Andrew Stadler                .appendPath(Long.toString(accountId))
9396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                .appendPath(Long.toString(id))
9496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                .appendPath(FORMAT_RAW)
9596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                .build();
9696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
9796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
98a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler    public static Uri getAttachmentThumbnailUri(long accountId, long id,
99c0033f24a26a08c47aa38d957f42cf63cfa3c345Andrew Stadler            int width, int height) {
10096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return CONTENT_URI.buildUpon()
101a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler                .appendPath(Long.toString(accountId))
10296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                .appendPath(Long.toString(id))
10396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                .appendPath(FORMAT_THUMBNAIL)
10496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                .appendPath(Integer.toString(width))
10596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                .appendPath(Integer.toString(height))
10696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                .build();
10796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
10896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
109a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler    /**
110a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     * Return the filename for a given attachment.  This should be used by any code that is
111a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     * going to *write* attachments.
112a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     *
113a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     * This does not create or write the file, or even the directories.  It simply builds
114a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     * the filename that should be used.
115a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     */
116a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler    public static File getAttachmentFilename(Context context, long accountId, long attachmentId) {
1173f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler        return new File(getAttachmentDirectory(context, accountId), Long.toString(attachmentId));
1183f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler    }
1193f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler
1203f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler    /**
1213f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler     * Return the directory for a given attachment.  This should be used by any code that is
1223f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler     * going to *write* attachments.
1233f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler     *
1243f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler     * This does not create or write the directory.  It simply builds the pathname that should be
1253f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler     * used.
1263f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler     */
1273f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler    public static File getAttachmentDirectory(Context context, long accountId) {
1283f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler        return context.getDatabasePath(accountId + ".db_att");
129a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler    }
130a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler
13196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
13296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean onCreate() {
13396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        /*
13496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project         * We use the cache dir as a temporary directory (since Android doesn't give us one) so
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project         * on startup we'll clean up any .tmp files from the last run.
13696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project         */
13796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        File[] files = getContext().getCacheDir().listFiles();
13896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        for (File file : files) {
139aeeefedb43c40e887850654ac88270755f5631c1Mihai Preda            String filename = file.getName();
140aeeefedb43c40e887850654ac88270755f5631c1Mihai Preda            if (filename.endsWith(".tmp") || filename.startsWith("thmb_")) {
14196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                file.delete();
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
14496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return true;
14596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
14696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
147301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler    /**
148301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Returns the mime type for a given attachment.  There are three possible results:
149301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *  - If thumbnail Uri, always returns "image/png" (even if there's no attachment)
150301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *  - If the attachment does not exist, returns null
151301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *  - Returns the mime type of the attachment
152301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     */
15396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
15496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public String getType(Uri uri) {
1551ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        long callingId = Binder.clearCallingIdentity();
1561ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        try {
1571ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            List<String> segments = uri.getPathSegments();
1581ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String id = segments.get(1);
1591ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String format = segments.get(2);
1601ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            if (FORMAT_THUMBNAIL.equals(format)) {
1611ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                return "image/png";
1621ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            } else {
1631ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
1641ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                Cursor c = getContext().getContentResolver().query(uri, MIME_TYPE_PROJECTION,
1651ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        null, null, null);
1661ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                try {
1671ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    if (c.moveToFirst()) {
1681ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        String mimeType = c.getString(MIME_TYPE_COLUMN_MIME_TYPE);
1691ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        String fileName = c.getString(MIME_TYPE_COLUMN_FILENAME);
1701ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        mimeType = inferMimeType(fileName, mimeType);
1711ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        return mimeType;
1721ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    }
1731ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                } finally {
1741ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    c.close();
17596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
1761ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                return null;
17796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
1781ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        } finally {
1791ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Binder.restoreCallingIdentity(callingId);
18096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
18196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
18296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
183301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler    /**
18480ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * Helper to convert unknown or unmapped attachments to something useful based on filename
18580ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * extensions.  Imperfect, but helps.
18680ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     *
1871d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     * If the file extension is ".eml", return "message/rfc822", which is necessary for the email
1881d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     * app to open it.
18980ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * If the given mime type is non-empty and anything other than "application/octet-stream",
19080ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * just return it.  (This is the most common case.)
19180ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * If the filename has a recognizable extension and it converts to a mime type, return that.
19280ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * If the filename has an unrecognized extension, return "application/extension"
19380ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * Otherwise return "application/octet-stream".
19480ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     *
19580ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * @param fileName The given filename
19680ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * @param mimeType The given mime type
19780ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     * @return A likely mime type for the attachment
19880ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler     */
19980ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    public static String inferMimeType(String fileName, String mimeType) {
2001d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki        if (fileName != null && fileName.toLowerCase().endsWith(".eml")) {
2011d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki            return "message/rfc822";
2021d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki        }
20380ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler        // If the given mime type appears to be non-empty and non-generic - return it
20480ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler        if (!TextUtils.isEmpty(mimeType) &&
20580ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler                !"application/octet-stream".equalsIgnoreCase(mimeType)) {
20680ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            return mimeType;
20780ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler        }
20880ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler
20980ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler        // Try to find an extension in the filename
21080ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler        if (!TextUtils.isEmpty(fileName)) {
21180ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            int lastDot = fileName.lastIndexOf('.');
21280ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            String extension = null;
21380ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
21480ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler                extension = fileName.substring(lastDot + 1).toLowerCase();
21580ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            }
21680ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            if (!TextUtils.isEmpty(extension)) {
21780ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler                // Extension found.  Look up mime type, or synthesize if none found.
21880ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler                mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
21980ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler                if (mimeType == null) {
22080ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler                    mimeType = "application/" + extension;
22180ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler                }
22280ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler                return mimeType;
22380ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            }
22480ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler        }
22580ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler
22680ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler        // Fallback case - no good guess could be made.
22780ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler        return "application/octet-stream";
22880ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    }
22980ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler
23080ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    /**
231301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Open an attachment file.  There are two "modes" - "raw", which returns an actual file,
232301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * and "thumbnail", which attempts to generate a thumbnail image.
2331d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
234301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Thumbnails are cached for easy space recovery and cleanup.
2351d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
236301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * TODO:  The thumbnail mode returns null for its failure cases, instead of throwing
237301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * FileNotFoundException, and should be fixed for consistency.
2381d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
239301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *  @throws FileNotFoundException
240301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     */
24196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
24296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
2431ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        long callingId = Binder.clearCallingIdentity();
2441ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        try {
2451ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            List<String> segments = uri.getPathSegments();
2461ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String accountId = segments.get(0);
2471ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String id = segments.get(1);
2481ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String format = segments.get(2);
2491ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            if (FORMAT_THUMBNAIL.equals(format)) {
2501ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                int width = Integer.parseInt(segments.get(3));
2511ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                int height = Integer.parseInt(segments.get(4));
2521ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                String filename = "thmb_" + accountId + "_" + id;
2531ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                File dir = getContext().getCacheDir();
2541ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                File file = new File(dir, filename);
2551ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                if (!file.exists()) {
2561ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    Uri attachmentUri =
2571ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        getAttachmentUri(Long.parseLong(accountId), Long.parseLong(id));
2581ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    Cursor c = query(attachmentUri,
2591ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            new String[] { AttachmentProviderColumns.DATA }, null, null, null);
2601ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    if (c != null) {
2611ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        try {
2621ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            if (c.moveToFirst()) {
2631ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                                attachmentUri = Uri.parse(c.getString(0));
2641ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            } else {
2651ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                                return null;
2661ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            }
2671ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        } finally {
2681ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            c.close();
2695182d80674a1ef90586f52355a21a82e8265c10aMihai Preda                        }
2701ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    }
2711ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    String type = getContext().getContentResolver().getType(attachmentUri);
2721ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    try {
2731ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        InputStream in =
2741ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            getContext().getContentResolver().openInputStream(attachmentUri);
2751ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        Bitmap thumbnail = createThumbnail(type, in);
276625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                        if (thumbnail == null) {
277625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                            return null;
278625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                        }
2791ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        thumbnail = Bitmap.createScaledBitmap(thumbnail, width, height, true);
2801ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        FileOutputStream out = new FileOutputStream(file);
2811ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
2821ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        out.close();
2831ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        in.close();
284625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                    } catch (IOException ioe) {
285625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                        Log.d(Email.LOG_TAG, "openFile/thumbnail failed with " + ioe.getMessage());
286625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                        return null;
287625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                    } catch (OutOfMemoryError oome) {
288625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                        Log.d(Email.LOG_TAG, "openFile/thumbnail failed with " + oome.getMessage());
2891ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        return null;
2905182d80674a1ef90586f52355a21a82e8265c10aMihai Preda                    }
2915182d80674a1ef90586f52355a21a82e8265c10aMihai Preda                }
2921ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
29396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
2941ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            else {
2951ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                return ParcelFileDescriptor.open(
2961ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        new File(getContext().getDatabasePath(accountId + ".db_att"), id),
2971ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        ParcelFileDescriptor.MODE_READ_ONLY);
2981ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            }
2991ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        } finally {
3001ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Binder.restoreCallingIdentity(callingId);
30196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
30296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
30396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
30496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
30596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public int delete(Uri uri, String arg1, String[] arg2) {
30696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return 0;
30796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
30896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
30996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
31096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public Uri insert(Uri uri, ContentValues values) {
31196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return null;
31296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
31396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
314301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler    /**
315301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Returns a cursor based on the data in the attachments table, or null if the attachment
316301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * is not recorded in the table.
3171d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
318301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Supports REST Uri only, for a single row - selection, selection args, and sortOrder are
319301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * ignored (non-null values should probably throw an exception....)
320301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     */
32196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
32296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
32396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            String sortOrder) {
3241ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        long callingId = Binder.clearCallingIdentity();
32596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
3261ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            if (projection == null) {
3271ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                projection =
3281ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    new String[] {
3291ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        AttachmentProviderColumns._ID,
3301ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        AttachmentProviderColumns.DATA,
3311ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                };
33296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
33396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
3341ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            List<String> segments = uri.getPathSegments();
3351ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String accountId = segments.get(0);
3361ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String id = segments.get(1);
3371ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String format = segments.get(2);
3381ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String name = null;
3391ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            int size = -1;
3401ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String contentUri = null;
3411ec89e6290df7853990517ebe869bdb212d7e337Marc Blank
3421ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
3431ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Cursor c = getContext().getContentResolver().query(uri, PROJECTION_QUERY,
3441ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    null, null, null);
3451ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            try {
3461ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                if (c.moveToFirst()) {
3471ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    name = c.getString(0);
3481ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    size = c.getInt(1);
3491ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    contentUri = c.getString(2);
3501ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                } else {
3511ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    return null;
3521ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
3531ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            } finally {
3541ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                c.close();
35596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
3561ec89e6290df7853990517ebe869bdb212d7e337Marc Blank
3571ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            MatrixCursor ret = new MatrixCursor(projection);
3581ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Object[] values = new Object[projection.length];
3591ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            for (int i = 0, count = projection.length; i < count; i++) {
3601ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                String column = projection[i];
3611ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                if (AttachmentProviderColumns._ID.equals(column)) {
3621ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    values[i] = id;
3631ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
3641ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                else if (AttachmentProviderColumns.DATA.equals(column)) {
3651ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    values[i] = contentUri;
3661ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
3671ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                else if (AttachmentProviderColumns.DISPLAY_NAME.equals(column)) {
3681ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    values[i] = name;
3691ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
3701ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                else if (AttachmentProviderColumns.SIZE.equals(column)) {
3711ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    values[i] = size;
3721ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
37396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
3741ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            ret.addRow(values);
3751ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            return ret;
3761ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        } finally {
3771ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Binder.restoreCallingIdentity(callingId);
37896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
37996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
38096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
38196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
38296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
38396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return 0;
38496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
38596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
38696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private Bitmap createThumbnail(String type, InputStream data) {
38796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if(MimeUtility.mimeTypeMatches(type, "image/*")) {
38896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return createImageThumbnail(data);
38996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
39096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return null;
39196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
39296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
39396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private Bitmap createImageThumbnail(InputStream data) {
39496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
39596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            Bitmap bitmap = BitmapFactory.decodeStream(data);
39696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return bitmap;
397625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler        } catch (OutOfMemoryError oome) {
398625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler            Log.d(Email.LOG_TAG, "createImageThumbnail failed with " + oome.getMessage());
39996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
400625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler        } catch (Exception e) {
401625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler            Log.d(Email.LOG_TAG, "createImageThumbnail failed with " + e.getMessage());
40296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
40396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
40496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
405625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler
4067436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda    /**
407301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Resolve attachment id to content URI.  Returns the resolved content URI (from the attachment
408301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * DB) or, if not found, simply returns the incoming value.
4091d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
4107436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda     * @param attachmentUri
4117436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda     * @return resolved content URI
412301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *
413301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * TODO:  Throws an SQLite exception on a missing DB file (e.g. unknown URI) instead of just
414301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * returning the incoming uri, as it should.
4157436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda     */
4167436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda    public static Uri resolveAttachmentIdToContentUri(ContentResolver resolver, Uri attachmentUri) {
4177436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda        Cursor c = resolver.query(attachmentUri,
4187436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda                new String[] { AttachmentProvider.AttachmentProviderColumns.DATA },
4197436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda                null, null, null);
4207436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda        if (c != null) {
4217436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda            try {
4227436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda                if (c.moveToFirst()) {
4239ef6f645f57d869a600113f555389b5d5e368c21Mihai Preda                    final String strUri = c.getString(0);
4249ef6f645f57d869a600113f555389b5d5e368c21Mihai Preda                    if (strUri != null) {
4259ef6f645f57d869a600113f555389b5d5e368c21Mihai Preda                        return Uri.parse(strUri);
4269ef6f645f57d869a600113f555389b5d5e368c21Mihai Preda                    } else {
4279ef6f645f57d869a600113f555389b5d5e368c21Mihai Preda                        Email.log("AttachmentProvider: attachment with null contentUri");
4289ef6f645f57d869a600113f555389b5d5e368c21Mihai Preda                    }
4297436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda                }
4307436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda            } finally {
4317436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda                c.close();
4327436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda            }
4337436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda        }
4347436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda        return attachmentUri;
4357436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda    }
4364b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler
4374b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler    /**
4384b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler     * In support of deleting a message, find all attachments and delete associated attachment
4394b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler     * files.
4404b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler     * @param context
4414b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler     * @param accountId the account for the message
4424b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler     * @param messageId the message
4434b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler     */
4444b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler    public static void deleteAllAttachmentFiles(Context context, long accountId, long messageId) {
4454b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, messageId);
4464b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        Cursor c = context.getContentResolver().query(uri, Attachment.ID_PROJECTION,
4474b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                null, null, null);
4484b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        try {
4494b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            while (c.moveToNext()) {
4504b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                long attachmentId = c.getLong(Attachment.ID_PROJECTION_COLUMN);
4514b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                File attachmentFile = getAttachmentFilename(context, accountId, attachmentId);
4524b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                // Note, delete() throws no exceptions for basic FS errors (e.g. file not found)
4534b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                // it just returns false, which we ignore, and proceed to the next file.
4544b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                // This entire loop is best-effort only.
4554b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                attachmentFile.delete();
4564b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            }
4574b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        } finally {
4584b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            c.close();
4594b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        }
4604b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler    }
46171754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler
46271754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler    /**
46371754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler     * In support of deleting a mailbox, find all messages and delete their attachments.
46471754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler     *
46571754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler     * @param context
46671754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler     * @param accountId the account for the mailbox
46771754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler     * @param mailboxId the mailbox for the messages
46871754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler     */
46971754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler    public static void deleteAllMailboxAttachmentFiles(Context context, long accountId,
47071754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler            long mailboxId) {
47171754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler        Cursor c = context.getContentResolver().query(Message.CONTENT_URI,
47271754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler                Message.ID_COLUMN_PROJECTION, MessageColumns.MAILBOX_KEY + "=?",
47371754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler                new String[] { Long.toString(mailboxId) }, null);
47471754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler        try {
47571754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler            while (c.moveToNext()) {
47671754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler                long messageId = c.getLong(Message.ID_PROJECTION_COLUMN);
47771754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler                deleteAllAttachmentFiles(context, accountId, messageId);
47871754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler            }
47971754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler        } finally {
48071754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler            c.close();
48171754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler        }
48271754d3f940fe82e251c274e3e56781e702cfd6fAndrew Stadler    }
4836c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki
4846c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki    /**
4856c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki     * Need this to suppress warning in unit tests.
4866c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki     */
4876c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki    @Override
4886c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki    public void shutdown() {
4896c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki        // Don't call super.shutdown(), which emits a warning...
4906c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki    }
49196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
492