MessageAttachmentTile.java revision 4c7d8b5f38bfa80192d457025b834c975d7d38a3
1/*
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.mail.browse;
19
20import android.content.ActivityNotFoundException;
21import android.content.Context;
22import android.content.Intent;
23import android.content.res.AssetFileDescriptor;
24import android.graphics.Bitmap;
25import android.graphics.BitmapFactory;
26import android.net.Uri;
27import android.os.AsyncTask;
28import android.util.AttributeSet;
29import android.view.LayoutInflater;
30import android.view.MenuItem;
31import android.view.View;
32import android.view.View.OnClickListener;
33import android.view.ViewGroup;
34import android.widget.ImageView;
35import android.widget.PopupMenu.OnMenuItemClickListener;
36import android.widget.RelativeLayout;
37
38import com.android.mail.R;
39import com.android.mail.photo.Intents;
40import com.android.mail.photo.Intents.PhotoViewIntentBuilder;
41import com.android.mail.photo.MailPhotoViewActivity;
42import com.android.mail.photo.util.ImageUtils;
43import com.android.mail.providers.Attachment;
44import com.android.mail.providers.UIProvider;
45import com.android.mail.providers.UIProvider.AttachmentDestination;
46import com.android.mail.utils.LogUtils;
47import com.android.mail.utils.Utils;
48
49import java.io.IOException;
50
51/**
52 * View for a single attachment in conversation view. Shows download status and allows launching
53 * intents to act on an attachment.
54 *
55 */
56public class MessageAttachmentTile extends RelativeLayout implements OnClickListener,
57        OnMenuItemClickListener, AttachmentViewInterface {
58
59    private Attachment mAttachment;
60    private ImageView mIcon;
61    private ImageView.ScaleType mIconScaleType;
62    private int mPhotoIndex;
63    private Uri mAttachmentsListUri;
64
65    private final AttachmentActionHandler mActionHandler;
66
67    private ThumbnailLoadTask mThumbnailTask;
68
69    private static final String LOG_TAG = new LogUtils().getLogTag();
70
71    private class ThumbnailLoadTask extends AsyncTask<Uri, Void, Bitmap> {
72
73        private final int mWidth;
74        private final int mHeight;
75
76        public ThumbnailLoadTask(int width, int height) {
77            mWidth = width;
78            mHeight = height;
79        }
80
81        @Override
82        protected Bitmap doInBackground(Uri... params) {
83            final Uri thumbnailUri = params[0];
84
85            AssetFileDescriptor fd = null;
86            Bitmap result = null;
87
88            try {
89                fd = getContext().getContentResolver().openAssetFileDescriptor(thumbnailUri, "r");
90                if (isCancelled() || fd == null) {
91                    return null;
92                }
93
94                final BitmapFactory.Options opts = new BitmapFactory.Options();
95                opts.inJustDecodeBounds = true;
96
97                BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts);
98                if (isCancelled() || opts.outWidth == -1 || opts.outHeight == -1) {
99                    return null;
100                }
101
102                opts.inJustDecodeBounds = false;
103
104                LogUtils.d(LOG_TAG, "in background, src w/h=%d/%d dst w/h=%d/%d, divider=%d",
105                        opts.outWidth, opts.outHeight, mWidth, mHeight, opts.inSampleSize);
106
107                result = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts);
108
109            } catch (Throwable t) {
110                LogUtils.e(LOG_TAG, t, "Unable to decode thumbnail %s", thumbnailUri);
111            } finally {
112                if (fd != null) {
113                    try {
114                        fd.close();
115                    } catch (IOException e) {
116                        LogUtils.e(LOG_TAG, e, "");
117                    }
118                }
119            }
120
121            return result;
122        }
123
124        @Override
125        protected void onPostExecute(Bitmap result) {
126            if (result == null) {
127                LogUtils.d(LOG_TAG, "back in UI thread, decode failed");
128                setThumbnailToDefault();
129                return;
130            }
131
132            LogUtils.d(LOG_TAG, "back in UI thread, decode success, w/h=%d/%d", result.getWidth(),
133                    result.getHeight());
134            mIcon.setImageBitmap(result);
135            mIcon.setScaleType(mIconScaleType);
136        }
137
138    }
139
140    public MessageAttachmentTile(Context context) {
141        this(context, null);
142    }
143
144    public MessageAttachmentTile(Context context, AttributeSet attrs) {
145        super(context, attrs);
146
147        mActionHandler = new AttachmentActionHandler(context, this);
148    }
149
150    public static MessageAttachmentTile inflate(LayoutInflater inflater, ViewGroup parent) {
151        MessageAttachmentTile view = (MessageAttachmentTile) inflater.inflate(
152                R.layout.conversation_message_attachment_tile, parent, false);
153        return view;
154    }
155
156    /**
157     * Render or update an attachment's view. This happens immediately upon instantiation, and
158     * repeatedly as status updates stream in, so only properties with new or changed values will
159     * cause sub-views to update.
160     *
161     */
162    public void render(Attachment attachment, Uri attachmentsListUri, int index) {
163        if (attachment == null) {
164            setVisibility(View.INVISIBLE);
165            return;
166        }
167
168        final Attachment prevAttachment = mAttachment;
169        mAttachment = attachment;
170        mActionHandler.setAttachment(mAttachment);
171        mAttachmentsListUri = attachmentsListUri;
172        mPhotoIndex = index;
173
174        LogUtils.d(LOG_TAG, "got attachment list row: name=%s state/dest=%d/%d dled=%d" +
175                " contentUri=%s MIME=%s", attachment.name, attachment.state,
176                attachment.destination, attachment.downloadedSize, attachment.contentUri,
177                attachment.contentType);
178
179        final Uri imageUri = attachment.getImageUri();
180        final Uri prevImageUri = (prevAttachment == null) ? null : prevAttachment.getImageUri();
181        // begin loading a thumbnail if this is an image and either the thumbnail or the original
182        // content is ready (and different from any existing image)
183        if (imageUri != null && (prevImageUri == null || !imageUri.equals(prevImageUri))) {
184            // cancel/dispose any existing task and start a new one
185            if (mThumbnailTask != null) {
186                mThumbnailTask.cancel(true);
187            }
188            mThumbnailTask = new ThumbnailLoadTask(mIcon.getWidth(), mIcon.getHeight());
189            mThumbnailTask.execute(imageUri);
190        } else if (imageUri == null) {
191            // not an image, or no thumbnail exists. fall back to default.
192            // async image load must separately ensure the default appears upon load failure.
193            setThumbnailToDefault();
194        }
195
196        mActionHandler.updateStatus();
197    }
198
199    private void setThumbnailToDefault() {
200        mIcon.setImageResource(R.drawable.ic_menu_attachment_holo_light);
201        mIcon.setScaleType(ImageView.ScaleType.CENTER);
202    }
203
204
205
206    @Override
207    protected void onFinishInflate() {
208        super.onFinishInflate();
209
210        mIcon = (ImageView) findViewById(R.id.attachment_tile_image);
211
212        setOnClickListener(this);
213
214        mIconScaleType = mIcon.getScaleType();
215    }
216
217    @Override
218    public void onClick(View v) {
219        onClick(v.getId(), v);
220    }
221
222    @Override
223    public boolean onMenuItemClick(MenuItem item) {
224        return onClick(item.getItemId(), null);
225    }
226
227    private boolean onClick(int res, View v) {
228        mActionHandler.showAttachment(AttachmentDestination.CACHE);
229
230        return true;
231    }
232
233    public void viewAttachment() {
234        if (ImageUtils.isImageMimeType(Utils.normalizeMimeType(mAttachment.contentType))) {
235            final PhotoViewIntentBuilder builder =
236                    Intents.newPhotoViewIntentBuilder(getContext(), MailPhotoViewActivity.class);
237            builder.setPhotoName(mAttachment.name)
238                .setPhotosUri(mAttachmentsListUri.toString())
239                .setProjection(UIProvider.ATTACHMENT_PROJECTION)
240                .setPhotoIndex(mPhotoIndex);
241
242            getContext().startActivity(builder.build());
243            return;
244        }
245
246        Intent intent = new Intent(Intent.ACTION_VIEW);
247        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
248                | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
249        Utils.setIntentDataAndTypeAndNormalize(intent, mAttachment.contentUri,
250                mAttachment.contentType);
251        try {
252            getContext().startActivity(intent);
253        } catch (ActivityNotFoundException e) {
254            // couldn't find activity for View intent
255            LogUtils.e(LOG_TAG, "Coun't find Activity for intent", e);
256        }
257    }
258
259    public void updateProgress(boolean showDeterminateProgress) {
260    }
261
262    public void updateStatus() {
263    }
264}
265