1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.view.inputmethod;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.content.ClipDescription;
23 import android.content.ContentProvider;
24 import android.net.Uri;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 
30 import com.android.internal.inputmethod.IInputContentUriToken;
31 
32 import java.security.InvalidParameterException;
33 
34 /**
35  * A container object with which input methods can send content files to the target application.
36  */
37 public final class InputContentInfo implements Parcelable {
38 
39     /**
40      * The content URI that may or may not have a user ID embedded by
41      * {@link ContentProvider#maybeAddUserId(Uri, int)}.  This always preserves the exact value
42      * specified to a constructor.  In other words, if it had user ID embedded when it was passed
43      * to the constructor, it still has the same user ID no matter if it is valid or not.
44      */
45     @NonNull
46     private final Uri mContentUri;
47     /**
48      * The user ID to which {@link #mContentUri} belongs to.  If {@link #mContentUri} already
49      * embedded the user ID when it was specified then this fields has the same user ID.  Otherwise
50      * the user ID is determined based on the process ID when the constructor is called.
51      *
52      * <p>CAUTION: If you received {@link InputContentInfo} from a different process, there is no
53      * guarantee that this value is correct and valid.  Never use this for any security purpose</p>
54      */
55     @UserIdInt
56     private final int mContentUriOwnerUserId;
57     @NonNull
58     private final ClipDescription mDescription;
59     @Nullable
60     private final Uri mLinkUri;
61     @NonNull
62     private IInputContentUriToken mUriToken;
63 
64     /**
65      * Constructs {@link InputContentInfo} object only with mandatory data.
66      *
67      * @param contentUri Content URI to be exported from the input method.
68      * This cannot be {@code null}.
69      * @param description A {@link ClipDescription} object that contains the metadata of
70      * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
71      * {@link ClipDescription#getLabel()} should be describing the content specified by
72      * {@code contentUri} for accessibility reasons.
73      */
InputContentInfo(@onNull Uri contentUri, @NonNull ClipDescription description)74     public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description) {
75         this(contentUri, description, null /* link Uri */);
76     }
77 
78     /**
79      * Constructs {@link InputContentInfo} object with additional link URI.
80      *
81      * @param contentUri Content URI to be exported from the input method.
82      * This cannot be {@code null}.
83      * @param description A {@link ClipDescription} object that contains the metadata of
84      * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
85      * {@link ClipDescription#getLabel()} should be describing the content specified by
86      * {@code contentUri} for accessibility reasons.
87      * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
88      * a way to navigate the user to the specified web page if this is not {@code null}.
89      * @throws InvalidParameterException if any invalid parameter is specified.
90      */
InputContentInfo(@onNull Uri contentUri, @NonNull ClipDescription description, @Nullable Uri linkUri)91     public InputContentInfo(@NonNull Uri contentUri, @NonNull ClipDescription description,
92             @Nullable Uri linkUri) {
93         validateInternal(contentUri, description, linkUri, true /* throwException */);
94         mContentUri = contentUri;
95         mContentUriOwnerUserId =
96                 ContentProvider.getUserIdFromUri(mContentUri, UserHandle.myUserId());
97         mDescription = description;
98         mLinkUri = linkUri;
99     }
100 
101     /**
102      * @return {@code true} if all the fields are valid.
103      * @hide
104      */
validate()105     public boolean validate() {
106         return validateInternal(mContentUri, mDescription, mLinkUri, false /* throwException */);
107     }
108 
109     /**
110      * Constructs {@link InputContentInfo} object with additional link URI.
111      *
112      * @param contentUri Content URI to be exported from the input method.
113      * This cannot be {@code null}.
114      * @param description A {@link ClipDescription} object that contains the metadata of
115      * {@code contentUri} such as MIME type(s). This object cannot be {@code null}. Also
116      * {@link ClipDescription#getLabel()} should be describing the content specified by
117      * {@code contentUri} for accessibility reasons.
118      * @param linkUri An optional {@code http} or {@code https} URI. The editor author may provide
119      * a way to navigate the user to the specified web page if this is not {@code null}.
120      * @param throwException {@code true} if this method should throw an
121      * {@link InvalidParameterException}.
122      * @throws InvalidParameterException if any invalid parameter is specified.
123      */
validateInternal(@onNull Uri contentUri, @NonNull ClipDescription description, @Nullable Uri linkUri, boolean throwException)124     private static boolean validateInternal(@NonNull Uri contentUri,
125             @NonNull ClipDescription description, @Nullable Uri linkUri, boolean throwException) {
126         if (contentUri == null) {
127             if (throwException) {
128                 throw new NullPointerException("contentUri");
129             }
130             return false;
131         }
132         if (description == null) {
133             if (throwException) {
134                 throw new NullPointerException("description");
135             }
136             return false;
137         }
138         final String contentUriScheme = contentUri.getScheme();
139         if (!"content".equals(contentUriScheme)) {
140             if (throwException) {
141                 throw new InvalidParameterException("contentUri must have content scheme");
142             }
143             return false;
144         }
145         if (linkUri != null) {
146             final String scheme = linkUri.getScheme();
147             if (scheme == null ||
148                     (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https"))) {
149                 if (throwException) {
150                     throw new InvalidParameterException(
151                             "linkUri must have either http or https scheme");
152                 }
153                 return false;
154             }
155         }
156         return true;
157     }
158 
159     /**
160      * @return Content URI with which the content can be obtained.
161      */
162     @NonNull
getContentUri()163     public Uri getContentUri() {
164         // Fix up the content URI when and only when the caller's user ID does not match the owner's
165         // user ID.
166         if (mContentUriOwnerUserId != UserHandle.myUserId()) {
167             return ContentProvider.maybeAddUserId(mContentUri, mContentUriOwnerUserId);
168         }
169         return mContentUri;
170     }
171 
172     /**
173      * @return {@link ClipDescription} object that contains the metadata of {@code #getContentUri()}
174      * such as MIME type(s). {@link ClipDescription#getLabel()} can be used for accessibility
175      * purpose.
176      */
177     @NonNull
getDescription()178     public ClipDescription getDescription() { return mDescription; }
179 
180     /**
181      * @return An optional {@code http} or {@code https} URI that is related to this content.
182      */
183     @Nullable
getLinkUri()184     public Uri getLinkUri() { return mLinkUri; }
185 
186     /**
187      * Update the internal state of this object to be associated with the given token.
188      *
189      * <p>TODO(yukawa): Come up with an idea to make {@link InputContentInfo} immutable.</p>
190      *
191      * @param token special URI token obtained from the system.
192      * @hide
193      */
setUriToken(IInputContentUriToken token)194     public void setUriToken(IInputContentUriToken token) {
195         if (mUriToken != null) {
196             throw new IllegalStateException("URI token is already set");
197         }
198         mUriToken = token;
199     }
200 
201     /**
202      * Requests a temporary read-only access permission for content URI associated with this object.
203      *
204      * <p>Does nothing if the temporary permission is already granted.</p>
205      */
requestPermission()206     public void requestPermission() {
207         if (mUriToken == null) {
208             return;
209         }
210         try {
211             mUriToken.take();
212         } catch (RemoteException e) {
213             e.rethrowFromSystemServer();
214         }
215     }
216 
217     /**
218      * Releases a temporary read-only access permission for content URI associated with this object.
219      *
220      * <p>Does nothing if the temporary permission is not granted.</p>
221      */
releasePermission()222     public void releasePermission() {
223         if (mUriToken == null) {
224             return;
225         }
226         try {
227             mUriToken.release();
228         } catch (RemoteException e) {
229             e.rethrowFromSystemServer();
230         }
231     }
232 
233     /**
234      * Used to package this object into a {@link Parcel}.
235      *
236      * @param dest The {@link Parcel} to be written.
237      * @param flags The flags used for parceling.
238      */
239     @Override
writeToParcel(Parcel dest, int flags)240     public void writeToParcel(Parcel dest, int flags) {
241         Uri.writeToParcel(dest, mContentUri);
242         dest.writeInt(mContentUriOwnerUserId);
243         mDescription.writeToParcel(dest, flags);
244         Uri.writeToParcel(dest, mLinkUri);
245         if (mUriToken != null) {
246             dest.writeInt(1);
247             dest.writeStrongBinder(mUriToken.asBinder());
248         } else {
249             dest.writeInt(0);
250         }
251     }
252 
InputContentInfo(@onNull Parcel source)253     private InputContentInfo(@NonNull Parcel source) {
254         mContentUri = Uri.CREATOR.createFromParcel(source);
255         mContentUriOwnerUserId = source.readInt();
256         mDescription = ClipDescription.CREATOR.createFromParcel(source);
257         mLinkUri = Uri.CREATOR.createFromParcel(source);
258         if (source.readInt() == 1) {
259             mUriToken = IInputContentUriToken.Stub.asInterface(source.readStrongBinder());
260         } else {
261             mUriToken = null;
262         }
263     }
264 
265     /**
266      * Used to make this class parcelable.
267      */
268     public static final @android.annotation.NonNull Parcelable.Creator<InputContentInfo> CREATOR
269             = new Parcelable.Creator<InputContentInfo>() {
270         @Override
271         public InputContentInfo createFromParcel(Parcel source) {
272             return new InputContentInfo(source);
273         }
274 
275         @Override
276         public InputContentInfo[] newArray(int size) {
277             return new InputContentInfo[size];
278         }
279     };
280 
281     /**
282      * {@inheritDoc}
283      */
284     @Override
describeContents()285     public int describeContents() {
286         return 0;
287     }
288 }
289