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