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