11a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira/*
21a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * Copyright (C) 2012 The Android Open Source Project
31a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira *
41a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
51a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * you may not use this file except in compliance with the License.
61a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * You may obtain a copy of the License at
71a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira *
81a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira *      http://www.apache.org/licenses/LICENSE-2.0
91a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira *
101a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * Unless required by applicable law or agreed to in writing, software
111a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
121a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * See the License for the specific language governing permissions and
141a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira * limitations under the License.
151a95a57f39da84cd7daf4e2161e50e518df51f13Mindy Pereira */
1630e2c24b056542f3b1b438aeb798305d1226d0c8Andy Huangpackage com.android.mail.utils;
177b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
1869c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Weiimport android.app.DownloadManager;
197b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.content.Context;
2069c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Weiimport android.net.ConnectivityManager;
2169c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Weiimport android.net.NetworkInfo;
222727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrookimport android.os.Bundle;
232727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrookimport android.os.ParcelFileDescriptor;
242727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrookimport android.os.SystemClock;
2596f6bed5c71d3cec9725ee3efc6465176c3023a8Mindy Pereiraimport android.text.TextUtils;
267b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
2730e2c24b056542f3b1b438aeb798305d1226d0c8Andy Huangimport com.android.mail.R;
2888fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huangimport com.android.mail.providers.Attachment;
290d2890660ce68b0c49f434d6496fdc69f1b39dc6Andrew Sappersteinimport com.google.common.collect.ImmutableMap;
307b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
310d2890660ce68b0c49f434d6496fdc69f1b39dc6Andrew Sappersteinimport java.io.File;
322727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrookimport java.io.FileInputStream;
33f1254d6f2f939ca90620080b1adffbb0c986cbe0Paul Westbrookimport java.io.FileNotFoundException;
342727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrookimport java.io.FileOutputStream;
352727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrookimport java.io.IOException;
360d2890660ce68b0c49f434d6496fdc69f1b39dc6Andrew Sappersteinimport java.io.InputStream;
377b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport java.text.DecimalFormat;
382727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrookimport java.text.SimpleDateFormat;
392727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrookimport java.util.Date;
4088fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huangimport java.util.Map;
417b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
427b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereirapublic class AttachmentUtils {
432727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    private static final String LOG_TAG = LogTag.getLogTag();
442727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook
457b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    private static final int KILO = 1024;
467b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    private static final int MEGA = KILO * KILO;
477b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
482727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    /** Any IO reads should be limited to this timeout */
492727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    private static final long READ_TIMEOUT = 3600 * 1000;
502727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook
512727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    private static final float MIN_CACHE_THRESHOLD = 0.25f;
522727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    private static final int MIN_CACHE_AVAILABLE_SPACE_BYTES = 100 * 1024 * 1024;
532727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook
547b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    /**
5588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * Singleton map of MIME->friendly description
5688fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * @see #getMimeTypeDisplayName(Context, String)
5788fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     */
5888fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang    private static Map<String, String> sDisplayNameMap;
5988fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang
6088fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang    /**
617b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * @return A string suitable for display in bytes, kilobytes or megabytes
627b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     *         depending on its size.
637b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     */
647b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    public static String convertToHumanReadableSize(Context context, long size) {
6514693431a3b1cd35d3b7149a8df417d409e1f732Mark Wei        final String count;
661eea8e00a944bce60d4a136587113232460fd6ddmindyp        if (size == 0) {
671eea8e00a944bce60d4a136587113232460fd6ddmindyp            return "";
6814693431a3b1cd35d3b7149a8df417d409e1f732Mark Wei        } else if (size < KILO) {
6914693431a3b1cd35d3b7149a8df417d409e1f732Mark Wei            count = String.valueOf(size);
7014693431a3b1cd35d3b7149a8df417d409e1f732Mark Wei            return context.getString(R.string.bytes, count);
717b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        } else if (size < MEGA) {
7214693431a3b1cd35d3b7149a8df417d409e1f732Mark Wei            count = String.valueOf(size / KILO);
7314693431a3b1cd35d3b7149a8df417d409e1f732Mark Wei            return context.getString(R.string.kilobytes, count);
747b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        } else {
757b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            DecimalFormat onePlace = new DecimalFormat("0.#");
7614693431a3b1cd35d3b7149a8df417d409e1f732Mark Wei            count = onePlace.format((float) size / (float) MEGA);
7714693431a3b1cd35d3b7149a8df417d409e1f732Mark Wei            return context.getString(R.string.megabytes, count);
787b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        }
797b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    }
8088fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang
8188fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang    /**
8288fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * Return a friendly localized file type for this attachment, or the empty string if
8388fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * unknown.
8488fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * @param context a Context to do resource lookup against
8588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * @return friendly file type or empty string
8688fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     */
8788fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang    public static String getDisplayType(final Context context, final Attachment attachment) {
88519c218db6109003ca72a35ffe276ed69b0230a4Martin Hibdon        if ((attachment.flags & Attachment.FLAG_DUMMY_ATTACHMENT) != 0) {
89519c218db6109003ca72a35ffe276ed69b0230a4Martin Hibdon            // This is a dummy attachment, display blank for type.
900d2890660ce68b0c49f434d6496fdc69f1b39dc6Andrew Sapperstein            return "";
91519c218db6109003ca72a35ffe276ed69b0230a4Martin Hibdon        }
92519c218db6109003ca72a35ffe276ed69b0230a4Martin Hibdon
9388fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        // try to get a friendly name for the exact mime type
9488fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        // then try to show a friendly name for the mime family
9588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        // finally, give up and just show the file extension
968750066b10f80e2a8080016973b3296d76d18266Mark Wei        final String contentType = attachment.getContentType();
978750066b10f80e2a8080016973b3296d76d18266Mark Wei        String displayType = getMimeTypeDisplayName(context, contentType);
988750066b10f80e2a8080016973b3296d76d18266Mark Wei        int index = !TextUtils.isEmpty(contentType) ? contentType.indexOf('/') : -1;
9988fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        if (displayType == null && index > 0) {
1008750066b10f80e2a8080016973b3296d76d18266Mark Wei            displayType = getMimeTypeDisplayName(context, contentType.substring(0, index));
10188fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        }
10288fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        if (displayType == null) {
1038750066b10f80e2a8080016973b3296d76d18266Mark Wei            String extension = Utils.getFileExtension(attachment.getName());
10488fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            // show '$EXTENSION File' for unknown file types
10588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            if (extension != null && extension.length() > 1 && extension.indexOf('.') == 0) {
10688fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                displayType = context.getString(R.string.attachment_unknown,
10788fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                        extension.substring(1).toUpperCase());
10888fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            }
10988fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        }
11088fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        if (displayType == null) {
11196f6bed5c71d3cec9725ee3efc6465176c3023a8Mindy Pereira            // no extension to display, but the map doesn't accept null entries
11288fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            displayType = "";
11388fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        }
11488fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        return displayType;
11588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang    }
11688fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang
11788fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang    /**
11888fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * Returns a user-friendly localized description of either a complete a MIME type or a
11988fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * MIME family.
12088fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * @param context used to look up localized strings
12188fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * @param type complete MIME type or just MIME family
12288fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     * @return localized description text, or null if not recognized
12388fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang     */
12488fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang    public static synchronized String getMimeTypeDisplayName(final Context context,
12588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            String type) {
12688fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        if (sDisplayNameMap == null) {
12788fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            String docName = context.getString(R.string.attachment_application_msword);
12888fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            String presoName = context.getString(R.string.attachment_application_vnd_ms_powerpoint);
12988fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            String sheetName = context.getString(R.string.attachment_application_vnd_ms_excel);
13088fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang
13188fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang            sDisplayNameMap = new ImmutableMap.Builder<String, String>()
13288fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("image", context.getString(R.string.attachment_image))
13388fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("audio", context.getString(R.string.attachment_audio))
13488fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("video", context.getString(R.string.attachment_video))
13588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("text", context.getString(R.string.attachment_text))
13688fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("application/pdf", context.getString(R.string.attachment_application_pdf))
13788fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang
13888fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                // Documents
13988fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("application/msword", docName)
14088fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
14188fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                        docName)
14288fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang
14388fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                // Presentations
14488fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("application/vnd.ms-powerpoint",
14588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                        presoName)
14688fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("application/vnd.openxmlformats-officedocument.presentationml.presentation",
14788fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                        presoName)
14888fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang
14988fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                // Spreadsheets
15088fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("application/vnd.ms-excel", sheetName)
15188fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .put("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
15288fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                        sheetName)
15388fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang
15488fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang                .build();
15588fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        }
15688fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang        return sDisplayNameMap.get(type);
15788fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang    }
15862066e47fe69c33b60ed970e46809ba81d044064Mark Wei
1592727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    /**
1602727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook     * Cache the file specified by the given attachment.  This will attempt to use any
1612727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook     * {@link ParcelFileDescriptor} in the Bundle parameter
1622727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook     * @param context
1632727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook     * @param attachment  Attachment to be cached
1642727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook     * @param attachmentFds optional {@link Bundle} containing {@link ParcelFileDescriptor} if the
1652727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook     *        caller has opened the files
1662727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook     * @return String file path for the cached attachment
1672727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook     */
1682727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    // TODO(pwestbro): Once the attachment has a field for the cached path, this method should be
1692727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    // changed to update the attachment, and return a boolean indicating that the attachment has
1702727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    // been cached.
1712727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    public static String cacheAttachmentUri(Context context, Attachment attachment,
1722727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            Bundle attachmentFds) {
1732727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        final File cacheDir = context.getCacheDir();
1742727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook
1752727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        final long totalSpace = cacheDir.getTotalSpace();
1762727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        if (attachment.size > 0) {
1772727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            final long usableSpace = cacheDir.getUsableSpace() - attachment.size;
1782727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            if (isLowSpace(totalSpace, usableSpace)) {
1792727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                LogUtils.w(LOG_TAG, "Low memory (%d/%d). Can't cache attachment %s",
1802727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                        usableSpace, totalSpace, attachment);
1812727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                return null;
1822727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            }
1832727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        }
1842727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        InputStream inputStream = null;
1852727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        FileOutputStream outputStream = null;
1862727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        File file = null;
1872727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        try {
1882727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-kk:mm:ss");
1892727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            file = File.createTempFile(dateFormat.format(new Date()), ".attachment", cacheDir);
1902727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            final ParcelFileDescriptor fileDescriptor = attachmentFds != null
1912727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                    && attachment.contentUri != null ? (ParcelFileDescriptor) attachmentFds
1922727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                    .getParcelable(attachment.contentUri.toString())
1932727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                    : null;
1942727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            if (fileDescriptor != null) {
1952727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                // Get the input stream from the file descriptor
1962727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
1972727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            } else {
198f1254d6f2f939ca90620080b1adffbb0c986cbe0Paul Westbrook                if (attachment.contentUri == null) {
199f1254d6f2f939ca90620080b1adffbb0c986cbe0Paul Westbrook                    // The contentUri of the attachment is null.  This can happen when sending a
200f1254d6f2f939ca90620080b1adffbb0c986cbe0Paul Westbrook                    // message that has been previously saved, and the attachments had been
201f1254d6f2f939ca90620080b1adffbb0c986cbe0Paul Westbrook                    // uploaded.
202f1254d6f2f939ca90620080b1adffbb0c986cbe0Paul Westbrook                    LogUtils.d(LOG_TAG, "contentUri is null in attachment: %s", attachment);
203f1254d6f2f939ca90620080b1adffbb0c986cbe0Paul Westbrook                    throw new FileNotFoundException("Missing contentUri in attachment");
204f1254d6f2f939ca90620080b1adffbb0c986cbe0Paul Westbrook                }
2052727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                // Attempt to open the file
2062727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                inputStream = context.getContentResolver().openInputStream(attachment.contentUri);
2072727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            }
2082727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            outputStream = new FileOutputStream(file);
2092727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            final long now = SystemClock.elapsedRealtime();
2102727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            final byte[] bytes = new byte[1024];
2112727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            while (true) {
2122727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                int len = inputStream.read(bytes);
2132727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                if (len <= 0) {
2142727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                    break;
2152727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                }
2162727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                outputStream.write(bytes, 0, len);
2172727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                if (SystemClock.elapsedRealtime() - now > READ_TIMEOUT) {
2182727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                    throw new IOException("Timed out reading attachment data");
2192727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                }
2202727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            }
2212727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            outputStream.flush();
2222727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            String cachedFileUri = file.getAbsolutePath();
2232727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            LogUtils.d(LOG_TAG, "Cached %s to %s", attachment.contentUri, cachedFileUri);
2242727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook
2252727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            final long usableSpace = cacheDir.getUsableSpace();
2262727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            if (isLowSpace(totalSpace, usableSpace)) {
2272727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                file.delete();
2282727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                LogUtils.w(LOG_TAG, "Low memory (%d/%d). Can't cache attachment %s",
2292727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                        usableSpace, totalSpace, attachment);
2302727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                cachedFileUri = null;
2312727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            }
2322727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook
2332727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            return cachedFileUri;
2340b171a6fef1b4c3a59b9b15f44a3b536fbd2d3afJin Cao        } catch (IOException | SecurityException e) {
2352727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            // Catch any exception here to allow for unexpected failures during caching se we don't
2362727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            // leave app in inconsistent state as we call this method outside of a transaction for
2372727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            // performance reasons.
2382727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            LogUtils.e(LOG_TAG, e, "Failed to cache attachment %s", attachment);
2392727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            if (file != null) {
2402727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                file.delete();
2412727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            }
2422727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            return null;
2432727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        } finally {
2442727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            try {
2452727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                if (inputStream != null) {
2462727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                    inputStream.close();
2472727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                }
2482727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                if (outputStream != null) {
2492727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                    outputStream.close();
2502727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                }
2512727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            } catch (IOException e) {
2522727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                LogUtils.w(LOG_TAG, e, "Failed to close stream");
2532727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook            }
2542727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        }
2552727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    }
2562727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook
2572727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    private static boolean isLowSpace(long totalSpace, long usableSpace) {
2582727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        // For caching attachments we want to enable caching if there is
2592727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        // more than 100MB available, or if 25% of total space is free on devices
2602727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        // where the cache partition is < 400MB.
2612727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook        return usableSpace <
2622727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook                Math.min(totalSpace * MIN_CACHE_THRESHOLD, MIN_CACHE_AVAILABLE_SPACE_BYTES);
2632727fa94c6be3f0c4a90ee3b3dd3193a10e57078Paul Westbrook    }
264345fcff8e66f1f6637aea802c117930d4f80c871Mark Wei
265345fcff8e66f1f6637aea802c117930d4f80c871Mark Wei    /**
26669c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei     * Checks if the attachment can be downloaded with the current network
26769c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei     * connection.
26869c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei     *
26969c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei     * @param attachment the attachment to be checked
27069c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei     * @return true if the attachment can be downloaded.
27169c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei     */
27269c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei    public static boolean canDownloadAttachment(Context context, Attachment attachment) {
27369c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
27469c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei                Context.CONNECTIVITY_SERVICE);
27569c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei        NetworkInfo info = connectivityManager.getActiveNetworkInfo();
27669c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei        if (info == null) {
27769c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei            return false;
27869c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei        } else if (info.isConnected()) {
27969c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei            if (info.getType() != ConnectivityManager.TYPE_MOBILE) {
28069c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei                // not mobile network
28169c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei                return true;
28269c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei            } else {
28369c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei                // mobile network
28469c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei                Long maxBytes = DownloadManager.getMaxBytesOverMobile(context);
28569c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei                return maxBytes == null || attachment == null || attachment.size <= maxBytes;
28669c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei            }
28769c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei        } else {
28869c4ddb7a49f11377143c9ff1c7b85b2450449e9Mark Wei            return false;
28962066e47fe69c33b60ed970e46809ba81d044064Mark Wei        }
29062066e47fe69c33b60ed970e46809ba81d044064Mark Wei    }
2917b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira}
292