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