• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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