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