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 }