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 }) 57 @Retention(RetentionPolicy.SOURCE) 58 public @interface ResultCode {} 59 60 /** The call was successful. */ 61 public static final int RESULT_OK = 0; 62 63 /** An unknown error occurred while processing the call. */ 64 public static final int RESULT_UNKNOWN_ERROR = 1; 65 66 /** 67 * An internal error occurred within AppSearch, which the caller cannot address. 68 * 69 * <p>This error may be considered similar to {@link IllegalStateException} 70 */ 71 public static final int RESULT_INTERNAL_ERROR = 2; 72 73 /** 74 * The caller supplied invalid arguments to the call. 75 * 76 * <p>This error may be considered similar to {@link IllegalArgumentException}. 77 */ 78 public static final int RESULT_INVALID_ARGUMENT = 3; 79 80 /** 81 * An issue occurred reading or writing to storage. The call might succeed if repeated. 82 * 83 * <p>This error may be considered similar to {@link java.io.IOException}. 84 */ 85 public static final int RESULT_IO_ERROR = 4; 86 87 /** Storage is out of space, and no more space could be reclaimed. */ 88 public static final int RESULT_OUT_OF_SPACE = 5; 89 90 /** An entity the caller requested to interact with does not exist in the system. */ 91 public static final int RESULT_NOT_FOUND = 6; 92 93 /** The caller supplied a schema which is invalid or incompatible with the previous schema. */ 94 public static final int RESULT_INVALID_SCHEMA = 7; 95 96 /** The caller requested an operation it does not have privileges for. */ 97 public static final int RESULT_SECURITY_ERROR = 8; 98 99 private final @ResultCode int mResultCode; 100 @Nullable private final ValueType mResultValue; 101 @Nullable private final String mErrorMessage; 102 AppSearchResult( @esultCode int resultCode, @Nullable ValueType resultValue, @Nullable String errorMessage)103 private AppSearchResult( 104 @ResultCode int resultCode, 105 @Nullable ValueType resultValue, 106 @Nullable String errorMessage) { 107 mResultCode = resultCode; 108 mResultValue = resultValue; 109 mErrorMessage = errorMessage; 110 } 111 112 /** Returns {@code true} if {@link #getResultCode} equals {@link AppSearchResult#RESULT_OK}. */ isSuccess()113 public boolean isSuccess() { 114 return getResultCode() == RESULT_OK; 115 } 116 117 /** Returns one of the {@code RESULT} constants defined in {@link AppSearchResult}. */ getResultCode()118 public @ResultCode int getResultCode() { 119 return mResultCode; 120 } 121 122 /** 123 * Returns the result value associated with this result, if it was successful. 124 * 125 * <p>See the documentation of the particular {@link AppSearchSession} call producing this 126 * {@link AppSearchResult} for what is placed in the result value by that call. 127 * 128 * @throws IllegalStateException if this {@link AppSearchResult} is not successful. 129 */ 130 @Nullable getResultValue()131 public ValueType getResultValue() { 132 if (!isSuccess()) { 133 throw new IllegalStateException("AppSearchResult is a failure: " + this); 134 } 135 return mResultValue; 136 } 137 138 /** 139 * Returns the error message associated with this result. 140 * 141 * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error 142 * message may be {@code null} even if {@link #isSuccess} is {@code false}. See the 143 * documentation of the particular {@link AppSearchSession} call producing this {@link 144 * AppSearchResult} for what is returned by {@link #getErrorMessage}. 145 */ 146 @Nullable getErrorMessage()147 public String getErrorMessage() { 148 return mErrorMessage; 149 } 150 151 @Override equals(@ullable Object other)152 public boolean equals(@Nullable Object other) { 153 if (this == other) { 154 return true; 155 } 156 if (!(other instanceof AppSearchResult)) { 157 return false; 158 } 159 AppSearchResult<?> otherResult = (AppSearchResult<?>) other; 160 return mResultCode == otherResult.mResultCode 161 && Objects.equals(mResultValue, otherResult.mResultValue) 162 && Objects.equals(mErrorMessage, otherResult.mErrorMessage); 163 } 164 165 @Override hashCode()166 public int hashCode() { 167 return Objects.hash(mResultCode, mResultValue, mErrorMessage); 168 } 169 170 @Override 171 @NonNull toString()172 public String toString() { 173 if (isSuccess()) { 174 return "[SUCCESS]: " + mResultValue; 175 } 176 return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage; 177 } 178 179 /** 180 * Creates a new successful {@link AppSearchResult}. 181 * 182 * @param value An optional value to associate with the successful result of the operation being 183 * performed. 184 */ 185 @NonNull newSuccessfulResult( @ullable ValueType value)186 public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult( 187 @Nullable ValueType value) { 188 return new AppSearchResult<>(RESULT_OK, value, /*errorMessage=*/ null); 189 } 190 191 /** 192 * Creates a new failed {@link AppSearchResult}. 193 * 194 * @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}. 195 * @param errorMessage An optional string describing the reason or nature of the failure. 196 */ 197 @NonNull newFailedResult( @esultCode int resultCode, @Nullable String errorMessage)198 public static <ValueType> AppSearchResult<ValueType> newFailedResult( 199 @ResultCode int resultCode, @Nullable String errorMessage) { 200 return new AppSearchResult<>(resultCode, /*resultValue=*/ null, errorMessage); 201 } 202 203 /** 204 * Creates a new failed {@link AppSearchResult} by a AppSearchResult in another type. 205 * 206 * @hide 207 */ 208 @NonNull newFailedResult( @onNull AppSearchResult<?> otherFailedResult)209 public static <ValueType> AppSearchResult<ValueType> newFailedResult( 210 @NonNull AppSearchResult<?> otherFailedResult) { 211 Preconditions.checkState( 212 !otherFailedResult.isSuccess(), 213 "Cannot convert a success result to a failed result"); 214 return AppSearchResult.newFailedResult( 215 otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage()); 216 } 217 218 /** @hide */ 219 @NonNull throwableToFailedResult( @onNull Throwable t)220 public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult( 221 @NonNull Throwable t) { 222 // Log for traceability. NOT_FOUND is logged at VERBOSE because this error can occur during 223 // the regular operation of the system (b/183550974). Everything else is indicative of an 224 // actual problem and is logged at WARN. 225 if (t instanceof AppSearchException 226 && ((AppSearchException) t).getResultCode() == RESULT_NOT_FOUND) { 227 if (LogUtil.DEBUG) { 228 Log.v(TAG, "Converting throwable to failed result: " + t); 229 } 230 } else { 231 Log.w(TAG, "Converting throwable to failed result.", t); 232 } 233 234 if (t instanceof AppSearchException) { 235 return ((AppSearchException) t).toAppSearchResult(); 236 } 237 238 String exceptionClass = t.getClass().getSimpleName(); 239 @AppSearchResult.ResultCode int resultCode; 240 if (t instanceof IllegalStateException || t instanceof NullPointerException) { 241 resultCode = AppSearchResult.RESULT_INTERNAL_ERROR; 242 } else if (t instanceof IllegalArgumentException) { 243 resultCode = AppSearchResult.RESULT_INVALID_ARGUMENT; 244 } else if (t instanceof IOException) { 245 resultCode = AppSearchResult.RESULT_IO_ERROR; 246 } else if (t instanceof SecurityException) { 247 resultCode = AppSearchResult.RESULT_SECURITY_ERROR; 248 } else { 249 resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR; 250 } 251 return AppSearchResult.newFailedResult(resultCode, exceptionClass + ": " + t.getMessage()); 252 } 253 } 254