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