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