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