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