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
18package com.android.mail.browse;
19
20import android.app.FragmentManager;
21import android.content.ActivityNotFoundException;
22import android.content.Context;
23import android.content.Intent;
24import android.graphics.Bitmap;
25import android.util.AttributeSet;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.View.OnClickListener;
29import android.view.ViewGroup;
30import android.view.ViewParent;
31
32import com.android.ex.photo.util.ImageUtils;
33import com.android.mail.R;
34import com.android.mail.analytics.Analytics;
35import com.android.mail.providers.Attachment;
36import com.android.mail.providers.UIProvider;
37import com.android.mail.providers.UIProvider.AttachmentDestination;
38import com.android.mail.providers.UIProvider.AttachmentRendition;
39import com.android.mail.ui.AttachmentTile;
40import com.android.mail.ui.AttachmentTileGrid;
41import com.android.mail.utils.AttachmentUtils;
42import com.android.mail.utils.LogTag;
43import com.android.mail.utils.LogUtils;
44import com.android.mail.utils.Utils;
45
46import java.util.Comparator;
47import 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 */
54public 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 {
71        void viewPhoto(MessageAttachmentTile source);
72    }
73
74    public MessageAttachmentTile(Context context) {
75        this(context, null);
76    }
77
78    public MessageAttachmentTile(Context context, AttributeSet attrs) {
79        super(context, attrs);
80
81        mActionHandler = new AttachmentActionHandler(context, this);
82    }
83
84    public void initialize(FragmentManager fragmentManager) {
85        mActionHandler.initialize(fragmentManager);
86    }
87
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     */
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
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
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
124    public void onClick(View v) {
125        onClick();
126    }
127
128    private boolean onClick() {
129        showAndDownloadAttachments();
130        return true;
131    }
132
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
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
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
194    public void updateProgress(boolean showDeterminateProgress) {
195        // do not show progress for image tiles
196    }
197
198    @Override
199    public void onUpdateStatus() {
200    }
201
202    @Override
203    public void setThumbnailToDefault() {
204        super.setThumbnailToDefault();
205        mTextContainer.setVisibility(VISIBLE);
206    }
207
208    @Override
209    public void setThumbnail(Bitmap result) {
210        super.setThumbnail(result);
211        mTextContainer.setVisibility(GONE);
212    }
213
214    @Override
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         */
235        public ViewIndexDistanceComparator(int index) {
236            mIndex = index;
237        }
238
239        @Override
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