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