AttachmentTile.java revision e16b4dddf6a310e0e70908fdffd7bad45d97a99c
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.ui; 19 20import android.content.ContentResolver; 21import android.content.Context; 22import android.graphics.Bitmap; 23import android.net.Uri; 24import android.os.Parcel; 25import android.os.Parcelable; 26import android.text.TextUtils; 27import android.util.AttributeSet; 28import android.util.DisplayMetrics; 29import android.view.View; 30import android.widget.ImageView; 31import android.widget.RelativeLayout; 32import android.widget.TextView; 33import android.widget.ImageView.ScaleType; 34 35import com.android.ex.photo.util.ImageUtils; 36import com.android.mail.R; 37import com.android.mail.providers.Attachment; 38import com.android.mail.utils.LogTag; 39import com.android.mail.utils.AttachmentUtils; 40import com.android.mail.utils.LogUtils; 41 42/** 43 * Base class for attachment tiles that handles the work of fetching and displaying the bitmaps for 44 * the tiles. 45 */ 46public class AttachmentTile extends RelativeLayout implements AttachmentBitmapHolder { 47 protected Attachment mAttachment; 48 private ImageView mIcon; 49 private ImageView mDefaultIcon; 50 private ThumbnailLoadTask mThumbnailTask; 51 private TextView mTitle; 52 private TextView mSubtitle; 53 private String mAttachmentSizeText; 54 private String mDisplayType; 55 private boolean mDefaultThumbnailSet; 56 private AttachmentPreviewCache mAttachmentPreviewCache; 57 58 private static final String LOG_TAG = LogTag.getLogTag(); 59 // previews with width/height or height/width less than this value will be 60 // considered skinny 61 private static final float skinnyThresholdRatio = 0.5f; 62 63 64 /** 65 * Returns true if the attachment should be rendered as a tile. with a large image preview. 66 * @param attachment the attachment to render 67 * @return true if the attachment should be rendered as a tile 68 */ 69 public static boolean isTiledAttachment(final Attachment attachment) { 70 return ImageUtils.isImageMimeType(attachment.contentType); 71 } 72 73 public AttachmentTile(Context context) { 74 this(context, null); 75 } 76 77 public AttachmentTile(Context context, AttributeSet attrs) { 78 super(context, attrs); 79 mDefaultThumbnailSet = true; 80 } 81 82 @Override 83 protected void onFinishInflate() { 84 super.onFinishInflate(); 85 86 mTitle = (TextView) findViewById(R.id.attachment_tile_title); 87 mSubtitle = (TextView) findViewById(R.id.attachment_tile_subtitle); 88 mIcon = (ImageView) findViewById(R.id.attachment_tile_image); 89 mDefaultIcon = (ImageView) findViewById(R.id.attachment_default_image); 90 } 91 92 @Override 93 protected void onLayout(boolean changed, int l, int t, int r, int b) { 94 super.onLayout(changed, l, t, r, b); 95 96 ThumbnailLoadTask.setupThumbnailPreview(mThumbnailTask, this, mAttachment, null); 97 } 98 99 /** 100 * Render or update an attachment's view. This happens immediately upon instantiation, and 101 * repeatedly as status updates stream in, so only properties with new or changed values will 102 * cause sub-views to update. 103 */ 104 public void render(Attachment attachment, Uri attachmentsListUri, int index, 105 AttachmentPreviewCache attachmentPreviewCache, boolean loaderResult) { 106 if (attachment == null) { 107 setVisibility(View.INVISIBLE); 108 return; 109 } 110 111 final Attachment prevAttachment = mAttachment; 112 mAttachment = attachment; 113 mAttachmentPreviewCache = attachmentPreviewCache; 114 115 LogUtils.d(LOG_TAG, "got attachment list row: name=%s state/dest=%d/%d dled=%d" + 116 " contentUri=%s MIME=%s", attachment.name, attachment.state, 117 attachment.destination, attachment.downloadedSize, attachment.contentUri, 118 attachment.contentType); 119 120 if (prevAttachment == null || !TextUtils.equals(attachment.name, prevAttachment.name)) { 121 mTitle.setText(attachment.name); 122 } 123 124 if (prevAttachment == null || attachment.size != prevAttachment.size) { 125 mAttachmentSizeText = AttachmentUtils.convertToHumanReadableSize(getContext(), 126 attachment.size); 127 mDisplayType = AttachmentUtils.getDisplayType(getContext(), attachment); 128 updateSubtitleText(); 129 } 130 131 ThumbnailLoadTask.setupThumbnailPreview(mThumbnailTask, this, attachment, prevAttachment); 132 } 133 134 private void updateSubtitleText() { 135 // TODO: make this a formatted resource when we have a UX design. 136 // not worth translation right now. 137 StringBuilder sb = new StringBuilder(); 138 sb.append(mAttachmentSizeText); 139 sb.append(' '); 140 sb.append(mDisplayType); 141 mSubtitle.setText(sb.toString()); 142 } 143 144 @Override 145 public void setThumbnailToDefault() { 146 Bitmap cachedPreview = mAttachmentPreviewCache.get(mAttachment); 147 if (cachedPreview != null) { 148 setThumbnail(cachedPreview); 149 return; 150 } 151 mDefaultIcon.setVisibility(View.VISIBLE); 152 mDefaultThumbnailSet = true; 153 } 154 155 @Override 156 public void setThumbnail(Bitmap result) { 157 if (result == null) { 158 return; 159 } 160 161 // We got a real thumbnail; hide the default thumbnail. 162 mDefaultIcon.setVisibility(View.GONE); 163 164 final int maxSize = getResources().getInteger(R.integer.attachment_preview_max_size); 165 final int width = result.getWidth(); 166 final int height = result.getHeight(); 167 final int scaledWidth = width * getResources().getDisplayMetrics().densityDpi 168 / DisplayMetrics.DENSITY_DEFAULT; 169 final int scaledHeight = height * getResources().getDisplayMetrics().densityDpi 170 / DisplayMetrics.DENSITY_DEFAULT; 171 // ratio of the image 172 final float ratio = Math.min((float) width / height, (float) height / width); 173 174 final boolean large = width >= maxSize || scaledWidth >= mIcon.getWidth() 175 || height >= maxSize || scaledHeight >= mIcon.getHeight(); 176 final boolean skinny = 177 // the image is loooong 178 ratio < skinnyThresholdRatio && 179 // AND if the image was centered and cropped, the resulting 180 // image would still be loooong 181 !(scaledWidth >= mIcon.getHeight() * skinnyThresholdRatio 182 && scaledHeight >= mIcon.getWidth() * skinnyThresholdRatio); 183 LogUtils.d(LOG_TAG, "scaledWidth: %d, scaledHeight: %d, large: %b, skinny: %b", scaledWidth, 184 scaledHeight, large, skinny); 185 186 if (large) { 187 // preview fills up at least 1 dimension 188 if (skinny) { 189 // just center. The shorter dimension stays the same while the 190 // longer dimension is cropped 191 mIcon.setScaleType(ScaleType.CENTER); 192 } else { 193 // fill. Both dimensions are scaled to fill the box, the longer 194 // dimension is cropped 195 mIcon.setScaleType(ScaleType.CENTER_CROP); 196 } 197 } else { 198 // preview is small. just center 199 mIcon.setScaleType(ScaleType.CENTER); 200 } 201 202 mIcon.setImageBitmap(result); 203 mAttachmentPreviewCache.set(mAttachment, result); 204 mDefaultThumbnailSet = false; 205 } 206 207 @Override 208 public int getThumbnailWidth() { 209 return mIcon.getWidth(); 210 } 211 212 @Override 213 public int getThumbnailHeight() { 214 return mIcon.getHeight(); 215 } 216 217 @Override 218 public ContentResolver getResolver() { 219 return getContext().getContentResolver(); 220 } 221 222 @Override 223 public boolean bitmapSetToDefault() { 224 return mDefaultThumbnailSet; 225 } 226 227 public static final class AttachmentPreview implements Parcelable { 228 public String attachmentIdentifier; 229 public Bitmap preview; 230 231 @Override 232 public int describeContents() { 233 return 0; 234 } 235 236 @Override 237 public void writeToParcel(Parcel dest, int flags) { 238 dest.writeString(attachmentIdentifier); 239 dest.writeParcelable(preview, 0); 240 } 241 242 public static final Parcelable.Creator<AttachmentPreview> CREATOR 243 = new Parcelable.Creator<AttachmentPreview>() { 244 @Override 245 public AttachmentPreview createFromParcel(Parcel in) { 246 return new AttachmentPreview(in); 247 } 248 249 @Override 250 public AttachmentPreview[] newArray(int size) { 251 return new AttachmentPreview[size]; 252 } 253 }; 254 255 private AttachmentPreview(Parcel in) { 256 attachmentIdentifier = in.readString(); 257 preview = in.readParcelable(null); 258 } 259 260 public AttachmentPreview(Attachment attachment, Bitmap preview) { 261 this.attachmentIdentifier = AttachmentUtils.getIdentifier(attachment); 262 this.preview = preview; 263 } 264 } 265 266 public interface AttachmentPreviewCache { 267 void set(Attachment attachment, Bitmap preview); 268 Bitmap get(Attachment attachment); 269 } 270} 271