| /** |
| * Copyright (c) 2011, Google Inc. |
| * |
| * 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.mail.providers; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.text.TextUtils; |
| |
| import com.android.emailcommon.internet.MimeUtility; |
| import com.android.emailcommon.mail.MessagingException; |
| import com.android.emailcommon.mail.Part; |
| import com.android.mail.browse.MessageAttachmentBar; |
| import com.android.mail.providers.UIProvider.AttachmentColumns; |
| import com.android.mail.providers.UIProvider.AttachmentDestination; |
| import com.android.mail.providers.UIProvider.AttachmentRendition; |
| import com.android.mail.providers.UIProvider.AttachmentState; |
| import com.android.mail.providers.UIProvider.AttachmentType; |
| import com.android.mail.utils.LogTag; |
| import com.android.mail.utils.LogUtils; |
| import com.android.mail.utils.MimeType; |
| import com.android.mail.utils.Utils; |
| import com.google.common.base.Objects; |
| import com.google.common.collect.Lists; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.Collection; |
| import java.util.List; |
| |
| public class Attachment implements Parcelable { |
| public static final int MAX_ATTACHMENT_PREVIEWS = 2; |
| public static final String LOG_TAG = LogTag.getLogTag(); |
| /** |
| * Workaround for b/8070022 so that appending a null partId to the end of a |
| * uri wouldn't remove the trailing backslash |
| */ |
| public static final String EMPTY_PART_ID = "empty"; |
| |
| // Indicates that this is a dummy placeholder attachment. |
| public static final int FLAG_DUMMY_ATTACHMENT = 1<<10; |
| |
| /** |
| * Part id of the attachment. |
| */ |
| public String partId; |
| |
| /** |
| * Attachment file name. See {@link AttachmentColumns#NAME} Use {@link #setName(String)}. |
| */ |
| private String name; |
| |
| /** |
| * Attachment size in bytes. See {@link AttachmentColumns#SIZE}. |
| */ |
| public int size; |
| |
| /** |
| * The provider-generated URI for this Attachment. Must be globally unique. |
| * For local attachments generated by the Compose UI prior to send/save, |
| * this field will be null. |
| * |
| * @see AttachmentColumns#URI |
| */ |
| public Uri uri; |
| |
| /** |
| * MIME type of the file. Use {@link #getContentType()} and {@link #setContentType(String)}. |
| * |
| * @see AttachmentColumns#CONTENT_TYPE |
| */ |
| private String contentType; |
| private String inferredContentType; |
| |
| /** |
| * Use {@link #setState(int)} |
| * |
| * @see AttachmentColumns#STATE |
| */ |
| public int state; |
| |
| /** |
| * @see AttachmentColumns#DESTINATION |
| */ |
| public int destination; |
| |
| /** |
| * @see AttachmentColumns#DOWNLOADED_SIZE |
| */ |
| public int downloadedSize; |
| |
| /** |
| * Shareable, openable uri for this attachment |
| * <p> |
| * content:// Gmail.getAttachmentDefaultUri() if origin is SERVER_ATTACHMENT |
| * <p> |
| * content:// uri pointing to the content to be uploaded if origin is |
| * LOCAL_FILE |
| * <p> |
| * file:// uri pointing to an EXTERNAL apk file. The package manager only |
| * handles file:// uris not content:// uris. We do the same workaround in |
| * {@link MessageAttachmentBar#onClick(android.view.View)} and |
| * UiProvider#getUiAttachmentsCursorForUIAttachments(). |
| * |
| * @see AttachmentColumns#CONTENT_URI |
| */ |
| public Uri contentUri; |
| |
| /** |
| * Might be null. |
| * |
| * @see AttachmentColumns#THUMBNAIL_URI |
| */ |
| public Uri thumbnailUri; |
| |
| /** |
| * Might be null. |
| * |
| * @see AttachmentColumns#PREVIEW_INTENT_URI |
| */ |
| public Uri previewIntentUri; |
| |
| /** |
| * The visibility type of this attachment. |
| * |
| * @see AttachmentColumns#TYPE |
| */ |
| public int type; |
| |
| public int flags; |
| |
| /** |
| * Might be null. JSON string. |
| * |
| * @see AttachmentColumns#PROVIDER_DATA |
| */ |
| public String providerData; |
| |
| private transient Uri mIdentifierUri; |
| |
| /** |
| * True if this attachment can be downloaded again. |
| */ |
| private boolean supportsDownloadAgain; |
| |
| |
| public Attachment() { |
| } |
| |
| public Attachment(Parcel in) { |
| name = in.readString(); |
| size = in.readInt(); |
| uri = in.readParcelable(null); |
| contentType = in.readString(); |
| state = in.readInt(); |
| destination = in.readInt(); |
| downloadedSize = in.readInt(); |
| contentUri = in.readParcelable(null); |
| thumbnailUri = in.readParcelable(null); |
| previewIntentUri = in.readParcelable(null); |
| providerData = in.readString(); |
| supportsDownloadAgain = in.readInt() == 1; |
| type = in.readInt(); |
| flags = in.readInt(); |
| } |
| |
| public Attachment(Cursor cursor) { |
| if (cursor == null) { |
| return; |
| } |
| |
| name = cursor.getString(cursor.getColumnIndex(AttachmentColumns.NAME)); |
| size = cursor.getInt(cursor.getColumnIndex(AttachmentColumns.SIZE)); |
| uri = Uri.parse(cursor.getString(cursor.getColumnIndex(AttachmentColumns.URI))); |
| contentType = cursor.getString(cursor.getColumnIndex(AttachmentColumns.CONTENT_TYPE)); |
| state = cursor.getInt(cursor.getColumnIndex(AttachmentColumns.STATE)); |
| destination = cursor.getInt(cursor.getColumnIndex(AttachmentColumns.DESTINATION)); |
| downloadedSize = cursor.getInt(cursor.getColumnIndex(AttachmentColumns.DOWNLOADED_SIZE)); |
| contentUri = parseOptionalUri( |
| cursor.getString(cursor.getColumnIndex(AttachmentColumns.CONTENT_URI))); |
| thumbnailUri = parseOptionalUri( |
| cursor.getString(cursor.getColumnIndex(AttachmentColumns.THUMBNAIL_URI))); |
| previewIntentUri = parseOptionalUri( |
| cursor.getString(cursor.getColumnIndex(AttachmentColumns.PREVIEW_INTENT_URI))); |
| providerData = cursor.getString(cursor.getColumnIndex(AttachmentColumns.PROVIDER_DATA)); |
| supportsDownloadAgain = cursor.getInt( |
| cursor.getColumnIndex(AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN)) == 1; |
| type = cursor.getInt(cursor.getColumnIndex(AttachmentColumns.TYPE)); |
| flags = cursor.getInt(cursor.getColumnIndex(AttachmentColumns.FLAGS)); |
| } |
| |
| public Attachment(JSONObject srcJson) { |
| name = srcJson.optString(AttachmentColumns.NAME, null); |
| size = srcJson.optInt(AttachmentColumns.SIZE); |
| uri = parseOptionalUri(srcJson, AttachmentColumns.URI); |
| contentType = srcJson.optString(AttachmentColumns.CONTENT_TYPE, null); |
| state = srcJson.optInt(AttachmentColumns.STATE); |
| destination = srcJson.optInt(AttachmentColumns.DESTINATION); |
| downloadedSize = srcJson.optInt(AttachmentColumns.DOWNLOADED_SIZE); |
| contentUri = parseOptionalUri(srcJson, AttachmentColumns.CONTENT_URI); |
| thumbnailUri = parseOptionalUri(srcJson, AttachmentColumns.THUMBNAIL_URI); |
| previewIntentUri = parseOptionalUri(srcJson, AttachmentColumns.PREVIEW_INTENT_URI); |
| providerData = srcJson.optString(AttachmentColumns.PROVIDER_DATA); |
| supportsDownloadAgain = srcJson.optBoolean(AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, true); |
| type = srcJson.optInt(AttachmentColumns.TYPE); |
| flags = srcJson.optInt(AttachmentColumns.FLAGS); |
| } |
| |
| /** |
| * Constructor for use when creating attachments in eml files. |
| */ |
| public Attachment(Context context, Part part, Uri emlFileUri, String messageId, String partId) { |
| try { |
| // Transfer fields from mime format to provider format |
| final String contentTypeHeader = MimeUtility.unfoldAndDecode(part.getContentType()); |
| name = MimeUtility.getHeaderParameter(contentTypeHeader, "name"); |
| if (name == null) { |
| final String contentDisposition = |
| MimeUtility.unfoldAndDecode(part.getDisposition()); |
| name = MimeUtility.getHeaderParameter(contentDisposition, "filename"); |
| } |
| |
| contentType = MimeType.inferMimeType(name, part.getMimeType()); |
| uri = EmlAttachmentProvider.getAttachmentUri(emlFileUri, messageId, partId); |
| contentUri = uri; |
| thumbnailUri = uri; |
| previewIntentUri = null; |
| state = AttachmentState.SAVED; |
| providerData = null; |
| supportsDownloadAgain = false; |
| destination = AttachmentDestination.CACHE; |
| type = AttachmentType.STANDARD; |
| flags = 0; |
| |
| // insert attachment into content provider so that we can open the file |
| final ContentResolver resolver = context.getContentResolver(); |
| resolver.insert(uri, toContentValues()); |
| |
| // save the file in the cache |
| try { |
| final InputStream in = part.getBody().getInputStream(); |
| final OutputStream out = resolver.openOutputStream(uri, "rwt"); |
| size = IOUtils.copy(in, out); |
| downloadedSize = size; |
| in.close(); |
| out.close(); |
| } catch (FileNotFoundException e) { |
| LogUtils.e(LOG_TAG, e, "Error in writing attachment to cache"); |
| } catch (IOException e) { |
| LogUtils.e(LOG_TAG, e, "Error in writing attachment to cache"); |
| } |
| // perform a second insert to put the updated size and downloaded size values in |
| resolver.insert(uri, toContentValues()); |
| } catch (MessagingException e) { |
| LogUtils.e(LOG_TAG, e, "Error parsing eml attachment"); |
| } |
| } |
| |
| /** |
| * Create an attachment from a {@link ContentValues} object. |
| * The keys should be {@link AttachmentColumns}. |
| */ |
| public Attachment(ContentValues values) { |
| name = values.getAsString(AttachmentColumns.NAME); |
| size = values.getAsInteger(AttachmentColumns.SIZE); |
| uri = parseOptionalUri(values.getAsString(AttachmentColumns.URI)); |
| contentType = values.getAsString(AttachmentColumns.CONTENT_TYPE); |
| state = values.getAsInteger(AttachmentColumns.STATE); |
| destination = values.getAsInteger(AttachmentColumns.DESTINATION); |
| downloadedSize = values.getAsInteger(AttachmentColumns.DOWNLOADED_SIZE); |
| contentUri = parseOptionalUri(values.getAsString(AttachmentColumns.CONTENT_URI)); |
| thumbnailUri = parseOptionalUri(values.getAsString(AttachmentColumns.THUMBNAIL_URI)); |
| previewIntentUri = |
| parseOptionalUri(values.getAsString(AttachmentColumns.PREVIEW_INTENT_URI)); |
| providerData = values.getAsString(AttachmentColumns.PROVIDER_DATA); |
| supportsDownloadAgain = values.getAsBoolean(AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN); |
| type = values.getAsInteger(AttachmentColumns.TYPE); |
| flags = values.getAsInteger(AttachmentColumns.FLAGS); |
| } |
| |
| /** |
| * Returns the various attachment fields in a {@link ContentValues} object. |
| * The keys for each field should be {@link AttachmentColumns}. |
| */ |
| public ContentValues toContentValues() { |
| final ContentValues values = new ContentValues(12); |
| |
| values.put(AttachmentColumns.NAME, name); |
| values.put(AttachmentColumns.SIZE, size); |
| values.put(AttachmentColumns.URI, uri.toString()); |
| values.put(AttachmentColumns.CONTENT_TYPE, contentType); |
| values.put(AttachmentColumns.STATE, state); |
| values.put(AttachmentColumns.DESTINATION, destination); |
| values.put(AttachmentColumns.DOWNLOADED_SIZE, downloadedSize); |
| values.put(AttachmentColumns.CONTENT_URI, contentUri.toString()); |
| values.put(AttachmentColumns.THUMBNAIL_URI, thumbnailUri.toString()); |
| values.put(AttachmentColumns.PREVIEW_INTENT_URI, |
| previewIntentUri == null ? null : previewIntentUri.toString()); |
| values.put(AttachmentColumns.PROVIDER_DATA, providerData); |
| values.put(AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, supportsDownloadAgain); |
| values.put(AttachmentColumns.TYPE, type); |
| values.put(AttachmentColumns.FLAGS, flags); |
| |
| return values; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeString(name); |
| dest.writeInt(size); |
| dest.writeParcelable(uri, flags); |
| dest.writeString(contentType); |
| dest.writeInt(state); |
| dest.writeInt(destination); |
| dest.writeInt(downloadedSize); |
| dest.writeParcelable(contentUri, flags); |
| dest.writeParcelable(thumbnailUri, flags); |
| dest.writeParcelable(previewIntentUri, flags); |
| dest.writeString(providerData); |
| dest.writeInt(supportsDownloadAgain ? 1 : 0); |
| dest.writeInt(type); |
| dest.writeInt(flags); |
| } |
| |
| public JSONObject toJSON() throws JSONException { |
| final JSONObject result = new JSONObject(); |
| |
| result.put(AttachmentColumns.NAME, name); |
| result.put(AttachmentColumns.SIZE, size); |
| result.put(AttachmentColumns.URI, stringify(uri)); |
| result.put(AttachmentColumns.CONTENT_TYPE, contentType); |
| result.put(AttachmentColumns.STATE, state); |
| result.put(AttachmentColumns.DESTINATION, destination); |
| result.put(AttachmentColumns.DOWNLOADED_SIZE, downloadedSize); |
| result.put(AttachmentColumns.CONTENT_URI, stringify(contentUri)); |
| result.put(AttachmentColumns.THUMBNAIL_URI, stringify(thumbnailUri)); |
| result.put(AttachmentColumns.PREVIEW_INTENT_URI, stringify(previewIntentUri)); |
| result.put(AttachmentColumns.PROVIDER_DATA, providerData); |
| result.put(AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, supportsDownloadAgain); |
| result.put(AttachmentColumns.TYPE, type); |
| result.put(AttachmentColumns.FLAGS, flags); |
| |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| try { |
| final JSONObject jsonObject = toJSON(); |
| // Add some additional fields that are helpful when debugging issues |
| jsonObject.put("partId", partId); |
| if (providerData != null) { |
| try { |
| // pretty print the provider data |
| jsonObject.put(AttachmentColumns.PROVIDER_DATA, new JSONObject(providerData)); |
| } catch (JSONException e) { |
| LogUtils.e(LOG_TAG, e, "JSONException when adding provider data"); |
| } |
| } |
| return jsonObject.toString(4); |
| } catch (JSONException e) { |
| LogUtils.e(LOG_TAG, e, "JSONException in toString"); |
| return super.toString(); |
| } |
| } |
| |
| private static String stringify(Object object) { |
| return object != null ? object.toString() : null; |
| } |
| |
| protected static Uri parseOptionalUri(String uriString) { |
| return uriString == null ? null : Uri.parse(uriString); |
| } |
| |
| protected static Uri parseOptionalUri(JSONObject srcJson, String key) { |
| final String uriStr = srcJson.optString(key, null); |
| return uriStr == null ? null : Uri.parse(uriStr); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| public boolean isPresentLocally() { |
| return state == AttachmentState.SAVED; |
| } |
| |
| public boolean canSave() { |
| return !isSavedToExternal() && !isInstallable(); |
| } |
| |
| public boolean canShare() { |
| return isPresentLocally() && contentUri != null; |
| } |
| |
| public boolean isDownloading() { |
| return state == AttachmentState.DOWNLOADING || state == AttachmentState.PAUSED; |
| } |
| |
| public boolean isSavedToExternal() { |
| return state == AttachmentState.SAVED && destination == AttachmentDestination.EXTERNAL; |
| } |
| |
| public boolean isInstallable() { |
| return MimeType.isInstallable(getContentType()); |
| } |
| |
| public boolean shouldShowProgress() { |
| return (state == AttachmentState.DOWNLOADING || state == AttachmentState.PAUSED) |
| && size > 0 && downloadedSize > 0 && downloadedSize <= size; |
| } |
| |
| public boolean isDownloadFailed() { |
| return state == AttachmentState.FAILED; |
| } |
| |
| public boolean isDownloadFinishedOrFailed() { |
| return state == AttachmentState.FAILED || state == AttachmentState.SAVED; |
| } |
| |
| public boolean supportsDownloadAgain() { |
| return supportsDownloadAgain; |
| } |
| |
| public boolean canPreview() { |
| return previewIntentUri != null; |
| } |
| |
| /** |
| * Returns a stable identifier URI for this attachment. TODO: make the uri |
| * field stable, and put provider-specific opaque bits and bobs elsewhere |
| */ |
| public Uri getIdentifierUri() { |
| if (Utils.isEmpty(mIdentifierUri)) { |
| mIdentifierUri = Utils.isEmpty(uri) ? |
| (Utils.isEmpty(contentUri) ? Uri.EMPTY : contentUri) |
| : uri.buildUpon().clearQuery().build(); |
| } |
| return mIdentifierUri; |
| } |
| |
| public String getContentType() { |
| if (TextUtils.isEmpty(inferredContentType)) { |
| inferredContentType = MimeType.inferMimeType(name, contentType); |
| } |
| return inferredContentType; |
| } |
| |
| public Uri getUriForRendition(int rendition) { |
| final Uri uri; |
| switch (rendition) { |
| case AttachmentRendition.BEST: |
| uri = contentUri; |
| break; |
| case AttachmentRendition.SIMPLE: |
| uri = thumbnailUri; |
| break; |
| default: |
| throw new IllegalArgumentException("invalid rendition: " + rendition); |
| } |
| return uri; |
| } |
| |
| public void setContentType(String contentType) { |
| if (!TextUtils.equals(this.contentType, contentType)) { |
| this.inferredContentType = null; |
| this.contentType = contentType; |
| } |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public boolean setName(String name) { |
| if (!TextUtils.equals(this.name, name)) { |
| this.inferredContentType = null; |
| this.name = name; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Sets the attachment state. Side effect: sets downloadedSize |
| */ |
| public void setState(int state) { |
| this.state = state; |
| if (state == AttachmentState.FAILED || state == AttachmentState.NOT_SAVED) { |
| this.downloadedSize = 0; |
| } |
| } |
| |
| @Override |
| public boolean equals(final Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| final Attachment that = (Attachment) o; |
| |
| if (destination != that.destination) { |
| return false; |
| } |
| if (downloadedSize != that.downloadedSize) { |
| return false; |
| } |
| if (size != that.size) { |
| return false; |
| } |
| if (state != that.state) { |
| return false; |
| } |
| if (supportsDownloadAgain != that.supportsDownloadAgain) { |
| return false; |
| } |
| if (type != that.type) { |
| return false; |
| } |
| if (contentType != null ? !contentType.equals(that.contentType) |
| : that.contentType != null) { |
| return false; |
| } |
| if (contentUri != null ? !contentUri.equals(that.contentUri) : that.contentUri != null) { |
| return false; |
| } |
| if (name != null ? !name.equals(that.name) : that.name != null) { |
| return false; |
| } |
| if (partId != null ? !partId.equals(that.partId) : that.partId != null) { |
| return false; |
| } |
| if (previewIntentUri != null ? !previewIntentUri.equals(that.previewIntentUri) |
| : that.previewIntentUri != null) { |
| return false; |
| } |
| if (providerData != null ? !providerData.equals(that.providerData) |
| : that.providerData != null) { |
| return false; |
| } |
| if (thumbnailUri != null ? !thumbnailUri.equals(that.thumbnailUri) |
| : that.thumbnailUri != null) { |
| return false; |
| } |
| if (uri != null ? !uri.equals(that.uri) : that.uri != null) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = partId != null ? partId.hashCode() : 0; |
| result = 31 * result + (name != null ? name.hashCode() : 0); |
| result = 31 * result + size; |
| result = 31 * result + (uri != null ? uri.hashCode() : 0); |
| result = 31 * result + (contentType != null ? contentType.hashCode() : 0); |
| result = 31 * result + state; |
| result = 31 * result + destination; |
| result = 31 * result + downloadedSize; |
| result = 31 * result + (contentUri != null ? contentUri.hashCode() : 0); |
| result = 31 * result + (thumbnailUri != null ? thumbnailUri.hashCode() : 0); |
| result = 31 * result + (previewIntentUri != null ? previewIntentUri.hashCode() : 0); |
| result = 31 * result + type; |
| result = 31 * result + (providerData != null ? providerData.hashCode() : 0); |
| result = 31 * result + (supportsDownloadAgain ? 1 : 0); |
| return result; |
| } |
| |
| public static String toJSONArray(Collection<? extends Attachment> attachments) { |
| if (attachments == null) { |
| return null; |
| } |
| final JSONArray result = new JSONArray(); |
| try { |
| for (Attachment attachment : attachments) { |
| result.put(attachment.toJSON()); |
| } |
| } catch (JSONException e) { |
| throw new IllegalArgumentException(e); |
| } |
| return result.toString(); |
| } |
| |
| public static List<Attachment> fromJSONArray(String jsonArrayStr) { |
| final List<Attachment> results = Lists.newArrayList(); |
| if (jsonArrayStr != null) { |
| try { |
| final JSONArray arr = new JSONArray(jsonArrayStr); |
| |
| for (int i = 0; i < arr.length(); i++) { |
| results.add(new Attachment(arr.getJSONObject(i))); |
| } |
| |
| } catch (JSONException e) { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| return results; |
| } |
| |
| private static final String SERVER_ATTACHMENT = "SERVER_ATTACHMENT"; |
| private static final String LOCAL_FILE = "LOCAL_FILE"; |
| |
| public String toJoinedString() { |
| return TextUtils.join(UIProvider.ATTACHMENT_INFO_DELIMITER, Lists.newArrayList( |
| partId == null ? "" : partId, |
| name == null ? "" |
| : name.replaceAll("[" + UIProvider.ATTACHMENT_INFO_DELIMITER |
| + UIProvider.ATTACHMENT_INFO_SEPARATOR + "]", ""), |
| getContentType(), |
| String.valueOf(size), |
| getContentType(), |
| contentUri != null ? SERVER_ATTACHMENT : LOCAL_FILE, |
| contentUri != null ? contentUri.toString() : "", |
| "" /* cachedFileUri */, |
| String.valueOf(type))); |
| } |
| |
| /** |
| * For use with {@link UIProvider.ConversationColumns#ATTACHMENT_PREVIEW_STATES}. |
| * |
| * @param previewStates The packed int describing the states of multiple attachments. |
| * @param attachmentIndex The index of the attachment to update. |
| * @param rendition The rendition of that attachment to update. |
| * @param downloaded Whether that specific rendition is downloaded. |
| * @return A packed int describing the updated downloaded states of the multiple attachments. |
| */ |
| public static int updatePreviewStates(int previewStates, int attachmentIndex, int rendition, |
| boolean downloaded) { |
| // find the bit that describes that specific attachment index and rendition |
| int shift = attachmentIndex * 2 + rendition; |
| int mask = 1 << shift; |
| // update the packed int at that bit |
| if (downloaded) { |
| // turns that bit into a 1 |
| return previewStates | mask; |
| } else { |
| // turns that bit into a 0 |
| return previewStates & ~mask; |
| } |
| } |
| |
| /** |
| * For use with {@link UIProvider.ConversationColumns#ATTACHMENT_PREVIEW_STATES}. |
| * |
| * @param previewStates The packed int describing the states of multiple attachments. |
| * @param attachmentIndex The index of the attachment. |
| * @param rendition The rendition of the attachment. |
| * @return The downloaded state of that particular rendition of that particular attachment. |
| */ |
| public static boolean getPreviewState(int previewStates, int attachmentIndex, int rendition) { |
| // find the bit that describes that specific attachment index |
| int shift = attachmentIndex * 2; |
| int mask = 1 << shift; |
| |
| if (rendition == AttachmentRendition.SIMPLE) { |
| // implicit shift of 0 finds the SIMPLE rendition bit |
| return (previewStates & mask) != 0; |
| } else if (rendition == AttachmentRendition.BEST) { |
| // shift of 1 finds the BEST rendition bit |
| return (previewStates & (mask << 1)) != 0; |
| } else { |
| return false; |
| } |
| } |
| |
| public static final Creator<Attachment> CREATOR = new Creator<Attachment>() { |
| @Override |
| public Attachment createFromParcel(Parcel source) { |
| return new Attachment(source); |
| } |
| |
| @Override |
| public Attachment[] newArray(int size) { |
| return new Attachment[size]; |
| } |
| }; |
| } |