• 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.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