1 /* 2 * Copyright 2024 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 17 package androidx.pdf.data; 18 19 import android.content.res.AssetFileDescriptor; 20 import android.net.Uri; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 24 import androidx.annotation.RestrictTo; 25 import androidx.pdf.models.Dimensions; 26 import androidx.pdf.util.Preconditions; 27 import androidx.pdf.util.Uris; 28 29 import org.jspecify.annotations.NonNull; 30 import org.jspecify.annotations.Nullable; 31 32 import java.io.IOException; 33 34 /** 35 * An {@link Openable} on a 'content' asset, wrapping a {@link AssetFileDescriptor}. 36 */ 37 @RestrictTo(RestrictTo.Scope.LIBRARY) 38 public class ContentOpenable implements Openable, Parcelable { 39 40 private static final String TAG = ContentOpenable.class.getSimpleName(); 41 42 /** The content Uri this {@link Openable} opens. */ 43 private final Uri mContentUri; 44 45 /** 46 * The content-type that this content should be opened as (e.g. when more than one 47 * available). 48 */ 49 private final @Nullable String mContentType; 50 51 /** 52 * If not null, this {@link Openable} will open an image preview of the actual contents. 53 * The preview will be requested with these specified dimensions. 54 */ 55 private final @Nullable Dimensions mSize; 56 57 private @Nullable Open mOpen; 58 59 /** Creates an {@link Openable} for the contents @ uri with its default content-type. */ ContentOpenable(@onNull Uri uri)60 public ContentOpenable(@NonNull Uri uri) { 61 this(uri, null, null); 62 } 63 64 /** Creates an {@link Openable} for the contents @ uri with the given content-type. */ ContentOpenable(@onNull Uri uri, @NonNull String contentType)65 public ContentOpenable(@NonNull Uri uri, @NonNull String contentType) { 66 this(uri, contentType, null); 67 } 68 69 /** 70 * Creates an {@link Openable} for an image preview (of the given size) of the contents @ 71 * uri. 72 */ ContentOpenable(@onNull Uri uri, @NonNull Dimensions size)73 public ContentOpenable(@NonNull Uri uri, @NonNull Dimensions size) { 74 this(uri, null, size); 75 } 76 ContentOpenable(@onNull Uri uri, @Nullable String contentType, @Nullable Dimensions size)77 private ContentOpenable(@NonNull Uri uri, @Nullable String contentType, 78 @Nullable Dimensions size) { 79 Preconditions.checkNotNull(uri); 80 Preconditions.checkArgument(Uris.isContentUri(uri), 81 "Does not accept Uri " + uri.getScheme()); 82 this.mContentUri = uri; 83 this.mContentType = contentType; 84 this.mSize = size; 85 } 86 getContentUri()87 public @NonNull Uri getContentUri() { 88 return mContentUri; 89 } 90 getSize()91 public @Nullable Dimensions getSize() { 92 return mSize; 93 } 94 95 /** 96 * Returns a new instance of {@link androidx.pdf.data.Openable.Open}. 97 * 98 * NOTE: Clients are responsible for closing each instance that they obtain from this method. 99 * 100 * @return The {@link androidx.pdf.data.Openable.Open} for this Openable. 101 */ 102 @Override openWith(@onNull Opener opener)103 public @NonNull Open openWith(@NonNull Opener opener) throws IOException { 104 /* 105 * We want to explicitly return {@link Opener#open(ContentOpenable)} every time instead 106 * of just 107 * returning {@link #open} if it's not null, in case the underlying data is backed by a 108 * pipe, 109 * in which case we can't seek or re-read the resulting {@link android.os 110 * .ParcelFileDescriptor}, 111 * so callers can call this again to get a fresh handle on the underlying data. 112 */ 113 mOpen = opener.open(this); 114 return mOpen; 115 } 116 117 @Override getContentType()118 public @Nullable String getContentType() { 119 return mContentType; 120 } 121 122 @Override length()123 public long length() { 124 return mOpen != null ? mOpen.length() : -1; 125 } 126 127 @Override toString()128 public @NonNull String toString() { 129 return String.format("%s [%s]: %s / @%s", TAG, mContentType, mContentUri, mSize); 130 } 131 132 @Override writeToParcel(@onNull Parcel dest, int flags)133 public void writeToParcel(@NonNull Parcel dest, int flags) { 134 dest.writeParcelable(mContentUri, flags); 135 if (mContentType != null) { 136 dest.writeString(mContentType); 137 } else { 138 dest.writeString(""); 139 } 140 if (mSize != null) { 141 /* Value of 1 indicates that {@code size} is not null, to avoid un-parceling errors. */ 142 dest.writeInt(1); 143 dest.writeParcelable(mSize, flags); 144 } else { 145 dest.writeInt(0); 146 } 147 } 148 149 @Override describeContents()150 public int describeContents() { 151 return 0; 152 } 153 154 @SuppressWarnings("deprecation") 155 public static final Creator<ContentOpenable> CREATOR = new Creator<ContentOpenable>() { 156 @Override 157 public ContentOpenable createFromParcel(Parcel parcel) { 158 Uri uri = parcel.readParcelable(Uri.class.getClassLoader()); 159 String contentType = parcel.readString(); 160 if (contentType.isEmpty()) { 161 contentType = null; 162 } 163 Dimensions size = null; 164 boolean sizeIsPresent = parcel.readInt() > 0; 165 if (sizeIsPresent) { 166 size = parcel.readParcelable(Dimensions.class.getClassLoader()); 167 } 168 return new ContentOpenable(uri, contentType, size); 169 } 170 171 @Override 172 public ContentOpenable[] newArray(int size) { 173 return new ContentOpenable[size]; 174 } 175 }; 176 }