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