1 /* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.browse; 19 20 import android.app.FragmentManager; 21 import android.content.ActivityNotFoundException; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.graphics.Bitmap; 25 import android.util.AttributeSet; 26 import android.view.LayoutInflater; 27 import android.view.View; 28 import android.view.View.OnClickListener; 29 import android.view.ViewGroup; 30 import android.view.ViewParent; 31 32 import com.android.ex.photo.util.ImageUtils; 33 import com.android.mail.R; 34 import com.android.mail.analytics.Analytics; 35 import com.android.mail.providers.Attachment; 36 import com.android.mail.providers.UIProvider; 37 import com.android.mail.providers.UIProvider.AttachmentDestination; 38 import com.android.mail.providers.UIProvider.AttachmentRendition; 39 import com.android.mail.ui.AttachmentTile; 40 import com.android.mail.ui.AttachmentTileGrid; 41 import com.android.mail.utils.AttachmentUtils; 42 import com.android.mail.utils.LogTag; 43 import com.android.mail.utils.LogUtils; 44 import com.android.mail.utils.Utils; 45 46 import java.util.Comparator; 47 import java.util.PriorityQueue; 48 49 /** 50 * View for a single attachment in conversation view. Shows download status and allows launching 51 * intents to act on an attachment. 52 * 53 */ 54 public class MessageAttachmentTile extends AttachmentTile implements OnClickListener, 55 AttachmentViewInterface { 56 57 private int mPhotoIndex; 58 private View mTextContainer; 59 60 private final AttachmentActionHandler mActionHandler; 61 62 private PhotoViewHandler mPhotoViewHandler; 63 64 private static final String LOG_TAG = LogTag.getLogTag(); 65 66 /** 67 * Let someone else do this work, since it typically requires broader visibility of context, 68 * like what other photos to also show alongside this one. 69 */ 70 public interface PhotoViewHandler { viewPhoto(MessageAttachmentTile source)71 void viewPhoto(MessageAttachmentTile source); 72 } 73 MessageAttachmentTile(Context context)74 public MessageAttachmentTile(Context context) { 75 this(context, null); 76 } 77 MessageAttachmentTile(Context context, AttributeSet attrs)78 public MessageAttachmentTile(Context context, AttributeSet attrs) { 79 super(context, attrs); 80 81 mActionHandler = new AttachmentActionHandler(context, this); 82 } 83 initialize(FragmentManager fragmentManager)84 public void initialize(FragmentManager fragmentManager) { 85 mActionHandler.initialize(fragmentManager); 86 } 87 setPhotoViewHandler(PhotoViewHandler pvh)88 public void setPhotoViewHandler(PhotoViewHandler pvh) { 89 mPhotoViewHandler = pvh; 90 } 91 92 /** 93 * Render or update an attachment's view. This happens immediately upon instantiation, and 94 * repeatedly as status updates stream in, so only properties with new or changed values will 95 * cause sub-views to update. 96 */ render(Attachment attachment, int index, AttachmentPreviewCache attachmentPreviewCache, boolean loaderResult)97 public void render(Attachment attachment, int index, 98 AttachmentPreviewCache attachmentPreviewCache, boolean loaderResult) { 99 render(attachment, attachmentPreviewCache); 100 101 mPhotoIndex = index; 102 103 mActionHandler.setAttachment(mAttachment); 104 mActionHandler.updateStatus(loaderResult); 105 } 106 inflate(LayoutInflater inflater, ViewGroup parent)107 public static MessageAttachmentTile inflate(LayoutInflater inflater, ViewGroup parent) { 108 MessageAttachmentTile view = (MessageAttachmentTile) inflater.inflate( 109 R.layout.conversation_message_attachment_tile, parent, false); 110 return view; 111 } 112 113 114 @Override onFinishInflate()115 protected void onFinishInflate() { 116 super.onFinishInflate(); 117 118 mTextContainer = findViewById(R.id.attachment_tile_text_container); 119 120 setOnClickListener(this); 121 } 122 123 @Override onClick(View v)124 public void onClick(View v) { 125 onClick(); 126 } 127 onClick()128 private boolean onClick() { 129 showAndDownloadAttachments(); 130 return true; 131 } 132 showAndDownloadAttachments()133 private void showAndDownloadAttachments() { 134 // TODO: clean this up, it seems like it should live in AttachmentTileGrid since it keeps 135 // inappropriately touching this view's peers 136 AttachmentTileGrid tileGrid = ((AttachmentTileGrid) getParent()); 137 int childCount = tileGrid.getChildCount(); 138 139 PriorityQueue<MessageAttachmentTile> queue = new PriorityQueue<MessageAttachmentTile>( 140 childCount, new ViewIndexDistanceComparator(mPhotoIndex)); 141 for (int i = 0; i < childCount; i++) { 142 MessageAttachmentTile tile = (MessageAttachmentTile) tileGrid.getChildAt(i); 143 queue.add(tile); 144 } 145 146 // we want our downloads to have higher priority than the highest background downloads 147 int maxAdditionalPriority = childCount; 148 for (int i = 0; i < childCount; i++) { 149 // higher priority tiles are returned first 150 MessageAttachmentTile tile = queue.remove(); 151 tile.downloadAttachment(maxAdditionalPriority - i, i != 0); 152 } 153 154 viewAttachment(); 155 } 156 downloadAttachment(int additionalPriority, boolean delayDownload)157 public void downloadAttachment(int additionalPriority, boolean delayDownload) { 158 if (!mAttachment.isPresentLocally()) { 159 mActionHandler.startDownloadingAttachment(AttachmentDestination.CACHE, 160 UIProvider.AttachmentRendition.BEST, additionalPriority, delayDownload); 161 } 162 } 163 164 @Override viewAttachment()165 public void viewAttachment() { 166 final String mime = Utils.normalizeMimeType(mAttachment.getContentType()); 167 168 Analytics.getInstance() 169 .sendEvent("view_attachment", mime, "attachment_tile", mAttachment.size); 170 171 if (ImageUtils.isImageMimeType(mime)) { 172 if (mPhotoViewHandler != null) { 173 mPhotoViewHandler.viewPhoto(this); 174 } else { 175 LogUtils.e(LOG_TAG, "unable to view image attachment b/c handler is null"); 176 } 177 return; 178 } 179 180 Intent intent = new Intent(Intent.ACTION_VIEW); 181 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION 182 | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 183 Utils.setIntentDataAndTypeAndNormalize( 184 intent, mAttachment.contentUri, mime); 185 try { 186 getContext().startActivity(intent); 187 } catch (ActivityNotFoundException e) { 188 // couldn't find activity for View intent 189 LogUtils.e(LOG_TAG, "Couldn't find Activity for intent", e); 190 } 191 } 192 193 @Override updateProgress(boolean showDeterminateProgress)194 public void updateProgress(boolean showDeterminateProgress) { 195 // do not show progress for image tiles 196 } 197 198 @Override onUpdateStatus()199 public void onUpdateStatus() { 200 } 201 202 @Override setThumbnailToDefault()203 public void setThumbnailToDefault() { 204 super.setThumbnailToDefault(); 205 mTextContainer.setVisibility(VISIBLE); 206 } 207 208 @Override setThumbnail(Bitmap result)209 public void setThumbnail(Bitmap result) { 210 super.setThumbnail(result); 211 mTextContainer.setVisibility(GONE); 212 } 213 214 @Override thumbnailLoadFailed()215 public void thumbnailLoadFailed() { 216 super.thumbnailLoadFailed(); 217 218 if (AttachmentUtils.canDownloadAttachment(getContext(), null)) { 219 // Download if there is network. This check prevents the attachment 220 // download from failing and making the error toast show 221 mActionHandler.startDownloadingAttachment( 222 AttachmentDestination.CACHE, AttachmentRendition.SIMPLE, 0, false); 223 } 224 } 225 226 /** 227 * Given two child views, figure out whose index is closest to the specified 228 * index. 229 */ 230 public static class ViewIndexDistanceComparator implements Comparator<View>{ 231 final private int mIndex; 232 /** 233 * @param index Compare based on each view's distance to this index 234 */ ViewIndexDistanceComparator(int index)235 public ViewIndexDistanceComparator(int index) { 236 mIndex = index; 237 } 238 239 @Override compare(View lhs, View rhs)240 public int compare(View lhs, View rhs) { 241 ViewParent parent = lhs.getParent(); 242 if (parent == rhs.getParent()) { 243 if (parent instanceof ViewGroup) { 244 ViewGroup p = (ViewGroup) parent; 245 int lhsIndex = p.indexOfChild(lhs); 246 int rhsIndex = p.indexOfChild(rhs); 247 int lhsDistance = Math.abs(mIndex - lhsIndex); 248 int rhsDistance = Math.abs(mIndex - rhsIndex); 249 // prefer shorter distance since they are the next ones to be swiped to 250 int result = lhsDistance - rhsDistance; 251 if (result == 0) { 252 // prefer higher index since they are to the right in the photoviewer 253 return rhsIndex - lhsIndex; 254 } 255 return result; 256 } 257 } 258 return 0; 259 } 260 } 261 } 262