AttachmentTileGrid.java revision a6e8789043b567a54cb930dd785fba573cf65f5c
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.mail.ui;
18
19import android.app.FragmentManager;
20import android.content.Context;
21import android.graphics.Bitmap;
22import android.net.Uri;
23import android.util.AttributeSet;
24import android.view.LayoutInflater;
25import android.view.View;
26import android.widget.FrameLayout;
27
28import com.android.mail.R;
29import com.android.mail.browse.MessageAttachmentTile;
30import com.android.mail.compose.ComposeAttachmentTile;
31import com.android.mail.providers.Attachment;
32import com.android.mail.ui.AttachmentTile.AttachmentPreview;
33import com.android.mail.ui.AttachmentTile.AttachmentPreviewCache;
34import com.android.mail.utils.AttachmentUtils;
35import com.google.common.collect.Lists;
36import com.google.common.collect.Maps;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.List;
41
42/**
43 * Acts as a grid composed of {@link AttachmentTile}s.
44 */
45public class AttachmentTileGrid extends FrameLayout implements AttachmentPreviewCache {
46    private LayoutInflater mInflater;
47    private Uri mAttachmentsListUri;
48    private final int mTileMinSize;
49    private final int mTileMaxSize;
50    private int mColumnCount;
51    private List<Attachment> mAttachments;
52    private HashMap<String, AttachmentPreview> mAttachmentPreviews;
53    private FragmentManager mFragmentManager;
54
55    public AttachmentTileGrid(Context context, AttributeSet attrs) {
56        super(context, attrs);
57        mInflater = LayoutInflater.from(context);
58        mTileMinSize = context.getResources()
59                .getDimensionPixelSize(R.dimen.attachment_tile_min_size);
60        mTileMaxSize = context.getResources()
61                .getDimensionPixelSize(R.dimen.attachment_tile_max_size);
62        mAttachmentPreviews = Maps.newHashMap();
63    }
64
65    /**
66     * Configures the grid to add {@link Attachment}s information to the views.
67     */
68    public void configureGrid(FragmentManager fragmentManager, Uri attachmentsListUri,
69            List<Attachment> list, boolean loaderResult) {
70        mFragmentManager = fragmentManager;
71        mAttachmentsListUri = attachmentsListUri;
72        mAttachments = list;
73        // Adding tiles to grid and filling in attachment information
74        int index = 0;
75        for (Attachment attachment : list) {
76            addMessageTileFromAttachment(attachment, index++, loaderResult);
77        }
78    }
79
80    private void addMessageTileFromAttachment(Attachment attachment, int index,
81            boolean loaderResult) {
82        final MessageAttachmentTile attachmentTile;
83
84        if (getChildCount() <= index) {
85            attachmentTile = MessageAttachmentTile.inflate(mInflater, this);
86            attachmentTile.initialize(mFragmentManager);
87            addView(attachmentTile);
88        } else {
89            attachmentTile = (MessageAttachmentTile) getChildAt(index);
90        }
91
92        attachmentTile.render(attachment, mAttachmentsListUri, index, this, loaderResult);
93    }
94
95    public ComposeAttachmentTile addComposeTileFromAttachment(Attachment attachment) {
96        final ComposeAttachmentTile attachmentTile =
97                ComposeAttachmentTile.inflate(mInflater, this);
98
99        addView(attachmentTile);
100        attachmentTile.render(attachment, null, -1, this, false);
101
102        return attachmentTile;
103    }
104
105    @Override
106    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
107        onMeasureForTiles(widthMeasureSpec);
108    }
109
110    private void onMeasureForTiles(int widthMeasureSpec) {
111        final int width = MeasureSpec.getSize(widthMeasureSpec);
112
113        final int childCount = getChildCount();
114        if (childCount == 0) {
115            // Just in case...
116            setMeasuredDimension(width, 0);
117            return;
118        }
119
120        // Divide width by minimum tile size to get the number of columns.
121        // Truncation will ensure that the minimum will always be the minimum
122        // but that the tiles can (and likely will) grow larger.
123        mColumnCount = width / mTileMinSize;
124
125        // Just in case...
126        if (mColumnCount == 0) {
127            mColumnCount = 1;
128        }
129
130        // 1. Calculate image size.
131        //      = [total width] / [child count]
132        //
133        // 2. Set it to width/height of each children.
134        //    If we have a remainder, some tiles will have
135        //    1 pixel larger width than its height.
136        //
137        // 3. Set the dimensions of itself.
138        //    Let width = given width.
139        //    Let height = image size + bottom padding.
140
141        // b/7204811: Hack for measure. set imageSize to max size. We'd rather have too much
142        // whitespace, than overlapping overlays
143        final int imageSize = mTileMaxSize; //(width) / mColumnCount;
144        final int remainder = width - (imageSize * mColumnCount);
145
146        for (int i = 0; i < childCount; i++) {
147            final View child = getChildAt(i);
148            // Compensate for the remainder
149            final int childWidth = imageSize + (i < remainder ? 1 : 0);
150            child.measure(
151                    MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
152                    MeasureSpec.makeMeasureSpec(imageSize, MeasureSpec.EXACTLY)
153                    );
154        }
155
156        // Calculate the number of rows so we can get the proper height.
157        // Then multiply by the height of one tile to get the grid height.
158        final int numRows = ((childCount - 1) / mColumnCount) + 1;
159        setMeasuredDimension(width,
160                numRows*(imageSize + getChildAt(0).getPaddingBottom()));
161    }
162
163    @Override
164    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
165        onLayoutForTiles();
166    }
167
168    private void onLayoutForTiles() {
169        final int count = getChildCount();
170        int childLeft = 0;
171        int childTop = 0;
172        boolean skipBeginningOfRowFirstTime = true;
173
174        // Layout the grid.
175        for (int i = 0; i < count; i++) {
176            final View child = getChildAt(i);
177
178            // Note MeasuredWidth and MeasuredHeight include the padding.
179            final int childWidth = child.getMeasuredWidth();
180            final int childHeight = child.getMeasuredHeight();
181
182            // If we're at the beginning of a row and it is not the first row
183            // in the grid, reset childLeft to 0 and update childTop
184            // to reflect the top of the new row.
185            if (!skipBeginningOfRowFirstTime && i % mColumnCount == 0) {
186                childLeft = 0;
187                childTop += childHeight;
188            } else {
189                skipBeginningOfRowFirstTime = false;
190            }
191
192            child.layout(childLeft, childTop,
193                    childLeft + childWidth, childTop + childHeight);
194            childLeft += childWidth;
195        }
196    }
197
198    @Override
199    public void sendAccessibilityEvent(int eventType) {
200        // This method is called when the child tile is INVISIBLE (meaning "empty"), and the
201        // Accessibility Manager needs to find alternative content description to speak.
202        // Here, we ignore the default behavior, since we don't want to let the manager speak
203        // a contact name for the tile next to the INVISIBLE tile.
204    }
205
206    public List<Attachment> getAttachments() {
207        return mAttachments;
208    }
209
210    public ArrayList<AttachmentPreview> getAttachmentPreviews() {
211        return Lists.newArrayList(mAttachmentPreviews.values());
212    }
213
214    public void setAttachmentPreviews(ArrayList<AttachmentPreview> previews) {
215        if (previews != null) {
216            for (AttachmentPreview preview : previews) {
217                mAttachmentPreviews.put(preview.attachmentIdentifier, preview);
218            }
219        }
220    }
221
222    /*
223     * Save the preview for an attachment
224     */
225    @Override
226    public void set(Attachment attachment, Bitmap preview) {
227        final String attachmentIdentifier = AttachmentUtils.getIdentifier(attachment);
228        if (attachmentIdentifier != null) {
229            mAttachmentPreviews.put(
230                    attachmentIdentifier, new AttachmentPreview(attachment, preview));
231        }
232    }
233
234    /*
235     * Returns a saved preview that was previously set
236     */
237    @Override
238    public Bitmap get(Attachment attachment) {
239        final String attachmentIdentifier = AttachmentUtils.getIdentifier(attachment);
240        if (attachmentIdentifier != null) {
241            final AttachmentPreview attachmentPreview = mAttachmentPreviews.get(
242                    attachmentIdentifier);
243            if (attachmentPreview != null) {
244                return attachmentPreview.preview;
245            }
246        }
247        return null;
248    }
249}
250