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.appsearch.app; 18 19 import android.os.Parcel; 20 import android.os.Parcelable; 21 22 import androidx.annotation.NonNull; 23 import androidx.annotation.Nullable; 24 import androidx.annotation.RestrictTo; 25 import androidx.appsearch.flags.FlaggedApi; 26 import androidx.appsearch.flags.Flags; 27 import androidx.appsearch.safeparcel.AbstractSafeParcelable; 28 import androidx.appsearch.safeparcel.SafeParcelable; 29 import androidx.appsearch.safeparcel.stub.StubCreators.AppSearchBlobHandleCreator; 30 import androidx.appsearch.util.IndentingStringBuilder; 31 import androidx.core.util.Preconditions; 32 33 import java.util.Arrays; 34 import java.util.Objects; 35 36 /** 37 * An identifier to represent a blob in AppSearch. 38 * 39 * <p>A "blob" is a large binary object. It is used to store a significant amount of data that is 40 * not searchable, such as images, videos, audio files, or other binary data. Unlike other fields in 41 * AppSearch, blobs are stored as blob files on disk rather than in memory, and use 42 * {@link android.os.ParcelFileDescriptor} to read and write. This allows for efficient handling of 43 * large, non-searchable content. 44 * 45 * <p> {@link AppSearchBlobHandle} is a light-weight {@code Property} of {@link GenericDocument}, 46 * which is a pointer to the heavy-weight blob data. 47 * 48 * <p> The blob data could be written via {@link AppSearchSession#openBlobForWriteAsync} and read 49 * via {@link AppSearchSession#openBlobForReadAsync}. 50 * 51 * <p> A {@link GenericDocument} with {@link AppSearchBlobHandle} {@code Property} could be put and 52 * read without the large blob data. This offers lazy retrieval to blob data when searching 53 * {@link GenericDocument} in AppSearch. 54 * 55 * @see GenericDocument.Builder#setPropertyBlobHandle 56 */ 57 @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE) 58 // TODO(b/384721898): Switch to JSpecify annotations 59 @SuppressWarnings({"HiddenSuperclass", "JSpecifyNullness"}) 60 @SafeParcelable.Class(creator = "AppSearchBlobHandleCreator") 61 @ExperimentalAppSearchApi 62 public final class AppSearchBlobHandle extends AbstractSafeParcelable { 63 /** The length of the SHA-256 digest in bytes. SHA-256 produces a 256-bit (32-byte) digest. */ 64 private static final int SHA_256_DIGEST_BYTE_LENGTH = 32; 65 66 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 67 @NonNull 68 public static final Parcelable.Creator<AppSearchBlobHandle> CREATOR = 69 new AppSearchBlobHandleCreator(); 70 @Field(id = 1, getter = "getSha256Digest") 71 private final @NonNull byte[] mSha256Digest; 72 73 @Field(id = 2, getter = "getPackageName") 74 private final @NonNull String mPackageName; 75 76 @Field(id = 3, getter = "getDatabaseName") 77 private final @NonNull String mDatabaseName; 78 79 @Field(id = 4, getter = "getNamespace") 80 private final @NonNull String mNamespace; 81 82 private @Nullable Integer mHashCode; 83 84 /** 85 * Build an {@link AppSearchBlobHandle}. 86 * @exportToFramework:hide 87 */ 88 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 89 @Constructor AppSearchBlobHandle( @aramid = 1) @onNull byte[] sha256Digest, @Param(id = 2) @NonNull String packageName, @Param(id = 3) @NonNull String databaseName, @Param(id = 4) @NonNull String namespace)90 AppSearchBlobHandle( 91 @Param(id = 1) @NonNull byte[] sha256Digest, 92 @Param(id = 2) @NonNull String packageName, 93 @Param(id = 3) @NonNull String databaseName, 94 @Param(id = 4) @NonNull String namespace) { 95 mSha256Digest = Preconditions.checkNotNull(sha256Digest); 96 Preconditions.checkState(sha256Digest.length == SHA_256_DIGEST_BYTE_LENGTH, 97 "The input digest isn't a sha-256 digest."); 98 mPackageName = Preconditions.checkNotNull(packageName); 99 mDatabaseName = Preconditions.checkNotNull(databaseName); 100 mNamespace = Preconditions.checkNotNull(namespace); 101 } 102 103 /** 104 * Returns the SHA-256 hash of the blob that this object is representing. 105 * 106 * <p> For two objects of {@link AppSearchBlobHandle} to be considered equal, the 107 * {@code packageName}, {@code database}, {@code namespace} and {@code digest} must be equal. 108 */ getSha256Digest()109 public @NonNull byte[] getSha256Digest() { 110 return mSha256Digest; 111 } 112 113 /** 114 * Returns the package name indicating the owner app of the blob that this object is 115 * representing. 116 * 117 * <p> For two objects of {@link AppSearchBlobHandle} to be considered equal, the 118 * {@code packageName}, {@code database}, {@code namespace} and {@code digest} must be equal. 119 */ getPackageName()120 public @NonNull String getPackageName() { 121 return mPackageName; 122 } 123 124 /** 125 * Returns the name of database stored the blob that this object is representing. 126 * 127 * <p> For two objects of {@link AppSearchBlobHandle} to be considered equal, the 128 * {@code packageName}, {@code database}, {@code namespace} and {@code digest} must be equal. 129 */ getDatabaseName()130 public @NonNull String getDatabaseName() { 131 return mDatabaseName; 132 } 133 134 /** 135 * Returns the app-defined namespace this blob resides in. 136 * 137 * <p> For two objects of {@link AppSearchBlobHandle} to be considered equal, the 138 * {@code packageName}, {@code database}, {@code namespace} and {@code digest} must be equal. 139 */ getNamespace()140 public @NonNull String getNamespace() { 141 return mNamespace; 142 } 143 144 @Override equals(Object o)145 public boolean equals(Object o) { 146 if (this == o) return true; 147 if (!(o instanceof AppSearchBlobHandle)) return false; 148 149 AppSearchBlobHandle that = (AppSearchBlobHandle) o; 150 if (!Arrays.equals(mSha256Digest, that.mSha256Digest)) return false; 151 return mPackageName.equals(that.mPackageName) && mDatabaseName.equals(that.mDatabaseName) 152 && mNamespace.equals(that.mNamespace); 153 } 154 155 @Override hashCode()156 public int hashCode() { 157 if (mHashCode == null) { 158 mHashCode = Objects.hash( 159 Arrays.hashCode(mSha256Digest), mPackageName, mDatabaseName, mNamespace); 160 } 161 return mHashCode; 162 } 163 164 @Override toString()165 public @NonNull String toString() { 166 IndentingStringBuilder builder = new IndentingStringBuilder(); 167 builder.append("{\n"); 168 builder.increaseIndentLevel(); 169 builder.append("packageName: \"").append(mPackageName).append("\",\n"); 170 builder.append("databaseName: \"").append(mDatabaseName).append("\",\n"); 171 builder.append("namespace: \"").append(mNamespace).append("\",\n"); 172 builder.append("digest: \""); 173 for (byte b : mSha256Digest) { 174 String hex = Integer.toHexString(0xFF & b); 175 if (hex.length() == 1) { 176 builder.append('0'); 177 } 178 builder.append(hex); 179 } 180 builder.append("\",\n").decreaseIndentLevel(); 181 builder.append("}"); 182 183 return builder.toString(); 184 } 185 186 /** 187 * Create a new AppSearch blob identifier with given digest, package, database and namespace. 188 * 189 * <p> The package name and database name indicated where this blob will be stored. To write, 190 * commit or read this blob via {@link AppSearchSession}, it must match the package name and 191 * database name of {@link AppSearchSession}. 192 * 193 * <p> For two objects of {@link AppSearchBlobHandle} to be considered equal, the 194 * {@code packageName}, {@code database}, {@code namespace} and {@code digest} must be equal. 195 * 196 * @param digest The SHA-256 hash of the blob this is representing. 197 * @param packageName The package name of the owner of this Blob. 198 * @param databaseName The database name of this blob to stored into. 199 * @param namespace The namespace of this blob resides in. 200 * 201 * @return a new instance of {@link AppSearchBlobHandle} object. 202 */ createWithSha256(@onNull byte[] digest, @NonNull String packageName, @NonNull String databaseName, @NonNull String namespace)203 public static @NonNull AppSearchBlobHandle createWithSha256(@NonNull byte[] digest, 204 @NonNull String packageName, @NonNull String databaseName, @NonNull String namespace) { 205 Preconditions.checkNotNull(digest); 206 Preconditions.checkArgument(digest.length == SHA_256_DIGEST_BYTE_LENGTH, 207 "The digest is not a SHA-256 digest"); 208 Preconditions.checkNotNull(packageName); 209 Preconditions.checkNotNull(databaseName); 210 Preconditions.checkNotNull(namespace); 211 return new AppSearchBlobHandle(digest, packageName, databaseName, namespace); 212 } 213 214 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 215 @Override writeToParcel(@onNull Parcel dest, int flags)216 public void writeToParcel(@NonNull Parcel dest, int flags) { 217 AppSearchBlobHandleCreator.writeToParcel(this, dest, flags); 218 } 219 } 220