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