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