17b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira/** 27b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Copyright (c) 2011, Google Inc. 37b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * 47b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License"); 57b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * you may not use this file except in compliance with the License. 67b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * You may obtain a copy of the License at 77b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * 87b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * http://www.apache.org/licenses/LICENSE-2.0 97b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * 107b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Unless required by applicable law or agreed to in writing, software 117b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS, 127b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * See the License for the specific language governing permissions and 147b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * limitations under the License. 157b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */ 1630e2c24b056542f3b1b438aeb798305d1226d0c8Andy Huangpackage com.android.mail.compose; 177b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 189932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport android.content.ContentResolver; 197b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.content.Context; 209932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport android.database.Cursor; 219932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport android.database.sqlite.SQLiteException; 229932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport android.net.Uri; 239932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport android.os.ParcelFileDescriptor; 249932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport android.provider.OpenableColumns; 259932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport android.text.TextUtils; 267b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.util.AttributeSet; 277b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.view.View; 28237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sappersteinimport android.view.ViewGroup; 29ce2a25c96f9274d9c2944a5d197a3d3dd75d71eaAndrew Sappersteinimport android.view.inputmethod.InputMethodManager; 307b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.widget.LinearLayout; 317b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 32237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sappersteinimport com.android.mail.R; 33237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sappersteinimport com.android.mail.providers.Account; 34237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sappersteinimport com.android.mail.providers.Attachment; 35237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sappersteinimport com.android.mail.ui.AttachmentTile; 3662066e47fe69c33b60ed970e46809ba81d044064Mark Weiimport com.android.mail.ui.AttachmentTile.AttachmentPreview; 37865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sappersteinimport com.android.mail.ui.AttachmentTileGrid; 38b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag; 39237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sappersteinimport com.android.mail.utils.LogUtils; 40237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sappersteinimport com.google.common.annotations.VisibleForTesting; 41237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sappersteinimport com.google.common.collect.Lists; 42237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein 439932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport java.io.FileNotFoundException; 449932dee248718d2cec993d93b5115973d1389d0aMindy Pereiraimport java.io.IOException; 457b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport java.util.ArrayList; 467b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 477b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira/* 487b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * View for displaying attachments in the compose screen. 497b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */ 5040882430c874ff29890ec6fa31f7649788cbc470mindypclass AttachmentsView extends LinearLayout { 51b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook private static final String LOG_TAG = LogTag.getLogTag(); 522ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 538750066b10f80e2a8080016973b3296d76d18266Mark Wei private final ArrayList<Attachment> mAttachments; 5440882430c874ff29890ec6fa31f7649788cbc470mindyp private AttachmentAddedOrDeletedListener mChangeListener; 55237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein private AttachmentTileGrid mTileGrid; 56237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein private LinearLayout mAttachmentLayout; 577b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 587b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira public AttachmentsView(Context context) { 597b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira this(context, null); 607b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 617b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 627b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira public AttachmentsView(Context context, AttributeSet attrs) { 637b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira super(context, attrs); 647b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira mAttachments = Lists.newArrayList(); 657b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 667b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 67237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein @Override 68237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein protected void onFinishInflate() { 69237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein super.onFinishInflate(); 70237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein 71237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein mTileGrid = (AttachmentTileGrid) findViewById(R.id.attachment_tile_grid); 72237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein mAttachmentLayout = (LinearLayout) findViewById(R.id.attachment_bar_list); 732ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein } 742ea06183aa826b28fe6ebdb454da49ba3878bc4fAndrew Sapperstein 75226e4fd316954167654b207aba998b7a7e43ade9Andrew Sapperstein public void expandView() { 76226e4fd316954167654b207aba998b7a7e43ade9Andrew Sapperstein mTileGrid.setVisibility(VISIBLE); 77226e4fd316954167654b207aba998b7a7e43ade9Andrew Sapperstein mAttachmentLayout.setVisibility(VISIBLE); 78ce2a25c96f9274d9c2944a5d197a3d3dd75d71eaAndrew Sapperstein 79ce2a25c96f9274d9c2944a5d197a3d3dd75d71eaAndrew Sapperstein InputMethodManager imm = (InputMethodManager) getContext().getSystemService( 80ce2a25c96f9274d9c2944a5d197a3d3dd75d71eaAndrew Sapperstein Context.INPUT_METHOD_SERVICE); 81ce2a25c96f9274d9c2944a5d197a3d3dd75d71eaAndrew Sapperstein if (imm != null) { 82ce2a25c96f9274d9c2944a5d197a3d3dd75d71eaAndrew Sapperstein imm.hideSoftInputFromWindow(getWindowToken(), 0); 83ce2a25c96f9274d9c2944a5d197a3d3dd75d71eaAndrew Sapperstein } 84226e4fd316954167654b207aba998b7a7e43ade9Andrew Sapperstein } 85226e4fd316954167654b207aba998b7a7e43ade9Andrew Sapperstein 867b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira /** 877b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Set a listener for changes to the attachments. 887b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */ 8940882430c874ff29890ec6fa31f7649788cbc470mindyp public void setAttachmentChangesListener(AttachmentAddedOrDeletedListener listener) { 907b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira mChangeListener = listener; 917b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 927b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 937b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira /** 94865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein * Adds an attachment and updates the ui accordingly. 957b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */ 96865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein private void addAttachment(final Attachment attachment) { 97865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein mAttachments.add(attachment); 98865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein 99865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein // If the attachment is inline do not display this attachment. 100865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein if (attachment.isInlineAttachment()) { 101865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein return; 102865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein } 103865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein 1047b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira if (!isShown()) { 1057b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira setVisibility(View.VISIBLE); 1067b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 107226e4fd316954167654b207aba998b7a7e43ade9Andrew Sapperstein 108226e4fd316954167654b207aba998b7a7e43ade9Andrew Sapperstein expandView(); 1097b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 110237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein // If we have an attachment that should be shown in a tiled look, 111237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein // set up the tile and add it to the tile grid. 112237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein if (AttachmentTile.isTiledAttachment(attachment)) { 113237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein final ComposeAttachmentTile attachmentTile = 114237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein mTileGrid.addComposeTileFromAttachment(attachment); 115237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein attachmentTile.addDeleteListener(new OnClickListener() { 116237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein @Override 117237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein public void onClick(View v) { 118237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein deleteAttachment(attachmentTile, attachment); 119237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein } 120237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein }); 121237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein // Otherwise, use the old bar look and add it to the new 122237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein // inner LinearLayout. 123237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein } else { 124237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein final AttachmentComposeView attachmentView = 125237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein new AttachmentComposeView(getContext(), attachment); 126237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein 127237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein attachmentView.addDeleteListener(new OnClickListener() { 128237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein @Override 129237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein public void onClick(View v) { 130237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein deleteAttachment(attachmentView, attachment); 131237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein } 132237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein }); 133237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein 134237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein 135237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein mAttachmentLayout.addView(attachmentView, new LinearLayout.LayoutParams( 136237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein LinearLayout.LayoutParams.MATCH_PARENT, 137237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein LinearLayout.LayoutParams.MATCH_PARENT)); 138237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein } 13940882430c874ff29890ec6fa31f7649788cbc470mindyp if (mChangeListener != null) { 14040882430c874ff29890ec6fa31f7649788cbc470mindyp mChangeListener.onAttachmentAdded(); 14140882430c874ff29890ec6fa31f7649788cbc470mindyp } 1427b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 1437b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 1447b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira @VisibleForTesting 145237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein protected void deleteAttachment(final View attachmentView, 1467b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira final Attachment attachment) { 1477b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira mAttachments.remove(attachment); 148237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein ((ViewGroup) attachmentView.getParent()).removeView(attachmentView); 1497b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira if (mChangeListener != null) { 1507b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira mChangeListener.onAttachmentDeleted(); 1517b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 152ca87d6d907e6d00670c8af607cf82cf7a73f7b39Andrew Sapperstein } 153ca87d6d907e6d00670c8af607cf82cf7a73f7b39Andrew Sapperstein 1547b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira /** 1557b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Get all attachments being managed by this view. 1567b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * @return attachments. 1577b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */ 1587b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira public ArrayList<Attachment> getAttachments() { 1597b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira return mAttachments; 1607b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 1617b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 1627b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira /** 16362066e47fe69c33b60ed970e46809ba81d044064Mark Wei * Get all attachments previews that have been loaded 16462066e47fe69c33b60ed970e46809ba81d044064Mark Wei * @return attachments previews. 16562066e47fe69c33b60ed970e46809ba81d044064Mark Wei */ 16662066e47fe69c33b60ed970e46809ba81d044064Mark Wei public ArrayList<AttachmentPreview> getAttachmentPreviews() { 16762066e47fe69c33b60ed970e46809ba81d044064Mark Wei return mTileGrid.getAttachmentPreviews(); 16862066e47fe69c33b60ed970e46809ba81d044064Mark Wei } 16962066e47fe69c33b60ed970e46809ba81d044064Mark Wei 17062066e47fe69c33b60ed970e46809ba81d044064Mark Wei /** 17162066e47fe69c33b60ed970e46809ba81d044064Mark Wei * Call this on restore instance state so previews persist across configuration changes 17262066e47fe69c33b60ed970e46809ba81d044064Mark Wei */ 17362066e47fe69c33b60ed970e46809ba81d044064Mark Wei public void setAttachmentPreviews(ArrayList<AttachmentPreview> previews) { 17462066e47fe69c33b60ed970e46809ba81d044064Mark Wei mTileGrid.setAttachmentPreviews(previews); 17562066e47fe69c33b60ed970e46809ba81d044064Mark Wei } 17662066e47fe69c33b60ed970e46809ba81d044064Mark Wei 17762066e47fe69c33b60ed970e46809ba81d044064Mark Wei /** 1787b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Delete all attachments being managed by this view. 1797b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */ 1807b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira public void deleteAllAttachments() { 1817b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira mAttachments.clear(); 182237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein mTileGrid.removeAllViews(); 183237129ddf57a1e25469d8990322fb7913e18ae20Andrew Sapperstein mAttachmentLayout.removeAllViews(); 184e7d87dda85e55d1cd40a840c7d99aef73c65641dAndrew Sapperstein setVisibility(GONE); 1857b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 1867b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 1877b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira /** 1887b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Get the total size of all attachments currently in this view. 1897b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */ 190865ae9ca2606d901ce33259bae5cfe71beaa18baAndrew Sapperstein private long getTotalAttachmentsSize() { 1917aee8f75f07eb39ecacdf0630d2199211aea5718Mindy Pereira long totalSize = 0; 1927b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira for (Attachment attachment : mAttachments) { 1937aee8f75f07eb39ecacdf0630d2199211aea5718Mindy Pereira totalSize += attachment.size; 1947b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 1957b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira return totalSize; 1967b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 1977b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira 1987b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira /** 19940882430c874ff29890ec6fa31f7649788cbc470mindyp * Interface to implement to be notified about changes to the attachments 20040882430c874ff29890ec6fa31f7649788cbc470mindyp * explicitly made by the user. 2017b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */ 20240882430c874ff29890ec6fa31f7649788cbc470mindyp public interface AttachmentAddedOrDeletedListener { 2037b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira public void onAttachmentDeleted(); 20440882430c874ff29890ec6fa31f7649788cbc470mindyp 20540882430c874ff29890ec6fa31f7649788cbc470mindyp public void onAttachmentAdded(); 2067b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira } 2079932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 2089932dee248718d2cec993d93b5115973d1389d0aMindy Pereira /** 2095c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * Generate an {@link Attachment} object for a given local content URI. Attempts to populate 2105c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * the {@link Attachment#name}, {@link Attachment#size}, and {@link Attachment#contentType} 2115c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * fields using a {@link ContentResolver}. 2129932dee248718d2cec993d93b5115973d1389d0aMindy Pereira * 2135c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * @param contentUri 2145c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * @return an Attachment object 2155c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * @throws AttachmentFailureException 2169932dee248718d2cec993d93b5115973d1389d0aMindy Pereira */ 2175c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang public Attachment generateLocalAttachment(Uri contentUri) throws AttachmentFailureException { 2185c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang if (contentUri == null || TextUtils.isEmpty(contentUri.getPath())) { 2193cd4f40dab2c0810cb5ecd77256abf022863b2b5Mindy Pereira throw new AttachmentFailureException("Failed to create local attachment"); 2209932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2219932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 22291ede36047ebe20cb2f4783c71454d1791abc95cAndy Huang // FIXME: do not query resolver for type on the UI thread 22391ede36047ebe20cb2f4783c71454d1791abc95cAndy Huang final ContentResolver contentResolver = getContext().getContentResolver(); 22491ede36047ebe20cb2f4783c71454d1791abc95cAndy Huang String contentType = contentResolver.getType(contentUri); 22591ede36047ebe20cb2f4783c71454d1791abc95cAndy Huang 2269932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (contentType == null) contentType = ""; 2279932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 2285c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang final Attachment attachment = new Attachment(); 2295c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang attachment.uri = null; // URI will be assigned by the provider upon send/save 2308750066b10f80e2a8080016973b3296d76d18266Mark Wei attachment.setName(null); 2319932dee248718d2cec993d93b5115973d1389d0aMindy Pereira attachment.size = 0; 2325c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang attachment.contentUri = contentUri; 2333ca3bf8538bbabcaf9d86818e25c40f3a9d9b0c9Mark Wei attachment.thumbnailUri = contentUri; 2349932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 2359932dee248718d2cec993d93b5115973d1389d0aMindy Pereira Cursor metadataCursor = null; 2369932dee248718d2cec993d93b5115973d1389d0aMindy Pereira try { 2379932dee248718d2cec993d93b5115973d1389d0aMindy Pereira metadataCursor = contentResolver.query( 2385c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang contentUri, new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, 2399932dee248718d2cec993d93b5115973d1389d0aMindy Pereira null, null, null); 2409932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (metadataCursor != null) { 2419932dee248718d2cec993d93b5115973d1389d0aMindy Pereira try { 2429932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (metadataCursor.moveToNext()) { 2438750066b10f80e2a8080016973b3296d76d18266Mark Wei attachment.setName(metadataCursor.getString(0)); 2449932dee248718d2cec993d93b5115973d1389d0aMindy Pereira attachment.size = metadataCursor.getInt(1); 2459932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2469932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } finally { 2479932dee248718d2cec993d93b5115973d1389d0aMindy Pereira metadataCursor.close(); 2489932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2499932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2509932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } catch (SQLiteException ex) { 2519932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // One of the two columns is probably missing, let's make one more attempt to get at 2529932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // least one. 2539932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // Note that the documentations in Intent#ACTION_OPENABLE and 2549932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // OpenableColumns seem to contradict each other about whether these columns are 2559932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // required, but it doesn't hurt to fail properly. 2569932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 2579932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // Let's try to get DISPLAY_NAME 2589932dee248718d2cec993d93b5115973d1389d0aMindy Pereira try { 2595c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang metadataCursor = getOptionalColumn(contentResolver, contentUri, 2605c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang OpenableColumns.DISPLAY_NAME); 2619932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (metadataCursor != null && metadataCursor.moveToNext()) { 2628750066b10f80e2a8080016973b3296d76d18266Mark Wei attachment.setName(metadataCursor.getString(0)); 2639932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2649932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } finally { 2659932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (metadataCursor != null) metadataCursor.close(); 2669932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2679932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 2689932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // Let's try to get SIZE 2699932dee248718d2cec993d93b5115973d1389d0aMindy Pereira try { 2709932dee248718d2cec993d93b5115973d1389d0aMindy Pereira metadataCursor = 2715c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang getOptionalColumn(contentResolver, contentUri, OpenableColumns.SIZE); 2729932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (metadataCursor != null && metadataCursor.moveToNext()) { 2739932dee248718d2cec993d93b5115973d1389d0aMindy Pereira attachment.size = metadataCursor.getInt(0); 2749932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } else { 2759932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // Unable to get the size from the metadata cursor. Open the file and seek. 2765c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang attachment.size = getSizeFromFile(contentUri, contentResolver); 2779932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2789932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } finally { 2799932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (metadataCursor != null) metadataCursor.close(); 2809932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2819932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } catch (SecurityException e) { 2829932dee248718d2cec993d93b5115973d1389d0aMindy Pereira throw new AttachmentFailureException("Security Exception from attachment uri", e); 2839932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 2849932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 2858750066b10f80e2a8080016973b3296d76d18266Mark Wei if (attachment.getName() == null) { 2868750066b10f80e2a8080016973b3296d76d18266Mark Wei attachment.setName(contentUri.getLastPathSegment()); 2879932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 28802a653d50ec553acf629069ac0ac2348e09077dbAlon Albert if (attachment.size == 0) { 28902a653d50ec553acf629069ac0ac2348e09077dbAlon Albert // if the attachment is not a content:// for example, a file:// URI 29002a653d50ec553acf629069ac0ac2348e09077dbAlon Albert attachment.size = getSizeFromFile(contentUri, contentResolver); 29102a653d50ec553acf629069ac0ac2348e09077dbAlon Albert } 2929932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 2938750066b10f80e2a8080016973b3296d76d18266Mark Wei attachment.setContentType(contentType); 2945c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang return attachment; 2955c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang } 2965c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang 2975c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang /** 2985c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * Adds an attachment of either local or remote origin, checking to see if the attachment 2995c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * exceeds file size limits. 3005c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * @param account 3015c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * @param attachment the attachment to be added. 3025c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * 3035c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * @return size of the attachment added. 3045c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang * @throws AttachmentFailureException if an error occurs adding the attachment. 3055c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang */ 3065c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang public long addAttachment(Account account, Attachment attachment) 3075c5fd571b40a15b95ea5a5d6033f377706ffd0fbAndy Huang throws AttachmentFailureException { 30821970daede1dcd7580adf6245df72f455ed09d2aPaul Westbrook final int maxSize = account.settings.getMaxAttachmentSize(); 3099932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 3109932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // Error getting the size or the size was too big. 3119932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (attachment.size == -1 || attachment.size > maxSize) { 312434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei throw new AttachmentFailureException( 313434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei "Attachment too large to attach", R.string.too_large_to_attach_single); 3149932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } else if ((getTotalAttachmentsSize() 3159932dee248718d2cec993d93b5115973d1389d0aMindy Pereira + attachment.size) > maxSize) { 316434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei throw new AttachmentFailureException( 317434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei "Attachment too large to attach", R.string.too_large_to_attach_additional); 3189932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } else { 3199932dee248718d2cec993d93b5115973d1389d0aMindy Pereira addAttachment(attachment); 3209932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 3219932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 3229932dee248718d2cec993d93b5115973d1389d0aMindy Pereira return attachment.size; 3239932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 3249932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 3253b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy private static int getSizeFromFile(Uri uri, ContentResolver contentResolver) { 3269932dee248718d2cec993d93b5115973d1389d0aMindy Pereira int size = -1; 3279932dee248718d2cec993d93b5115973d1389d0aMindy Pereira ParcelFileDescriptor file = null; 3289932dee248718d2cec993d93b5115973d1389d0aMindy Pereira try { 3299932dee248718d2cec993d93b5115973d1389d0aMindy Pereira file = contentResolver.openFileDescriptor(uri, "r"); 3309932dee248718d2cec993d93b5115973d1389d0aMindy Pereira size = (int) file.getStatSize(); 3319932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } catch (FileNotFoundException e) { 33221970daede1dcd7580adf6245df72f455ed09d2aPaul Westbrook LogUtils.w(LOG_TAG, e, "Error opening file to obtain size."); 3339932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } finally { 3349932dee248718d2cec993d93b5115973d1389d0aMindy Pereira try { 3359932dee248718d2cec993d93b5115973d1389d0aMindy Pereira if (file != null) { 3369932dee248718d2cec993d93b5115973d1389d0aMindy Pereira file.close(); 3379932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 3389932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } catch (IOException e) { 3399932dee248718d2cec993d93b5115973d1389d0aMindy Pereira LogUtils.w(LOG_TAG, "Error closing file opened to obtain size."); 3409932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 3419932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 34221970daede1dcd7580adf6245df72f455ed09d2aPaul Westbrook // We only want to return a non-negative value. (ParcelFileDescriptor#getStatSize() will 34321970daede1dcd7580adf6245df72f455ed09d2aPaul Westbrook // return -1 if the fd is not a file 34421970daede1dcd7580adf6245df72f455ed09d2aPaul Westbrook return Math.max(size, 0); 3459932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 3469932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 3479932dee248718d2cec993d93b5115973d1389d0aMindy Pereira /** 3489932dee248718d2cec993d93b5115973d1389d0aMindy Pereira * @return a cursor to the requested column or null if an exception occurs while trying 3499932dee248718d2cec993d93b5115973d1389d0aMindy Pereira * to query it. 3509932dee248718d2cec993d93b5115973d1389d0aMindy Pereira */ 3513b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy private static Cursor getOptionalColumn(ContentResolver contentResolver, Uri uri, 3523b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy String columnName) { 3539932dee248718d2cec993d93b5115973d1389d0aMindy Pereira Cursor result = null; 3549932dee248718d2cec993d93b5115973d1389d0aMindy Pereira try { 3559932dee248718d2cec993d93b5115973d1389d0aMindy Pereira result = contentResolver.query(uri, new String[]{columnName}, null, null, null); 3569932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } catch (SQLiteException ex) { 3579932dee248718d2cec993d93b5115973d1389d0aMindy Pereira // ignore, leave result null 3589932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 3599932dee248718d2cec993d93b5115973d1389d0aMindy Pereira return result; 36040882430c874ff29890ec6fa31f7649788cbc470mindyp } 36140882430c874ff29890ec6fa31f7649788cbc470mindyp 36240882430c874ff29890ec6fa31f7649788cbc470mindyp public void focusLastAttachment() { 363d8401e4849df494b815578bc4cc320013337c727mindyp Attachment lastAttachment = mAttachments.get(mAttachments.size() - 1); 364d8401e4849df494b815578bc4cc320013337c727mindyp View lastView = null; 365d8401e4849df494b815578bc4cc320013337c727mindyp int last = 0; 366d8401e4849df494b815578bc4cc320013337c727mindyp if (AttachmentTile.isTiledAttachment(lastAttachment)) { 367d8401e4849df494b815578bc4cc320013337c727mindyp last = mTileGrid.getChildCount() - 1; 368d8401e4849df494b815578bc4cc320013337c727mindyp if (last > 0) { 369d8401e4849df494b815578bc4cc320013337c727mindyp lastView = mTileGrid.getChildAt(last); 370d8401e4849df494b815578bc4cc320013337c727mindyp } 371d8401e4849df494b815578bc4cc320013337c727mindyp } else { 37232f5d8a714358b617d7676394b8fb084d6f06594mindyp last = mAttachmentLayout.getChildCount() - 1; 373d8401e4849df494b815578bc4cc320013337c727mindyp if (last > 0) { 37432f5d8a714358b617d7676394b8fb084d6f06594mindyp lastView = mAttachmentLayout.getChildAt(last); 375d8401e4849df494b815578bc4cc320013337c727mindyp } 376d8401e4849df494b815578bc4cc320013337c727mindyp } 377d8401e4849df494b815578bc4cc320013337c727mindyp if (lastView != null) { 378d8401e4849df494b815578bc4cc320013337c727mindyp lastView.requestFocus(); 379d8401e4849df494b815578bc4cc320013337c727mindyp } 3809932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 3819932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 3829932dee248718d2cec993d93b5115973d1389d0aMindy Pereira /** 3839932dee248718d2cec993d93b5115973d1389d0aMindy Pereira * Class containing information about failures when adding attachments. 3849932dee248718d2cec993d93b5115973d1389d0aMindy Pereira */ 3859932dee248718d2cec993d93b5115973d1389d0aMindy Pereira static class AttachmentFailureException extends Exception { 3869932dee248718d2cec993d93b5115973d1389d0aMindy Pereira private static final long serialVersionUID = 1L; 387434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei private final int errorRes; 3889932dee248718d2cec993d93b5115973d1389d0aMindy Pereira 389434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei public AttachmentFailureException(String detailMessage) { 390434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei super(detailMessage); 391434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei this.errorRes = R.string.generic_attachment_problem; 392434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei } 393434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei 394434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei public AttachmentFailureException(String error, int errorRes) { 3959932dee248718d2cec993d93b5115973d1389d0aMindy Pereira super(error); 396434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei this.errorRes = errorRes; 3979932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 398434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei 3999932dee248718d2cec993d93b5115973d1389d0aMindy Pereira public AttachmentFailureException(String detailMessage, Throwable throwable) { 4009932dee248718d2cec993d93b5115973d1389d0aMindy Pereira super(detailMessage, throwable); 401434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei this.errorRes = R.string.generic_attachment_problem; 402434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei } 403434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei 404434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei /** 405434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei * Get the error string resource that corresponds to this attachment failure. Always a valid 406434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei * string resource. 407434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei */ 408434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei public int getErrorRes() { 409434f294a3ae17a4f76342af38d6376f672a5d9edMark Wei return errorRes; 4109932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 4119932dee248718d2cec993d93b5115973d1389d0aMindy Pereira } 4127b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira} 413