1 /* 2 * Copyright 2020 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 package android.app.appsearch; 17 18 import android.annotation.FlaggedApi; 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.appsearch.exceptions.AppSearchException; 23 import android.app.appsearch.util.LogUtil; 24 import android.util.Log; 25 26 import com.android.appsearch.flags.Flags; 27 import com.android.internal.util.Preconditions; 28 29 import java.io.IOException; 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.util.Objects; 33 34 /** 35 * Information about the success or failure of an AppSearch call. 36 * 37 * @param <ValueType> The type of result object for successful calls. 38 */ 39 // TODO(b/384721898): Switch to JSpecify annotations 40 @SuppressWarnings("JSpecifyNullness") 41 public final class AppSearchResult<ValueType> { 42 private static final String TAG = "AppSearchResult"; 43 44 /** 45 * Result codes from {@link AppSearchSession} methods. 46 * 47 * @hide 48 */ 49 @IntDef( 50 value = { 51 RESULT_OK, 52 RESULT_UNKNOWN_ERROR, 53 RESULT_INTERNAL_ERROR, 54 RESULT_INVALID_ARGUMENT, 55 RESULT_IO_ERROR, 56 RESULT_OUT_OF_SPACE, 57 RESULT_NOT_FOUND, 58 RESULT_INVALID_SCHEMA, 59 RESULT_SECURITY_ERROR, 60 RESULT_DENIED, 61 RESULT_RATE_LIMITED, 62 RESULT_ALREADY_EXISTS 63 }) 64 @Retention(RetentionPolicy.SOURCE) 65 public @interface ResultCode {} 66 67 /** The call was successful. */ 68 public static final int RESULT_OK = 0; 69 70 /** An unknown error occurred while processing the call. */ 71 public static final int RESULT_UNKNOWN_ERROR = 1; 72 73 /** 74 * An internal error occurred within AppSearch, which the caller cannot address. 75 * 76 * <p>This error may be considered similar to {@link IllegalStateException} 77 */ 78 public static final int RESULT_INTERNAL_ERROR = 2; 79 80 /** 81 * The caller supplied invalid arguments to the call. 82 * 83 * <p>This error may be considered similar to {@link IllegalArgumentException}. 84 */ 85 public static final int RESULT_INVALID_ARGUMENT = 3; 86 87 /** 88 * An issue occurred reading or writing to storage. The call might succeed if repeated. 89 * 90 * <p>This error may be considered similar to {@link java.io.IOException}. 91 */ 92 public static final int RESULT_IO_ERROR = 4; 93 94 /** Storage is out of space, and no more space could be reclaimed. */ 95 public static final int RESULT_OUT_OF_SPACE = 5; 96 97 /** An entity the caller requested to interact with does not exist in the system. */ 98 public static final int RESULT_NOT_FOUND = 6; 99 100 /** The caller supplied a schema which is invalid or incompatible with the previous schema. */ 101 public static final int RESULT_INVALID_SCHEMA = 7; 102 103 /** The caller requested an operation it does not have privileges for. */ 104 public static final int RESULT_SECURITY_ERROR = 8; 105 106 /** 107 * The requested operation is denied for the caller. This error is logged and returned for 108 * denylist rejections. 109 */ 110 @FlaggedApi(Flags.FLAG_ENABLE_RESULT_DENIED_AND_RESULT_RATE_LIMITED) 111 public static final int RESULT_DENIED = 9; 112 113 /** 114 * The caller has hit AppSearch's rate limit and the requested operation has been rejected. The 115 * caller is recommended to reschedule tasks with exponential backoff. 116 */ 117 @FlaggedApi(Flags.FLAG_ENABLE_RESULT_DENIED_AND_RESULT_RATE_LIMITED) 118 public static final int RESULT_RATE_LIMITED = 10; 119 120 /** The operation is invalid because the resource already exists and can't be replaced. */ 121 @FlaggedApi(Flags.FLAG_ENABLE_RESULT_ALREADY_EXISTS) 122 public static final int RESULT_ALREADY_EXISTS = 12; 123 124 @ResultCode private final int mResultCode; 125 private final @Nullable ValueType mResultValue; 126 private final @Nullable String mErrorMessage; 127 AppSearchResult( @esultCode int resultCode, @Nullable ValueType resultValue, @Nullable String errorMessage)128 private AppSearchResult( 129 @ResultCode int resultCode, 130 @Nullable ValueType resultValue, 131 @Nullable String errorMessage) { 132 mResultCode = resultCode; 133 mResultValue = resultValue; 134 mErrorMessage = errorMessage; 135 } 136 137 /** Returns {@code true} if {@link #getResultCode} equals {@link AppSearchResult#RESULT_OK}. */ isSuccess()138 public boolean isSuccess() { 139 return getResultCode() == RESULT_OK; 140 } 141 142 /** Returns one of the {@code RESULT} constants defined in {@link AppSearchResult}. */ 143 @ResultCode getResultCode()144 public int getResultCode() { 145 return mResultCode; 146 } 147 148 /** 149 * Returns the result value associated with this result, if it was successful. 150 * 151 * <p>See the documentation of the particular {@link AppSearchSession} call producing this 152 * {@link AppSearchResult} for what is placed in the result value by that call. 153 * 154 * @throws IllegalStateException if this {@link AppSearchResult} is not successful. 155 */ getResultValue()156 public @Nullable ValueType getResultValue() { 157 if (!isSuccess()) { 158 throw new IllegalStateException("AppSearchResult is a failure: " + this); 159 } 160 return mResultValue; 161 } 162 163 /** 164 * Returns the error message associated with this result. 165 * 166 * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error 167 * message may be {@code null} even if {@link #isSuccess} is {@code false}. See the 168 * documentation of the particular {@link AppSearchSession} call producing this {@link 169 * AppSearchResult} for what is returned by {@link #getErrorMessage}. 170 */ getErrorMessage()171 public @Nullable String getErrorMessage() { 172 return mErrorMessage; 173 } 174 175 @Override equals(@ullable Object other)176 public boolean equals(@Nullable Object other) { 177 if (this == other) { 178 return true; 179 } 180 if (!(other instanceof AppSearchResult)) { 181 return false; 182 } 183 AppSearchResult<?> otherResult = (AppSearchResult<?>) other; 184 return mResultCode == otherResult.mResultCode 185 && Objects.equals(mResultValue, otherResult.mResultValue) 186 && Objects.equals(mErrorMessage, otherResult.mErrorMessage); 187 } 188 189 @Override hashCode()190 public int hashCode() { 191 return Objects.hash(mResultCode, mResultValue, mErrorMessage); 192 } 193 194 @Override toString()195 public @NonNull String toString() { 196 if (isSuccess()) { 197 return "[SUCCESS]: " + mResultValue; 198 } 199 return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage; 200 } 201 202 /** 203 * Creates a new successful {@link AppSearchResult}. 204 * 205 * @param value An optional value to associate with the successful result of the operation being 206 * performed. 207 */ newSuccessfulResult( @ullable ValueType value)208 public static @NonNull <ValueType> AppSearchResult<ValueType> newSuccessfulResult( 209 @Nullable ValueType value) { 210 return new AppSearchResult<>(RESULT_OK, value, /* errorMessage= */ null); 211 } 212 213 /** 214 * Creates a new failed {@link AppSearchResult}. 215 * 216 * @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}. 217 * @param errorMessage An optional string describing the reason or nature of the failure. 218 */ newFailedResult( @esultCode int resultCode, @Nullable String errorMessage)219 public static @NonNull <ValueType> AppSearchResult<ValueType> newFailedResult( 220 @ResultCode int resultCode, @Nullable String errorMessage) { 221 return new AppSearchResult<>(resultCode, /* resultValue= */ null, errorMessage); 222 } 223 224 /** 225 * Creates a new failed {@link AppSearchResult} by a AppSearchResult in another type. 226 * 227 * @hide 228 */ newFailedResult( @onNull AppSearchResult<?> otherFailedResult)229 public static @NonNull <ValueType> AppSearchResult<ValueType> newFailedResult( 230 @NonNull AppSearchResult<?> otherFailedResult) { 231 Preconditions.checkState( 232 !otherFailedResult.isSuccess(), 233 "Cannot convert a success result to a failed result"); 234 return AppSearchResult.newFailedResult( 235 otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage()); 236 } 237 238 /** @hide */ throwableToFailedResult( @onNull Throwable t)239 public static @NonNull <ValueType> AppSearchResult<ValueType> throwableToFailedResult( 240 @NonNull Throwable t) { 241 // Log for traceability. NOT_FOUND is logged at VERBOSE because this error can occur during 242 // the regular operation of the system (b/183550974). Everything else is indicative of an 243 // actual problem and is logged at WARN. 244 if (t instanceof AppSearchException 245 && ((AppSearchException) t).getResultCode() == RESULT_NOT_FOUND) { 246 if (LogUtil.DEBUG) { 247 Log.v(TAG, "Converting throwable to failed result: " + t); 248 } 249 } else { 250 Log.w(TAG, "Converting throwable to failed result.", t); 251 } 252 253 if (t instanceof AppSearchException) { 254 return ((AppSearchException) t).toAppSearchResult(); 255 } 256 257 String exceptionClass = t.getClass().getSimpleName(); 258 @AppSearchResult.ResultCode int resultCode; 259 if (t instanceof IllegalStateException || t instanceof NullPointerException) { 260 resultCode = AppSearchResult.RESULT_INTERNAL_ERROR; 261 } else if (t instanceof IllegalArgumentException) { 262 resultCode = AppSearchResult.RESULT_INVALID_ARGUMENT; 263 } else if (t instanceof IOException) { 264 resultCode = AppSearchResult.RESULT_IO_ERROR; 265 } else if (t instanceof SecurityException) { 266 resultCode = AppSearchResult.RESULT_SECURITY_ERROR; 267 } else { 268 resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR; 269 } 270 return AppSearchResult.newFailedResult(resultCode, exceptionClass + ": " + t.getMessage()); 271 } 272 } 273