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 
17 package com.android.mail.ui;
18 
19 import android.app.FragmentManager;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.util.AttributeSet;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.widget.FrameLayout;
27 
28 import com.android.mail.R;
29 import com.android.mail.browse.ConversationMessage;
30 import com.android.mail.browse.MessageAttachmentTile;
31 import com.android.mail.compose.ComposeAttachmentTile;
32 import com.android.mail.photo.MailPhotoViewActivity;
33 import com.android.mail.providers.Account;
34 import com.android.mail.providers.Attachment;
35 import com.android.mail.ui.AttachmentTile.AttachmentPreview;
36 import com.android.mail.ui.AttachmentTile.AttachmentPreviewCache;
37 import com.android.mail.utils.ViewUtils;
38 import com.google.common.collect.Lists;
39 import com.google.common.collect.Maps;
40 
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 import java.util.List;
44 
45 /**
46  * Acts as a grid composed of {@link AttachmentTile}s.
47  */
48 public 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 
AttachmentTileGrid(Context context, AttributeSet attrs)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      */
configureGrid(FragmentManager fragmentManager, Account account, ConversationMessage message, List<Attachment> list, boolean loaderResult)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 
addMessageTileFromAttachment(Attachment attachment, int index, boolean loaderResult)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 
addComposeTileFromAttachment(Attachment attachment)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
viewPhoto(MessageAttachmentTile source)113     public void viewPhoto(MessageAttachmentTile source) {
114         final int photoIndex = indexOfChild(source);
115 
116         MailPhotoViewActivity.startMailPhotoViewActivity(getContext(), mAccount.getEmailAddress(),
117                 mAccount.getType(), mMessage, photoIndex);
118     }
119 
120     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)121     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
122         onMeasureForTiles(widthMeasureSpec);
123     }
124 
onMeasureForTiles(int widthMeasureSpec)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
onLayout(boolean changed, int left, int top, int right, int bottom)180     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
181         onLayoutForTiles();
182     }
183 
onLayoutForTiles()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
sendAccessibilityEvent(int eventType)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 
getAttachments()233     public List<Attachment> getAttachments() {
234         return mAttachments;
235     }
236 
getAttachmentPreviews()237     public ArrayList<AttachmentPreview> getAttachmentPreviews() {
238         return Lists.newArrayList(mAttachmentPreviews.values());
239     }
240 
setAttachmentPreviews(ArrayList<AttachmentPreview> previews)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
set(Attachment attachment, Bitmap preview)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
get(Attachment attachment)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