/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.messaging.datamodel.data; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; import com.android.messaging.util.Assert; import com.android.messaging.util.ContentType; import com.android.messaging.util.LogUtil; import com.android.messaging.util.SafeAsyncTask; import com.android.messaging.util.UriUtil; /** * Represents a "pending" message part that acts as a placeholder for the actual attachment being * loaded. It handles the task to load and persist the attachment from a Uri to local scratch * folder. This item is not persisted to the database. */ public class PendingAttachmentData extends MessagePartData { /** The pending state. This is the initial state where we haven't started loading yet */ public static final int STATE_PENDING = 0; /** The state for when we are currently loading the attachment to the scratch space */ public static final int STATE_LOADING = 1; /** The attachment has been successfully loaded and no longer pending */ public static final int STATE_LOADED = 2; /** The attachment failed to load */ public static final int STATE_FAILED = 3; private static final int LOAD_MEDIA_TIME_LIMIT_MILLIS = 60 * 1000; // 60s /** The current state of the pending attachment. Refer to the STATE_* states above */ private int mCurrentState; /** * Create a new instance of PendingAttachmentData with an output Uri. * @param sourceUri the source Uri of the attachment. The Uri maybe temporary or remote, * so we need to persist it to local storage. */ protected PendingAttachmentData(final String caption, final String contentType, @NonNull final Uri sourceUri, final int width, final int height, final boolean onlySingleAttachment) { super(caption, contentType, sourceUri, width, height, onlySingleAttachment); mCurrentState = STATE_PENDING; } /** * Creates a pending attachment data that is able to load from the given source uri and * persist the media resource locally in the scratch folder. */ public static PendingAttachmentData createPendingAttachmentData(final String contentType, final Uri sourceUri) { return createPendingAttachmentData(null, contentType, sourceUri, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE); } public static PendingAttachmentData createPendingAttachmentData(final String caption, final String contentType, final Uri sourceUri, final int width, final int height) { Assert.isTrue(ContentType.isMediaType(contentType)); return new PendingAttachmentData(caption, contentType, sourceUri, width, height, false /*onlySingleAttachment*/); } public static PendingAttachmentData createPendingAttachmentData(final String caption, final String contentType, final Uri sourceUri, final int width, final int height, final boolean onlySingleAttachment) { Assert.isTrue(ContentType.isMediaType(contentType)); return new PendingAttachmentData(caption, contentType, sourceUri, width, height, onlySingleAttachment); } public int getCurrentState() { return mCurrentState; } public void loadAttachmentForDraft(final DraftMessageData draftMessageData, final String bindingId) { if (mCurrentState != STATE_PENDING) { return; } mCurrentState = STATE_LOADING; // Kick off a SafeAsyncTask to load the content of the media and persist it locally. // Note: we need to persist the media locally even if it's not remote, because we // want to be able to resend the media in case the message failed to send. new SafeAsyncTask(LOAD_MEDIA_TIME_LIMIT_MILLIS, true /* cancelExecutionOnTimeout */) { @Override protected MessagePartData doInBackgroundTimed(final Void... params) { final Uri contentUri = getContentUri(); final Uri persistedUri = UriUtil.persistContentToScratchSpace(contentUri); if (persistedUri != null) { return MessagePartData.createMediaMessagePart( getText(), getContentType(), persistedUri, getWidth(), getHeight()); } return null; } @Override protected void onCancelled() { LogUtil.w(LogUtil.BUGLE_TAG, "Timeout while retrieving media"); mCurrentState = STATE_FAILED; if (draftMessageData.isBound(bindingId)) { draftMessageData.removePendingAttachment(PendingAttachmentData.this); } } @Override protected void onPostExecute(final MessagePartData attachment) { if (attachment != null) { mCurrentState = STATE_LOADED; if (draftMessageData.isBound(bindingId)) { draftMessageData.updatePendingAttachment(attachment, PendingAttachmentData.this); } else { // The draft message data is no longer bound, drop the loaded attachment. attachment.destroyAsync(); } } else { // Media load failed. We already logged in doInBackground() so don't need to // do that again. mCurrentState = STATE_FAILED; if (draftMessageData.isBound(bindingId)) { draftMessageData.onPendingAttachmentLoadFailed(PendingAttachmentData.this); draftMessageData.removePendingAttachment(PendingAttachmentData.this); } } } }.executeOnThreadPool(); } protected PendingAttachmentData(final Parcel in) { super(in); mCurrentState = in.readInt(); } @Override public void writeToParcel(final Parcel out, final int flags) { super.writeToParcel(out, flags); out.writeInt(mCurrentState); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public PendingAttachmentData createFromParcel(final Parcel in) { return new PendingAttachmentData(in); } @Override public PendingAttachmentData[] newArray(final int size) { return new PendingAttachmentData[size]; } }; }