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