1/*
2 * Copyright (C) 2015 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 */
16package com.android.messaging.datamodel.data;
17
18import android.net.Uri;
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.support.annotation.NonNull;
22
23import com.android.messaging.util.Assert;
24import com.android.messaging.util.ContentType;
25import com.android.messaging.util.LogUtil;
26import com.android.messaging.util.SafeAsyncTask;
27import com.android.messaging.util.UriUtil;
28
29/**
30 * Represents a "pending" message part that acts as a placeholder for the actual attachment being
31 * loaded. It handles the task to load and persist the attachment from a Uri to local scratch
32 * folder. This item is not persisted to the database.
33 */
34public class PendingAttachmentData extends MessagePartData {
35    /** The pending state. This is the initial state where we haven't started loading yet */
36    public static final int STATE_PENDING = 0;
37
38    /** The state for when we are currently loading the attachment to the scratch space */
39    public static final int STATE_LOADING = 1;
40
41    /** The attachment has been successfully loaded and no longer pending */
42    public static final int STATE_LOADED = 2;
43
44    /** The attachment failed to load */
45    public static final int STATE_FAILED = 3;
46
47    private static final int LOAD_MEDIA_TIME_LIMIT_MILLIS = 60 * 1000;  // 60s
48
49    /** The current state of the pending attachment. Refer to the STATE_* states above */
50    private int mCurrentState;
51
52    /**
53     * Create a new instance of PendingAttachmentData with an output Uri.
54     * @param sourceUri the source Uri of the attachment. The Uri maybe temporary or remote,
55     * so we need to persist it to local storage.
56     */
57    protected PendingAttachmentData(final String caption, final String contentType,
58            @NonNull final Uri sourceUri, final int width, final int height,
59            final boolean onlySingleAttachment) {
60        super(caption, contentType, sourceUri, width, height, onlySingleAttachment);
61        mCurrentState = STATE_PENDING;
62    }
63
64    /**
65     * Creates a pending attachment data that is able to load from the given source uri and
66     * persist the media resource locally in the scratch folder.
67     */
68    public static PendingAttachmentData createPendingAttachmentData(final String contentType,
69            final Uri sourceUri) {
70        return createPendingAttachmentData(null, contentType, sourceUri, UNSPECIFIED_SIZE,
71                UNSPECIFIED_SIZE);
72    }
73
74    public static PendingAttachmentData createPendingAttachmentData(final String caption,
75            final String contentType, final Uri sourceUri, final int width, final int height) {
76        Assert.isTrue(ContentType.isMediaType(contentType));
77        return new PendingAttachmentData(caption, contentType, sourceUri, width, height,
78                false /*onlySingleAttachment*/);
79    }
80
81    public static PendingAttachmentData createPendingAttachmentData(final String caption,
82            final String contentType, final Uri sourceUri, final int width, final int height,
83            final boolean onlySingleAttachment) {
84        Assert.isTrue(ContentType.isMediaType(contentType));
85        return new PendingAttachmentData(caption, contentType, sourceUri, width, height,
86                onlySingleAttachment);
87    }
88
89    public int getCurrentState() {
90        return mCurrentState;
91    }
92
93    public void loadAttachmentForDraft(final DraftMessageData draftMessageData,
94            final String bindingId) {
95        if (mCurrentState != STATE_PENDING) {
96            return;
97        }
98        mCurrentState = STATE_LOADING;
99
100        // Kick off a SafeAsyncTask to load the content of the media and persist it locally.
101        // Note: we need to persist the media locally even if it's not remote, because we
102        // want to be able to resend the media in case the message failed to send.
103        new SafeAsyncTask<Void, Void, MessagePartData>(LOAD_MEDIA_TIME_LIMIT_MILLIS,
104                true /* cancelExecutionOnTimeout */) {
105            @Override
106            protected MessagePartData doInBackgroundTimed(final Void... params) {
107                final Uri contentUri = getContentUri();
108                final Uri persistedUri = UriUtil.persistContentToScratchSpace(contentUri);
109                if (persistedUri != null) {
110                    return MessagePartData.createMediaMessagePart(
111                            getText(),
112                            getContentType(),
113                            persistedUri,
114                            getWidth(),
115                            getHeight());
116                }
117                return null;
118            }
119
120            @Override
121            protected void onCancelled() {
122                LogUtil.w(LogUtil.BUGLE_TAG, "Timeout while retrieving media");
123                mCurrentState = STATE_FAILED;
124                if (draftMessageData.isBound(bindingId)) {
125                    draftMessageData.removePendingAttachment(PendingAttachmentData.this);
126                }
127            }
128
129            @Override
130            protected void onPostExecute(final MessagePartData attachment) {
131                if (attachment != null) {
132                    mCurrentState = STATE_LOADED;
133                    if (draftMessageData.isBound(bindingId)) {
134                        draftMessageData.updatePendingAttachment(attachment,
135                                PendingAttachmentData.this);
136                    } else {
137                        // The draft message data is no longer bound, drop the loaded attachment.
138                        attachment.destroyAsync();
139                    }
140                } else {
141                    // Media load failed. We already logged in doInBackground() so don't need to
142                    // do that again.
143                    mCurrentState = STATE_FAILED;
144                    if (draftMessageData.isBound(bindingId)) {
145                        draftMessageData.onPendingAttachmentLoadFailed(PendingAttachmentData.this);
146                        draftMessageData.removePendingAttachment(PendingAttachmentData.this);
147                    }
148                }
149            }
150        }.executeOnThreadPool();
151    }
152
153    protected PendingAttachmentData(final Parcel in) {
154        super(in);
155        mCurrentState = in.readInt();
156    }
157
158    @Override
159    public void writeToParcel(final Parcel out, final int flags) {
160        super.writeToParcel(out, flags);
161        out.writeInt(mCurrentState);
162    }
163
164    public static final Parcelable.Creator<PendingAttachmentData> CREATOR
165        = new Parcelable.Creator<PendingAttachmentData>() {
166            @Override
167            public PendingAttachmentData createFromParcel(final Parcel in) {
168                return new PendingAttachmentData(in);
169            }
170
171            @Override
172            public PendingAttachmentData[] newArray(final int size) {
173                return new PendingAttachmentData[size];
174            }
175        };
176}
177