AttachmentTileGrid.java revision e1557036849265afc24528be9effbe299b640bff
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.content.res.Resources;
22import android.graphics.Bitmap;
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.ConversationMessage;
30import com.android.mail.browse.MessageAttachmentTile;
31import com.android.mail.compose.ComposeAttachmentTile;
32import com.android.mail.photo.MailPhotoViewActivity;
33import com.android.mail.providers.Account;
34import com.android.mail.providers.Attachment;
35import com.android.mail.ui.AttachmentTile.AttachmentPreview;
36import com.android.mail.ui.AttachmentTile.AttachmentPreviewCache;
37import com.android.mail.utils.ViewUtils;
38import com.google.common.collect.Lists;
39import com.google.common.collect.Maps;
40
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.List;
44
45/**
46 * Acts as a grid composed of {@link AttachmentTile}s.
47 */
48public class AttachmentTileGrid extends FrameLayout implements AttachmentPreviewCache,
49        MessageAttachmentTile.PhotoViewHandler {
50    private final LayoutInflater mInflater;
51    private final int mTileMinSize;
52    private final int mTilePadding;
53    private int mColumnCount;
54    private List<Attachment> mAttachments;
55    private final HashMap<String, AttachmentPreview> mAttachmentPreviews;
56    private FragmentManager mFragmentManager;
57    private Account mAccount;
58    private ConversationMessage mMessage;
59
60    public AttachmentTileGrid(Context context, AttributeSet attrs) {
61        super(context, attrs);
62        mInflater = LayoutInflater.from(context);
63        final Resources res = context.getResources();
64        mTileMinSize = res.getDimensionPixelSize(R.dimen.attachment_tile_min_size);
65        mTilePadding = res.getDimensionPixelSize(R.dimen.attachment_tile_padding);
66        mAttachmentPreviews = Maps.newHashMap();
67    }
68
69    /**
70     * Configures the grid to add {@link Attachment}s information to the views.
71     */
72    public void configureGrid(FragmentManager fragmentManager, Account account,
73            ConversationMessage message, List<Attachment> list, boolean loaderResult) {
74
75        mFragmentManager = fragmentManager;
76        mAccount = account;
77        mMessage = message;
78        mAttachments = list;
79        // Adding tiles to grid and filling in attachment information
80        int index = 0;
81        for (Attachment attachment : list) {
82            addMessageTileFromAttachment(attachment, index++, loaderResult);
83        }
84    }
85
86    private void addMessageTileFromAttachment(Attachment attachment, int index,
87            boolean loaderResult) {
88        final MessageAttachmentTile attachmentTile;
89
90        if (getChildCount() <= index) {
91            attachmentTile = MessageAttachmentTile.inflate(mInflater, this);
92            attachmentTile.initialize(mFragmentManager);
93            attachmentTile.setPhotoViewHandler(this);
94            addView(attachmentTile);
95        } else {
96            attachmentTile = (MessageAttachmentTile) getChildAt(index);
97        }
98
99        attachmentTile.render(attachment, index, this, loaderResult);
100    }
101
102    public ComposeAttachmentTile addComposeTileFromAttachment(Attachment attachment) {
103        final ComposeAttachmentTile attachmentTile =
104                ComposeAttachmentTile.inflate(mInflater, this);
105
106        addView(attachmentTile);
107        attachmentTile.render(attachment, this);
108
109        return attachmentTile;
110    }
111
112    @Override
113    public void viewPhoto(MessageAttachmentTile source) {
114        final int photoIndex = indexOfChild(source);
115
116        MailPhotoViewActivity.startMailPhotoViewActivity(getContext(), mAccount.getEmailAddress(),
117                mMessage, photoIndex);
118    }
119
120    @Override
121    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
122        onMeasureForTiles(widthMeasureSpec);
123    }
124
125    private void onMeasureForTiles(int widthMeasureSpec) {
126        final int width = MeasureSpec.getSize(widthMeasureSpec);
127
128        final int childCount = getChildCount();
129        if (childCount == 0) {
130            // Just in case...
131            setMeasuredDimension(width, 0);
132            return;
133        }
134
135        // Divide width by minimum tile size to get the number of columns.
136        // Truncation will ensure that the minimum will always be the minimum
137        // but that the tiles can (and likely will) grow larger.
138        mColumnCount = width / mTileMinSize;
139
140        // Just in case...
141        if (mColumnCount == 0) {
142            mColumnCount = 1;
143        }
144
145        // 1. Calculate image size.
146        //      = [total width] / [child count]
147        //
148        // 2. Set it to width/height of each children.
149        //    If we have a remainder, some tiles will have
150        //    1 pixel larger width than its height.
151        //
152        // 3. Set the dimensions of itself.
153        //    Let width = given width.
154        //    Let height = image size + bottom padding.
155
156        final int widthMinusPadding = width - (mColumnCount - 1) * mTilePadding;
157
158        final int imageSize = (widthMinusPadding) / mColumnCount;
159        final int remainder = widthMinusPadding - (imageSize * mColumnCount);
160
161        for (int i = 0; i < childCount; i++) {
162            final View child = getChildAt(i);
163            // Compensate for the remainder
164            final int childWidth = imageSize + (i < remainder ? 1 : 0);
165            child.measure(
166                    MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
167                    MeasureSpec.makeMeasureSpec(imageSize, MeasureSpec.EXACTLY)
168                    );
169        }
170
171        // Calculate the number of rows so we can get the proper height.
172        // Then multiply by the height of one tile to get the grid height.
173        final int numRows = ((childCount - 1) / mColumnCount) + 1;
174        setMeasuredDimension(width,
175                numRows * (imageSize + getChildAt(0).getPaddingBottom()) +
176                (numRows - 1) * mTilePadding);
177    }
178
179    @Override
180    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
181        onLayoutForTiles();
182    }
183
184    private void onLayoutForTiles() {
185        final int count = getChildCount();
186        if (count == 0) {
187            return;
188        }
189
190        boolean skipBeginningOfRowFirstTime = true;
191        final boolean isRtl = ViewUtils.isViewRtl(this);
192        final int width = getMeasuredWidth();
193        int childLeft = (isRtl) ? width - getChildAt(0).getMeasuredWidth() : 0;
194        int childTop = 0;
195
196        // Layout the grid.
197        for (int i = 0; i < count; i++) {
198            final View child = getChildAt(i);
199
200            // Note MeasuredWidth and MeasuredHeight include the padding.
201            final int childWidth = child.getMeasuredWidth();
202            final int childHeight = child.getMeasuredHeight();
203
204            // If we're at the beginning of a row and it is not the first row
205            // in the grid, reset childLeft to 0 and update childTop
206            // to reflect the top of the new row.
207            if (!skipBeginningOfRowFirstTime && i % mColumnCount == 0) {
208                childLeft = (isRtl) ? width - childWidth : 0;
209                childTop += childHeight + mTilePadding;
210            } else {
211                skipBeginningOfRowFirstTime = false;
212            }
213
214            child.layout(childLeft, childTop,
215                    childLeft + childWidth, childTop + childHeight);
216
217            if (isRtl) {
218                childLeft -= childWidth - mTilePadding;
219            } else {
220                childLeft += childWidth + mTilePadding;
221            }
222        }
223    }
224
225    @Override
226    public void sendAccessibilityEvent(int eventType) {
227        // This method is called when the child tile is INVISIBLE (meaning "empty"), and the
228        // Accessibility Manager needs to find alternative content description to speak.
229        // Here, we ignore the default behavior, since we don't want to let the manager speak
230        // a contact name for the tile next to the INVISIBLE tile.
231    }
232
233    public List<Attachment> getAttachments() {
234        return mAttachments;
235    }
236
237    public ArrayList<AttachmentPreview> getAttachmentPreviews() {
238        return Lists.newArrayList(mAttachmentPreviews.values());
239    }
240
241    public void setAttachmentPreviews(ArrayList<AttachmentPreview> previews) {
242        if (previews != null) {
243            for (AttachmentPreview preview : previews) {
244                mAttachmentPreviews.put(preview.attachmentIdentifier, preview);
245            }
246        }
247    }
248
249    /*
250     * Save the preview for an attachment
251     */
252    @Override
253    public void set(Attachment attachment, Bitmap preview) {
254        final String attachmentIdentifier = attachment.getIdentifierUri().toString();
255        if (attachmentIdentifier != null) {
256            mAttachmentPreviews.put(
257                    attachmentIdentifier, new AttachmentPreview(attachment, preview));
258        }
259    }
260
261    /*
262     * Returns a saved preview that was previously set
263     */
264    @Override
265    public Bitmap get(Attachment attachment) {
266        final String attachmentIdentifier = attachment.getIdentifierUri().toString();
267        if (attachmentIdentifier != null) {
268            final AttachmentPreview attachmentPreview = mAttachmentPreviews.get(
269                    attachmentIdentifier);
270            if (attachmentPreview != null) {
271                return attachmentPreview.preview;
272            }
273        }
274        return null;
275    }
276}
277