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 18 package com.android.mail.ui; 19 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.graphics.Bitmap; 23 import android.net.Uri; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.text.TextUtils; 27 import android.util.AttributeSet; 28 import android.util.DisplayMetrics; 29 import android.view.View; 30 import android.widget.ImageView; 31 import android.widget.RelativeLayout; 32 import android.widget.TextView; 33 import android.widget.ImageView.ScaleType; 34 35 import com.android.ex.photo.util.ImageUtils; 36 import com.android.mail.R; 37 import com.android.mail.providers.Attachment; 38 import com.android.mail.utils.LogTag; 39 import com.android.mail.utils.AttachmentUtils; 40 import 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 */ 46 public 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.getContentType()); 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 flags=%d", attachment.getName(), attachment.state, 117 attachment.destination, attachment.downloadedSize, attachment.contentUri, 118 attachment.getContentType(), attachment.flags); 119 120 if ((attachment.flags & Attachment.FLAG_DUMMY_ATTACHMENT) != 0) { 121 // TODO: This is not an ideal string, but it's too late in KLP to add new strings. 122 mTitle.setText(R.string.load_more); 123 } else if (prevAttachment == null 124 || !TextUtils.equals(attachment.getName(), prevAttachment.getName())) { 125 mTitle.setText(attachment.getName()); 126 } 127 128 if (prevAttachment == null || attachment.size != prevAttachment.size) { 129 mAttachmentSizeText = AttachmentUtils.convertToHumanReadableSize(getContext(), 130 attachment.size); 131 mDisplayType = AttachmentUtils.getDisplayType(getContext(), attachment); 132 updateSubtitleText(); 133 } 134 135 ThumbnailLoadTask.setupThumbnailPreview(mThumbnailTask, this, attachment, prevAttachment); 136 } 137 138 private void updateSubtitleText() { 139 // TODO: make this a formatted resource when we have a UX design. 140 // not worth translation right now. 141 StringBuilder sb = new StringBuilder(); 142 sb.append(mAttachmentSizeText); 143 if (mDisplayType != null) { 144 sb.append(' '); 145 sb.append(mDisplayType); 146 } 147 mSubtitle.setText(sb.toString()); 148 } 149 150 @Override 151 public void setThumbnailToDefault() { 152 Bitmap cachedPreview = mAttachmentPreviewCache.get(mAttachment); 153 if (cachedPreview != null) { 154 setThumbnail(cachedPreview); 155 return; 156 } 157 mDefaultIcon.setVisibility(View.VISIBLE); 158 mDefaultThumbnailSet = true; 159 } 160 161 @Override 162 public void setThumbnail(Bitmap result) { 163 if (result == null) { 164 return; 165 } 166 167 // We got a real thumbnail; hide the default thumbnail. 168 mDefaultIcon.setVisibility(View.GONE); 169 170 final int maxSize = getResources().getInteger(R.integer.attachment_preview_max_size); 171 final int width = result.getWidth(); 172 final int height = result.getHeight(); 173 final int scaledWidth = width * getResources().getDisplayMetrics().densityDpi 174 / DisplayMetrics.DENSITY_DEFAULT; 175 final int scaledHeight = height * getResources().getDisplayMetrics().densityDpi 176 / DisplayMetrics.DENSITY_DEFAULT; 177 // ratio of the image 178 final float ratio = Math.min((float) width / height, (float) height / width); 179 180 final boolean large = width >= maxSize || scaledWidth >= mIcon.getWidth() 181 || height >= maxSize || scaledHeight >= mIcon.getHeight(); 182 final boolean skinny = 183 // the image is loooong 184 ratio < skinnyThresholdRatio && 185 // AND if the image was centered and cropped, the resulting 186 // image would still be loooong 187 !(scaledWidth >= mIcon.getHeight() * skinnyThresholdRatio 188 && scaledHeight >= mIcon.getWidth() * skinnyThresholdRatio); 189 LogUtils.d(LOG_TAG, "scaledWidth: %d, scaledHeight: %d, large: %b, skinny: %b", scaledWidth, 190 scaledHeight, large, skinny); 191 192 if (large) { 193 // preview fills up at least 1 dimension 194 if (skinny) { 195 // just center. The shorter dimension stays the same while the 196 // longer dimension is cropped 197 mIcon.setScaleType(ScaleType.CENTER); 198 } else { 199 // fill. Both dimensions are scaled to fill the box, the longer 200 // dimension is cropped 201 mIcon.setScaleType(ScaleType.CENTER_CROP); 202 } 203 } else { 204 // preview is small. just center 205 mIcon.setScaleType(ScaleType.CENTER); 206 } 207 208 mIcon.setImageBitmap(result); 209 mAttachmentPreviewCache.set(mAttachment, result); 210 mDefaultThumbnailSet = false; 211 } 212 213 @Override 214 public int getThumbnailWidth() { 215 return mIcon.getWidth(); 216 } 217 218 @Override 219 public int getThumbnailHeight() { 220 return mIcon.getHeight(); 221 } 222 223 @Override 224 public ContentResolver getResolver() { 225 return getContext().getContentResolver(); 226 } 227 228 @Override 229 public boolean bitmapSetToDefault() { 230 return mDefaultThumbnailSet; 231 } 232 233 public static final class AttachmentPreview implements Parcelable { 234 public String attachmentIdentifier; 235 public Bitmap preview; 236 237 @Override 238 public int describeContents() { 239 return 0; 240 } 241 242 @Override 243 public void writeToParcel(Parcel dest, int flags) { 244 dest.writeString(attachmentIdentifier); 245 dest.writeParcelable(preview, 0); 246 } 247 248 public static final Parcelable.Creator<AttachmentPreview> CREATOR 249 = new Parcelable.Creator<AttachmentPreview>() { 250 @Override 251 public AttachmentPreview createFromParcel(Parcel in) { 252 return new AttachmentPreview(in); 253 } 254 255 @Override 256 public AttachmentPreview[] newArray(int size) { 257 return new AttachmentPreview[size]; 258 } 259 }; 260 261 private AttachmentPreview(Parcel in) { 262 attachmentIdentifier = in.readString(); 263 preview = in.readParcelable(null); 264 } 265 266 public AttachmentPreview(Attachment attachment, Bitmap preview) { 267 this.attachmentIdentifier = attachment.getIdentifierUri().toString(); 268 this.preview = preview; 269 } 270 } 271 272 public interface AttachmentPreviewCache { 273 void set(Attachment attachment, Bitmap preview); 274 Bitmap get(Attachment attachment); 275 } 276 277 @Override 278 public void thumbnailLoadFailed() { 279 setThumbnailToDefault(); 280 } 281 } 282