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
1996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.content.ContentProvider;
203d25a519abf676f050b546d34401a277aea5de40Andrew Stadlerimport android.content.ContentUris;
2196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.content.ContentValues;
22dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blankimport android.content.Context;
23dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blankimport android.content.pm.PackageManager;
2496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.database.Cursor;
2596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.database.MatrixCursor;
2696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.graphics.Bitmap;
2796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.graphics.BitmapFactory;
2896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.net.Uri;
291ec89e6290df7853990517ebe869bdb212d7e337Marc Blankimport android.os.Binder;
3096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport android.os.ParcelFileDescriptor;
3196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
327fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.emailcommon.Logging;
337fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.emailcommon.internet.MimeUtility;
347fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.emailcommon.provider.EmailContent;
357fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.emailcommon.provider.EmailContent.Attachment;
367fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.emailcommon.provider.EmailContent.AttachmentColumns;
377fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.emailcommon.utility.AttachmentUtilities;
387fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.emailcommon.utility.AttachmentUtilities.Columns;
39560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils;
407fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.mail.utils.MatrixCursorWithCachedColumns;
417fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedy
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:
53e714bb9d153cfe13a7f0932e7d67ea08fa5a1d98Marc Blank *   content://com.android.mail.attachmentprovider/acct#/attach#/RAW
541d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki *
553d25a519abf676f050b546d34401a277aea5de40Andrew Stadler * And for access to thumbnails:
56e714bb9d153cfe13a7f0932e7d67ea08fa5a1d98Marc Blank *   content://com.android.mail.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
6980ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    private static final String[] MIME_TYPE_PROJECTION = new String[] {
7080ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler            AttachmentColumns.MIME_TYPE, AttachmentColumns.FILENAME };
7180ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    private static final int MIME_TYPE_COLUMN_MIME_TYPE = 0;
7280ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    private static final int MIME_TYPE_COLUMN_FILENAME = 1;
7380ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler
7480ebde2897dced46a0f24efb7c15a997b660a8feAndrew Stadler    private static final String[] PROJECTION_QUERY = new String[] { AttachmentColumns.FILENAME,
753d25a519abf676f050b546d34401a277aea5de40Andrew Stadler            AttachmentColumns.SIZE, AttachmentColumns.CONTENT_URI };
763d25a519abf676f050b546d34401a277aea5de40Andrew Stadler
7796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
7896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean onCreate() {
7996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        /*
8096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project         * We use the cache dir as a temporary directory (since Android doesn't give us one) so
8196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project         * on startup we'll clean up any .tmp files from the last run.
8296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project         */
83908fac0b247deff27fee44297f892afdfbb086a5Paul Westbrook
84908fac0b247deff27fee44297f892afdfbb086a5Paul Westbrook        final File[] files = getContext().getCacheDir().listFiles();
85908fac0b247deff27fee44297f892afdfbb086a5Paul Westbrook        if (files != null) {
86908fac0b247deff27fee44297f892afdfbb086a5Paul Westbrook            for (File file : files) {
87908fac0b247deff27fee44297f892afdfbb086a5Paul Westbrook                final String filename = file.getName();
88908fac0b247deff27fee44297f892afdfbb086a5Paul Westbrook                if (filename.endsWith(".tmp") || filename.startsWith("thmb_")) {
89908fac0b247deff27fee44297f892afdfbb086a5Paul Westbrook                    file.delete();
90908fac0b247deff27fee44297f892afdfbb086a5Paul Westbrook                }
9196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
9296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
9396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return true;
9496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
9596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
96301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler    /**
97301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Returns the mime type for a given attachment.  There are three possible results:
98301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *  - If thumbnail Uri, always returns "image/png" (even if there's no attachment)
99301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *  - If the attachment does not exist, returns null
100301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *  - Returns the mime type of the attachment
101301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     */
10296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
10396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public String getType(Uri uri) {
1041ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        long callingId = Binder.clearCallingIdentity();
1051ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        try {
1061ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            List<String> segments = uri.getPathSegments();
1071ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String id = segments.get(1);
1081ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String format = segments.get(2);
1098a574694606f0e5d781334d0d426fc379c51f3edMarc Blank            if (AttachmentUtilities.FORMAT_THUMBNAIL.equals(format)) {
1101ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                return "image/png";
1111ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            } else {
1121ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
1138a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                Cursor c = getContext().getContentResolver().query(uri, MIME_TYPE_PROJECTION, null,
1148a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                        null, null);
1151ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                try {
1161ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    if (c.moveToFirst()) {
1171ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        String mimeType = c.getString(MIME_TYPE_COLUMN_MIME_TYPE);
1181ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        String fileName = c.getString(MIME_TYPE_COLUMN_FILENAME);
1198a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                        mimeType = AttachmentUtilities.inferMimeType(fileName, mimeType);
1201ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        return mimeType;
1211ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    }
1221ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                } finally {
1231ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    c.close();
12496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
1251ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                return null;
12696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
1271ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        } finally {
1281ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Binder.restoreCallingIdentity(callingId);
12996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
13096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
13196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
132301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler    /**
133dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank     * Open an attachment file.  There are two "formats" - "raw", which returns an actual file,
134301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * and "thumbnail", which attempts to generate a thumbnail image.
1351d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
136301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Thumbnails are cached for easy space recovery and cleanup.
1371d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
138dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank     * TODO:  The thumbnail format returns null for its failure cases, instead of throwing
139301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * FileNotFoundException, and should be fixed for consistency.
1401d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
141301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     *  @throws FileNotFoundException
142301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     */
14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
14496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
145dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank        // If this is a write, the caller must have the EmailProvider permission, which is
146dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank        // based on signature only
147dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank        if (mode.equals("w")) {
148dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            Context context = getContext();
149f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            if (context.checkCallingOrSelfPermission(EmailContent.PROVIDER_PERMISSION)
150dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                    != PackageManager.PERMISSION_GRANTED) {
151dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                throw new FileNotFoundException();
152dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            }
153dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            List<String> segments = uri.getPathSegments();
154dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            String accountId = segments.get(0);
155dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            String id = segments.get(1);
156dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            File saveIn =
157dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                AttachmentUtilities.getAttachmentDirectory(context, Long.parseLong(accountId));
158dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            if (!saveIn.exists()) {
159dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                saveIn.mkdirs();
160dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            }
161dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            File newFile = new File(saveIn, id);
162dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            return ParcelFileDescriptor.open(
163dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                    newFile, ParcelFileDescriptor.MODE_READ_WRITE |
164dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                        ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE);
165dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank        }
1661ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        long callingId = Binder.clearCallingIdentity();
1671ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        try {
1681ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            List<String> segments = uri.getPathSegments();
1699794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah            final long accountId = Long.parseLong(segments.get(0));
1709794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah            final long id = Long.parseLong(segments.get(1));
1711ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String format = segments.get(2);
1728a574694606f0e5d781334d0d426fc379c51f3edMarc Blank            if (AttachmentUtilities.FORMAT_THUMBNAIL.equals(format)) {
1731ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                int width = Integer.parseInt(segments.get(3));
1741ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                int height = Integer.parseInt(segments.get(4));
1751ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                String filename = "thmb_" + accountId + "_" + id;
1761ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                File dir = getContext().getCacheDir();
1771ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                File file = new File(dir, filename);
1781ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                if (!file.exists()) {
1799794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah                    Uri attachmentUri = AttachmentUtilities.getAttachmentUri(accountId, id);
1801ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    Cursor c = query(attachmentUri,
1818a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                            new String[] { Columns.DATA }, null, null, null);
1821ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    if (c != null) {
1831ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        try {
1841ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            if (c.moveToFirst()) {
1851ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                                attachmentUri = Uri.parse(c.getString(0));
1861ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            } else {
1871ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                                return null;
1881ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            }
1891ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        } finally {
1901ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            c.close();
1915182d80674a1ef90586f52355a21a82e8265c10aMihai Preda                        }
1921ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    }
1931ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    String type = getContext().getContentResolver().getType(attachmentUri);
1941ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    try {
1951ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        InputStream in =
1961ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                            getContext().getContentResolver().openInputStream(attachmentUri);
1971ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        Bitmap thumbnail = createThumbnail(type, in);
198625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                        if (thumbnail == null) {
199625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                            return null;
200625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                        }
2011ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        thumbnail = Bitmap.createScaledBitmap(thumbnail, width, height, true);
2021ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        FileOutputStream out = new FileOutputStream(file);
2031ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
2041ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        out.close();
2051ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        in.close();
206625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                    } catch (IOException ioe) {
207560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                        LogUtils.d(Logging.LOG_TAG, "openFile/thumbnail failed with " +
20831d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                                ioe.getMessage());
209625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                        return null;
210625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler                    } catch (OutOfMemoryError oome) {
211560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                        LogUtils.d(Logging.LOG_TAG, "openFile/thumbnail failed with " +
21231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                                oome.getMessage());
2131ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        return null;
2145182d80674a1ef90586f52355a21a82e8265c10aMihai Preda                    }
2155182d80674a1ef90586f52355a21a82e8265c10aMihai Preda                }
2161ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
21796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
2181ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            else {
2191ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                return ParcelFileDescriptor.open(
2209794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah                        new File(getContext().getDatabasePath(accountId + ".db_att"),
2219794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah                                String.valueOf(id)),
2221ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                        ParcelFileDescriptor.MODE_READ_ONLY);
2231ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            }
2249794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah        } catch (NumberFormatException e) {
2259794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah            LogUtils.e(Logging.LOG_TAG,
2269794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah                    "AttachmentProvider.openFile: Failed to open as id is not a long");
2279794d7e8216138adf143a3b6faf3d5683316a662Rohan Shah            return null;
2281ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        } finally {
2291ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Binder.restoreCallingIdentity(callingId);
23096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
23196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
23296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
23396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
23496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public int delete(Uri uri, String arg1, String[] arg2) {
23596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return 0;
23696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
23796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
23896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
23996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public Uri insert(Uri uri, ContentValues values) {
24096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return null;
24196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
24296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
243301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler    /**
244301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Returns a cursor based on the data in the attachments table, or null if the attachment
245301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * is not recorded in the table.
2461d0be30871745ef0e623d8bb5e2e433567541623Makoto Onuki     *
247301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * Supports REST Uri only, for a single row - selection, selection args, and sortOrder are
248301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     * ignored (non-null values should probably throw an exception....)
249301ac18bb7f211b473c4f6fa6ec9bb276bbc6c10Andrew Stadler     */
25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
25196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            String sortOrder) {
2531ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        long callingId = Binder.clearCallingIdentity();
25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
2551ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            if (projection == null) {
2561ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                projection =
2571ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    new String[] {
2588a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                        Columns._ID,
2598a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                        Columns.DATA,
2601ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                };
26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
2631ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            List<String> segments = uri.getPathSegments();
2641ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String accountId = segments.get(0);
2651ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String id = segments.get(1);
2661ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String format = segments.get(2);
2671ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String name = null;
2681ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            int size = -1;
2691ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            String contentUri = null;
2701ec89e6290df7853990517ebe869bdb212d7e337Marc Blank
2711ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
2721ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Cursor c = getContext().getContentResolver().query(uri, PROJECTION_QUERY,
2731ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    null, null, null);
2741ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            try {
2751ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                if (c.moveToFirst()) {
2761ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    name = c.getString(0);
2771ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    size = c.getInt(1);
2781ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    contentUri = c.getString(2);
2791ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                } else {
2801ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    return null;
2811ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
2821ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            } finally {
2831ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                c.close();
28496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
2851ec89e6290df7853990517ebe869bdb212d7e337Marc Blank
2867fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedy            MatrixCursor ret = new MatrixCursorWithCachedColumns(projection);
2871ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Object[] values = new Object[projection.length];
2881ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            for (int i = 0, count = projection.length; i < count; i++) {
2891ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                String column = projection[i];
2908a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                if (Columns._ID.equals(column)) {
2911ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    values[i] = id;
2921ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
2938a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                else if (Columns.DATA.equals(column)) {
2941ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    values[i] = contentUri;
2951ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
2968a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                else if (Columns.DISPLAY_NAME.equals(column)) {
2971ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    values[i] = name;
2981ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
2998a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                else if (Columns.SIZE.equals(column)) {
3001ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                    values[i] = size;
3011ec89e6290df7853990517ebe869bdb212d7e337Marc Blank                }
30296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
3031ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            ret.addRow(values);
3041ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            return ret;
3051ec89e6290df7853990517ebe869bdb212d7e337Marc Blank        } finally {
3061ec89e6290df7853990517ebe869bdb212d7e337Marc Blank            Binder.restoreCallingIdentity(callingId);
30796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
30896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
30996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
31096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    @Override
31196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
31296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return 0;
31396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
31496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
315560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy    private static Bitmap createThumbnail(String type, InputStream data) {
31696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if(MimeUtility.mimeTypeMatches(type, "image/*")) {
31796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return createImageThumbnail(data);
31896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
31996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return null;
32096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
32196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
322560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy    private static Bitmap createImageThumbnail(InputStream data) {
32396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
32496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            Bitmap bitmap = BitmapFactory.decodeStream(data);
32596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return bitmap;
326625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler        } catch (OutOfMemoryError oome) {
327560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "createImageThumbnail failed with " + oome.getMessage());
32896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
329625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler        } catch (Exception e) {
330560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "createImageThumbnail failed with " + e.getMessage());
33196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            return null;
33296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
33396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
334625451ed25747d2637bd45274b0ec1f7d1bbb93cAndrew Stadler
3357436601fae756e8bc1f916560a1c2d1f8ce576fcMihai Preda    /**
3366c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki     * Need this to suppress warning in unit tests.
3376c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki     */
3386c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki    @Override
3396c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki    public void shutdown() {
3406c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki        // Don't call super.shutdown(), which emits a warning...
3416c36b4c613499655316d8c910e3c6bfb08a0d896Makoto Onuki    }
34296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
343