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  */
16 package com.android.messaging.datamodel.data;
17 
18 import android.net.Uri;
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import androidx.annotation.NonNull;
22 
23 import com.android.messaging.util.Assert;
24 import com.android.messaging.util.ContentType;
25 import com.android.messaging.util.LogUtil;
26 import com.android.messaging.util.SafeAsyncTask;
27 import 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  */
34 public 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      */
PendingAttachmentData(final String caption, final String contentType, @NonNull final Uri sourceUri, final int width, final int height, final boolean onlySingleAttachment)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      */
createPendingAttachmentData(final String contentType, final Uri sourceUri)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 
createPendingAttachmentData(final String caption, final String contentType, final Uri sourceUri, final int width, final int height)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 
createPendingAttachmentData(final String caption, final String contentType, final Uri sourceUri, final int width, final int height, final boolean onlySingleAttachment)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 
getCurrentState()89     public int getCurrentState() {
90         return mCurrentState;
91     }
92 
loadAttachmentForDraft(final DraftMessageData draftMessageData, final String bindingId)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 
PendingAttachmentData(final Parcel in)153     protected PendingAttachmentData(final Parcel in) {
154         super(in);
155         mCurrentState = in.readInt();
156     }
157 
158     @Override
writeToParcel(final Parcel out, final int flags)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