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