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 {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION read-only} 203 * access permission for the content URI associated with this object. 204 * 205 * <p>The lifecycle of the permission granted here is tied to this object instance. If the 206 * permission is not released explicitly via {@link #releasePermission()}, it will be 207 * released automatically when there are no more references to this object.</p> 208 * 209 * <p>Does nothing if the temporary permission is already granted.</p> 210 */ requestPermission()211 public void requestPermission() { 212 if (mUriToken == null) { 213 return; 214 } 215 try { 216 mUriToken.take(); 217 } catch (RemoteException e) { 218 e.rethrowFromSystemServer(); 219 } 220 } 221 222 /** 223 * Releases a temporary read-only access permission for content URI associated with this object. 224 * 225 * <p>Does nothing if the temporary permission is not granted.</p> 226 */ releasePermission()227 public void releasePermission() { 228 if (mUriToken == null) { 229 return; 230 } 231 try { 232 mUriToken.release(); 233 } catch (RemoteException e) { 234 e.rethrowFromSystemServer(); 235 } 236 } 237 238 /** 239 * Used to package this object into a {@link Parcel}. 240 * 241 * @param dest The {@link Parcel} to be written. 242 * @param flags The flags used for parceling. 243 */ 244 @Override writeToParcel(Parcel dest, int flags)245 public void writeToParcel(Parcel dest, int flags) { 246 Uri.writeToParcel(dest, mContentUri); 247 dest.writeInt(mContentUriOwnerUserId); 248 mDescription.writeToParcel(dest, flags); 249 Uri.writeToParcel(dest, mLinkUri); 250 if (mUriToken != null) { 251 dest.writeInt(1); 252 dest.writeStrongBinder(mUriToken.asBinder()); 253 } else { 254 dest.writeInt(0); 255 } 256 } 257 InputContentInfo(@onNull Parcel source)258 private InputContentInfo(@NonNull Parcel source) { 259 mContentUri = Uri.CREATOR.createFromParcel(source); 260 mContentUriOwnerUserId = source.readInt(); 261 mDescription = ClipDescription.CREATOR.createFromParcel(source); 262 mLinkUri = Uri.CREATOR.createFromParcel(source); 263 if (source.readInt() == 1) { 264 mUriToken = IInputContentUriToken.Stub.asInterface(source.readStrongBinder()); 265 } else { 266 mUriToken = null; 267 } 268 } 269 270 /** 271 * Used to make this class parcelable. 272 */ 273 public static final @android.annotation.NonNull Parcelable.Creator<InputContentInfo> CREATOR 274 = new Parcelable.Creator<InputContentInfo>() { 275 @Override 276 public InputContentInfo createFromParcel(Parcel source) { 277 return new InputContentInfo(source); 278 } 279 280 @Override 281 public InputContentInfo[] newArray(int size) { 282 return new InputContentInfo[size]; 283 } 284 }; 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override describeContents()290 public int describeContents() { 291 return 0; 292 } 293 } 294