Refactor how AppSearchResult and AppSearchBatchResult are parcelled.

Mainline modules seem to be metalava'd differently, causing the CREATOR,
describeContents(), and other parcelling methods to become part of the
public API.

Additionally, AppSearchResult and AppSearchBatchResult were forked from
Jetpack with parcelling added, and manually maintained.

Additionally, the parcelling wouldn't work properly if the keys and
values are not themselves parcelable, but this was not clearly
documented or called out.

This CL fixes these problems by creating parcelable wrappers around the
upstream Jetpack AppSearchResult and AppSearchBatchResult, usable only
when they contain parcelable keys and values.

Bug: 146218515
Test: Presubmit
Change-Id: I828a22dc6dae3e46f5d167f1a9d12627ba15d9fd
This commit is contained in:
Alexander Dorokhine
2021-05-04 13:24:47 -07:00
parent 321827b7f9
commit 2ee7641af7
17 changed files with 368 additions and 210 deletions

View File

@@ -18,6 +18,7 @@ package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.app.appsearch.aidl.IAppSearchManager;
import android.content.Context;
import com.android.internal.util.Preconditions;

View File

@@ -17,6 +17,7 @@ package android.app.appsearch;
import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
import android.app.appsearch.aidl.IAppSearchManager;
import android.content.Context;
/**

View File

@@ -23,6 +23,9 @@ import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.app.appsearch.exceptions.AppSearchException;
import android.os.Bundle;
import android.os.Parcel;
@@ -105,8 +108,8 @@ public class AppSearchMigrationHelper implements Closeable {
mUserId,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResult result) {
future.complete(result);
public void onResult(AppSearchResultParcel resultParcel) {
future.complete(resultParcel.getResult());
}
});
AppSearchResult<Void> result = future.get();
@@ -145,8 +148,8 @@ public class AppSearchMigrationHelper implements Closeable {
mService.putDocumentsFromFile(mPackageName, mDatabaseName, fileDescriptor, mUserId,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResult result) {
future.complete(result);
public void onResult(AppSearchResultParcel resultParcel) {
future.complete(resultParcel.getResult());
}
});
AppSearchResult<List<Bundle>> result = future.get();

View File

@@ -19,6 +19,11 @@ package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.appsearch.aidl.AppSearchBatchResultParcel;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.util.SchemaMigrationUtil;
import android.compat.annotation.UnsupportedAppUsage;
@@ -86,13 +91,15 @@ public final class AppSearchSession implements Closeable {
@NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
try {
mService.initialize(mUserId, new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Void> result = resultParcel.getResult();
if (result.isSuccess()) {
callback.accept(
AppSearchResult.newSuccessfulResult(AppSearchSession.this));
} else {
callback.accept(result);
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
@@ -191,15 +198,16 @@ public final class AppSearchSession implements Closeable {
mDatabaseName,
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Bundle> result = resultParcel.getResult();
if (result.isSuccess()) {
Bundle responseBundle = (Bundle) result.getResultValue();
GetSchemaResponse response =
new GetSchemaResponse(responseBundle);
new GetSchemaResponse(result.getResultValue());
callback.accept(AppSearchResult.newSuccessfulResult(response));
} else {
callback.accept(result);
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
@@ -227,15 +235,17 @@ public final class AppSearchSession implements Closeable {
mDatabaseName,
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<List<String>> result = resultParcel.getResult();
if (result.isSuccess()) {
Set<String> namespaces =
new ArraySet<>((List<String>) result.getResultValue());
new ArraySet<>(result.getResultValue());
callback.accept(
AppSearchResult.newSuccessfulResult(namespaces));
} else {
callback.accept(result);
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
@@ -280,13 +290,14 @@ public final class AppSearchSession implements Closeable {
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchBatchResultCallback.Stub() {
@Override
public void onResult(AppSearchBatchResult result) {
executor.execute(() -> callback.onResult(result));
public void onResult(AppSearchBatchResultParcel resultParcel) {
executor.execute(() -> callback.onResult(resultParcel.getResult()));
}
@Override
public void onSystemError(AppSearchResult result) {
executor.execute(() -> sendSystemErrorToCallback(result, callback));
public void onSystemError(AppSearchResultParcel resultParcel) {
executor.execute(() -> sendSystemErrorToCallback(
resultParcel.getResult(), callback));
}
});
mIsMutated = true;
@@ -341,15 +352,17 @@ public final class AppSearchSession implements Closeable {
mUserId,
new IAppSearchBatchResultCallback.Stub() {
@Override
public void onResult(AppSearchBatchResult result) {
public void onResult(AppSearchBatchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchBatchResult<String, Bundle> result =
resultParcel.getResult();
AppSearchBatchResult.Builder<String, GenericDocument>
documentResultBuilder =
new AppSearchBatchResult.Builder<>();
// Translate successful results
for (Map.Entry<String, Bundle> bundleEntry :
((Map<String, Bundle>) result.getSuccesses()).entrySet()) {
result.getSuccesses().entrySet()) {
GenericDocument document;
try {
document = new GenericDocument(bundleEntry.getValue());
@@ -380,8 +393,9 @@ public final class AppSearchSession implements Closeable {
}
@Override
public void onSystemError(AppSearchResult result) {
executor.execute(() -> sendSystemErrorToCallback(result, callback));
public void onSystemError(AppSearchResultParcel result) {
executor.execute(
() -> sendSystemErrorToCallback(result.getResult(), callback));
}
});
} catch (RemoteException e) {
@@ -492,8 +506,9 @@ public final class AppSearchSession implements Closeable {
/*systemUsage=*/ false,
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
executor.execute(() -> callback.accept(result));
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> callback.accept(resultParcel.getResult()));
}
});
mIsMutated = true;
@@ -550,13 +565,14 @@ public final class AppSearchSession implements Closeable {
new ArrayList<>(request.getIds()), mUserId,
new IAppSearchBatchResultCallback.Stub() {
@Override
public void onResult(AppSearchBatchResult result) {
executor.execute(() -> callback.onResult(result));
public void onResult(AppSearchBatchResultParcel resultParcel) {
executor.execute(() -> callback.onResult(resultParcel.getResult()));
}
@Override
public void onSystemError(AppSearchResult result) {
executor.execute(() -> sendSystemErrorToCallback(result, callback));
public void onSystemError(AppSearchResultParcel resultParcel) {
executor.execute(() -> sendSystemErrorToCallback(
resultParcel.getResult(), callback));
}
});
mIsMutated = true;
@@ -598,8 +614,9 @@ public final class AppSearchSession implements Closeable {
mService.removeByQuery(mPackageName, mDatabaseName, queryExpression,
searchSpec.getBundle(), mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
executor.execute(() -> callback.accept(result));
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> callback.accept(resultParcel.getResult()));
}
});
mIsMutated = true;
@@ -629,15 +646,15 @@ public final class AppSearchSession implements Closeable {
mDatabaseName,
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Bundle> result = resultParcel.getResult();
if (result.isSuccess()) {
Bundle responseBundle = (Bundle) result.getResultValue();
StorageInfo response =
new StorageInfo(responseBundle);
StorageInfo response = new StorageInfo(result.getResultValue());
callback.accept(AppSearchResult.newSuccessfulResult(response));
} else {
callback.accept(result);
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
@@ -686,13 +703,14 @@ public final class AppSearchSession implements Closeable {
request.getVersion(),
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Bundle> result = resultParcel.getResult();
if (result.isSuccess()) {
try {
SetSchemaResponse setSchemaResponse =
new SetSchemaResponse(
(Bundle) result.getResultValue());
new SetSchemaResponse(result.getResultValue());
if (!request.isForceOverride()) {
// Throw exception if there is any deleted types or
// incompatible types. That's the only case we swallowed
@@ -707,7 +725,7 @@ public final class AppSearchSession implements Closeable {
callback.accept(AppSearchResult.throwableToFailedResult(t));
}
} else {
callback.accept(result);
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
@@ -772,8 +790,9 @@ public final class AppSearchSession implements Closeable {
request.getVersion(),
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
setSchemaFuture.complete(result);
@Override
public void onResult(AppSearchResultParcel resultParcel) {
setSchemaFuture.complete(resultParcel.getResult());
}
});
AppSearchResult<Bundle> setSchemaResult = setSchemaFuture.get();
@@ -823,8 +842,8 @@ public final class AppSearchSession implements Closeable {
mUserId,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResult result) {
setSchema2Future.complete(result);
public void onResult(AppSearchResultParcel resultParcel) {
setSchema2Future.complete(resultParcel.getResult());
}
});
AppSearchResult<Bundle> setSchema2Result = setSchema2Future.get();

View File

@@ -16,10 +16,12 @@
package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.os.RemoteException;
import android.util.Log;
@@ -71,13 +73,15 @@ public class GlobalSearchSession implements Closeable {
@NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) {
try {
mService.initialize(mUserId, new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> {
AppSearchResult<Void> result = resultParcel.getResult();
if (result.isSuccess()) {
callback.accept(
AppSearchResult.newSuccessfulResult(GlobalSearchSession.this));
} else {
callback.accept(result);
callback.accept(AppSearchResult.newFailedResult(result));
}
});
}
@@ -159,8 +163,9 @@ public class GlobalSearchSession implements Closeable {
/*systemUsage=*/ true,
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
executor.execute(() -> callback.accept(result));
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> callback.accept(resultParcel.getResult()));
}
});
mIsMutated = true;

View File

@@ -20,6 +20,9 @@ import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
@@ -139,18 +142,20 @@ public class SearchResults implements Closeable {
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
return new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
executor.execute(() -> invokeCallback(result, callback));
@Override
public void onResult(AppSearchResultParcel resultParcel) {
executor.execute(() -> invokeCallback(resultParcel.getResult(), callback));
}
};
}
private void invokeCallback(AppSearchResult result,
private void invokeCallback(
@NonNull AppSearchResult<Bundle> searchResultPageResult,
@NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
if (result.isSuccess()) {
if (searchResultPageResult.isSuccess()) {
try {
SearchResultPage searchResultPage =
new SearchResultPage((Bundle) result.getResultValue());
new SearchResultPage(searchResultPageResult.getResultValue());
mNextPageToken = searchResultPage.getNextPageToken();
callback.accept(AppSearchResult.newSuccessfulResult(
searchResultPage.getResults()));
@@ -158,7 +163,7 @@ public class SearchResults implements Closeable {
callback.accept(AppSearchResult.throwableToFailedResult(t));
}
} else {
callback.accept(result);
callback.accept(AppSearchResult.newFailedResult(searchResultPageResult));
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2020, The Android Open Source Project
* Copyright 2021, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
package android.app.appsearch.aidl;
/** {@hide} */
parcelable AppSearchResult<ValueType>;
parcelable AppSearchBatchResultParcel<ValueType>;

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.aidl;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchResult;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Map;
import java.util.Objects;
/**
* Parcelable wrapper around {@link AppSearchBatchResult}.
*
* <p>{@link AppSearchBatchResult} can contain any type of key and value, including non-parcelable
* values. For the specific case of sending {@link AppSearchBatchResult} across Binder, this class
* wraps an {@link AppSearchBatchResult} that has String keys and Parcelable values. It provides
* parcelability of the whole structure.
*
* @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
* @hide
*/
public final class AppSearchBatchResultParcel<ValueType> implements Parcelable {
private final AppSearchBatchResult<String, ValueType> mResult;
/** Creates a new {@link AppSearchBatchResultParcel} from the given result. */
public AppSearchBatchResultParcel(@NonNull AppSearchBatchResult<String, ValueType> result) {
mResult = Objects.requireNonNull(result);
}
private AppSearchBatchResultParcel(@NonNull Parcel in) {
Bundle bundle = in.readBundle();
AppSearchBatchResult.Builder<String, ValueType> builder =
new AppSearchBatchResult.Builder<>();
for (String key : bundle.keySet()) {
AppSearchResultParcel<ValueType> resultParcel = bundle.getParcelable(key);
builder.setResult(key, resultParcel.getResult());
}
mResult = builder.build();
}
@NonNull
public AppSearchBatchResult<String, ValueType> getResult() {
return mResult;
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
Bundle bundle = new Bundle();
for (Map.Entry<String, AppSearchResult<ValueType>> entry
: mResult.getAll().entrySet()) {
bundle.putParcelable(entry.getKey(), new AppSearchResultParcel<>(entry.getValue()));
}
dest.writeBundle(bundle);
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** @hide */
@NonNull
public static final Creator<AppSearchBatchResultParcel<?>> CREATOR =
new Creator<AppSearchBatchResultParcel<?>>() {
@NonNull
@Override
public AppSearchBatchResultParcel<?> createFromParcel(@NonNull Parcel in) {
return new AppSearchBatchResultParcel<>(in);
}
@NonNull
@Override
public AppSearchBatchResultParcel<?>[] newArray(int size) {
return new AppSearchBatchResultParcel<?>[size];
}
};
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2020, The Android Open Source Project
* Copyright 2021, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
package android.app.appsearch.aidl;
/** {@hide} */
parcelable AppSearchBatchResult;
parcelable AppSearchResultParcel<ValueType>;

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch.aidl;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* Parcelable wrapper around {@link AppSearchResult}.
*
* <p>{@link AppSearchResult} can contain any value, including non-parcelable values. For the
* specific case of sending {@link AppSearchResult} across Binder, this class wraps an
* {@link AppSearchResult} that contains a parcelable type and provides parcelability of the whole
* structure.
*
* @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
* @hide
*/
public final class AppSearchResultParcel<ValueType> implements Parcelable {
private final AppSearchResult<ValueType> mResult;
/** Creates a new {@link AppSearchResultParcel} from the given result. */
public AppSearchResultParcel(@NonNull AppSearchResult<ValueType> result) {
mResult = Objects.requireNonNull(result);
}
private AppSearchResultParcel(@NonNull Parcel in) {
int resultCode = in.readInt();
ValueType resultValue = (ValueType) in.readValue(/*loader=*/ null);
String errorMessage = in.readString();
if (resultCode == AppSearchResult.RESULT_OK) {
mResult = AppSearchResult.newSuccessfulResult(resultValue);
} else {
mResult = AppSearchResult.newFailedResult(resultCode, errorMessage);
}
}
@NonNull
public AppSearchResult<ValueType> getResult() {
return mResult;
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mResult.getResultCode());
if (mResult.isSuccess()) {
dest.writeValue(mResult.getResultValue());
} else {
dest.writeValue(null);
}
dest.writeString(mResult.getErrorMessage());
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** @hide */
@NonNull
public static final Creator<AppSearchResultParcel<?>> CREATOR =
new Creator<AppSearchResultParcel<?>>() {
@NonNull
@Override
public AppSearchResultParcel<?> createFromParcel(@NonNull Parcel in) {
return new AppSearchResultParcel<>(in);
}
@NonNull
@Override
public AppSearchResultParcel<?>[] newArray(int size) {
return new AppSearchResultParcel<?>[size];
}
};
}

View File

@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
package android.app.appsearch.aidl;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.aidl.AppSearchBatchResultParcel;
import android.app.appsearch.aidl.AppSearchResultParcel;
/** {@hide} */
oneway interface IAppSearchBatchResultCallback {
void onResult(in AppSearchBatchResult result);
void onSystemError(in AppSearchResult result);
void onResult(in AppSearchBatchResultParcel resultParcel);
void onSystemError(in AppSearchResultParcel resultParcel);
}

View File

@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
package android.app.appsearch.aidl;
import android.os.Bundle;
import android.app.appsearch.IAppSearchBatchResultCallback;
import android.app.appsearch.IAppSearchResultCallback;
import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.os.ParcelFileDescriptor;
/** {@hide} */
@@ -200,7 +200,7 @@ interface IAppSearchManager {
* @param searchSpecBundle SearchSpec bundle.
* @param userId Id of the calling user.
* @param callback {@link IAppSearchResultCallback#onResult} will be called with an
* {@link AppSearchResult}&lt;{@code null}&gt;.
* {@link AppSearchResult}&lt;{@code Void}&gt;.
*/
void writeQueryResultsToFile(
in String packageName,

View File

@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
package android.app.appsearch.aidl;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.aidl.AppSearchResultParcel;
/** {@hide} */
oneway interface IAppSearchResultCallback {
void onResult(in AppSearchResult result);
void onResult(in AppSearchResultParcel resultParcel);
}

View File

@@ -13,37 +13,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import com.android.internal.util.Preconditions;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
/**
* Provides results for AppSearch batch operations which encompass multiple documents.
*
* <p>Individual results of a batch operation are separated into two maps: one for successes and
* one for failures. For successes, {@link #getSuccesses()} will return a map of keys to
* instances of the value type. For failures, {@link #getFailures()} will return a map of keys to
* {@link AppSearchResult} objects.
* <p>Individual results of a batch operation are separated into two maps: one for successes and one
* for failures. For successes, {@link #getSuccesses()} will return a map of keys to instances of
* the value type. For failures, {@link #getFailures()} will return a map of keys to {@link
* AppSearchResult} objects.
*
* <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for
* both successes and failures.
*
* @param <KeyType> The type of the keys for which the results will be reported.
* @param <ValueType> The type of the result objects for successful results.
* @see AppSearchSession#put
* @see AppSearchSession#getByDocumentId
* @see AppSearchSession#remove
*/
public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
public final class AppSearchBatchResult<KeyType, ValueType> {
@NonNull private final Map<KeyType, ValueType> mSuccesses;
@NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures;
@NonNull private final Map<KeyType, AppSearchResult<ValueType>> mAll;
@@ -57,27 +55,6 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
mAll = all;
}
private AppSearchBatchResult(@NonNull Parcel in) {
mAll = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
Map<KeyType, ValueType> successes = new ArrayMap<>();
Map<KeyType, AppSearchResult<ValueType>> failures = new ArrayMap<>();
for (Map.Entry<KeyType, AppSearchResult<ValueType>> entry : mAll.entrySet()) {
if (entry.getValue().isSuccess()) {
successes.put(entry.getKey(), entry.getValue().getResultValue());
} else {
failures.put(entry.getKey(), entry.getValue());
}
}
mSuccesses = Collections.unmodifiableMap(successes);
mFailures = Collections.unmodifiableMap(failures);
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mAll);
}
/** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
public boolean isSuccess() {
return mFailures.isEmpty();
@@ -99,8 +76,8 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
}
/**
* Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all
* failed individual results.
* Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all failed
* individual results.
*
* <p>The values of the {@link Map} will not be {@code null}.
*/
@@ -122,6 +99,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
/**
* Asserts that this {@link AppSearchBatchResult} has no failures.
*
* @hide
*/
public void checkSuccess() {
@@ -136,33 +114,13 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
return "{\n successes: " + mSuccesses + "\n failures: " + mFailures + "\n}";
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** @hide */
@NonNull
public static final Creator<AppSearchBatchResult> CREATOR =
new Creator<AppSearchBatchResult>() {
@NonNull
@Override
public AppSearchBatchResult createFromParcel(@NonNull Parcel in) {
return new AppSearchBatchResult(in);
}
@NonNull
@Override
public AppSearchBatchResult[] newArray(int size) {
return new AppSearchBatchResult[size];
}
};
/**
* Builder for {@link AppSearchBatchResult} objects.
*
* <p>Once {@link #build} is called, the instance can no longer be used.
*
* @param <KeyType> The type of the keys for which the results will be reported.
* @param <ValueType> The type of the result objects for successful results.
*/
public static final class Builder<KeyType, ValueType> {
private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>();
@@ -177,7 +135,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
*
* @throws IllegalStateException if the builder has already been used.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getSuccesses
@SuppressWarnings("MissingGetterMatchingBuilder") // See getSuccesses
@NonNull
public Builder<KeyType, ValueType> setSuccess(
@NonNull KeyType key, @Nullable ValueType result) {
@@ -193,7 +151,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
*
* @throws IllegalStateException if the builder has already been used.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getFailures
@SuppressWarnings("MissingGetterMatchingBuilder") // See getFailures
@NonNull
public Builder<KeyType, ValueType> setFailure(
@NonNull KeyType key,
@@ -211,7 +169,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl
*
* @throws IllegalStateException if the builder has already been used.
*/
@SuppressWarnings("MissingGetterMatchingBuilder") // See getAll
@SuppressWarnings("MissingGetterMatchingBuilder") // See getAll
@NonNull
public Builder<KeyType, ValueType> setResult(
@NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {

View File

@@ -13,15 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appsearch;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.exceptions.AppSearchException;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -36,24 +33,26 @@ import java.util.Objects;
*
* @param <ValueType> The type of result object for successful calls.
*/
public final class AppSearchResult<ValueType> implements Parcelable {
public final class AppSearchResult<ValueType> {
private static final String TAG = "AppSearchResult";
/**
* Result codes from {@link AppSearchSession} methods.
*
* @hide
*/
@IntDef(value = {
RESULT_OK,
RESULT_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_IO_ERROR,
RESULT_OUT_OF_SPACE,
RESULT_NOT_FOUND,
RESULT_INVALID_SCHEMA,
RESULT_SECURITY_ERROR,
})
@IntDef(
value = {
RESULT_OK,
RESULT_UNKNOWN_ERROR,
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_IO_ERROR,
RESULT_OUT_OF_SPACE,
RESULT_NOT_FOUND,
RESULT_INVALID_SCHEMA,
RESULT_SECURITY_ERROR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
@@ -109,20 +108,6 @@ public final class AppSearchResult<ValueType> implements Parcelable {
mErrorMessage = errorMessage;
}
private AppSearchResult(@NonNull Parcel in) {
mResultCode = in.readInt();
mResultValue = (ValueType) in.readValue(/*loader=*/ null);
mErrorMessage = in.readString();
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mResultCode);
dest.writeValue(mResultValue);
dest.writeString(mErrorMessage);
}
/** Returns {@code true} if {@link #getResultCode} equals {@link AppSearchResult#RESULT_OK}. */
public boolean isSuccess() {
return getResultCode() == RESULT_OK;
@@ -154,8 +139,8 @@ public final class AppSearchResult<ValueType> implements Parcelable {
*
* <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
* message may be {@code null} even if {@link #isSuccess} is {@code false}. See the
* documentation of the particular {@link AppSearchSession} call producing this
* {@link AppSearchResult} for what is returned by {@link #getErrorMessage}.
* documentation of the particular {@link AppSearchSession} call producing this {@link
* AppSearchResult} for what is returned by {@link #getErrorMessage}.
*/
@Nullable
public String getErrorMessage() {
@@ -190,40 +175,14 @@ public final class AppSearchResult<ValueType> implements Parcelable {
return "[FAILURE(" + mResultCode + ")]: " + mErrorMessage;
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** @hide */
@NonNull
public static final Creator<AppSearchResult> CREATOR = new Creator<AppSearchResult>() {
@NonNull
@Override
public AppSearchResult createFromParcel(@NonNull Parcel in) {
return new AppSearchResult(in);
}
@NonNull
@Override
public AppSearchResult[] newArray(int size) {
return new AppSearchResult[size];
}
};
/**
* Creates a new successful {@link AppSearchResult}.
*/
/** Creates a new successful {@link AppSearchResult}. */
@NonNull
public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
@Nullable ValueType value) {
return new AppSearchResult<>(RESULT_OK, value, /*errorMessage=*/ null);
}
/**
* Creates a new failed {@link AppSearchResult}.
*/
/** Creates a new failed {@link AppSearchResult}. */
@NonNull
public static <ValueType> AppSearchResult<ValueType> newFailedResult(
@ResultCode int resultCode, @Nullable String errorMessage) {
@@ -238,7 +197,8 @@ public final class AppSearchResult<ValueType> implements Parcelable {
@NonNull
public static <ValueType> AppSearchResult<ValueType> newFailedResult(
@NonNull AppSearchResult<?> otherFailedResult) {
Preconditions.checkState(!otherFailedResult.isSuccess(),
Preconditions.checkState(
!otherFailedResult.isSuccess(),
"Cannot convert a success result to a failed result");
return AppSearchResult.newFailedResult(
otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage());

View File

@@ -29,14 +29,16 @@ import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.IAppSearchBatchResultCallback;
import android.app.appsearch.IAppSearchManager;
import android.app.appsearch.IAppSearchResultCallback;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.StorageInfo;
import android.app.appsearch.aidl.AppSearchBatchResultParcel;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -869,7 +871,7 @@ public class AppSearchManagerService extends SystemService {
private void invokeCallbackOnResult(
IAppSearchResultCallback callback, AppSearchResult<?> result) {
try {
callback.onResult(result);
callback.onResult(new AppSearchResultParcel<>(result));
} catch (RemoteException e) {
Log.e(TAG, "Unable to send result to the callback", e);
}
@@ -877,9 +879,9 @@ public class AppSearchManagerService extends SystemService {
/** Invokes the {@link IAppSearchBatchResultCallback} with the result. */
private void invokeCallbackOnResult(
IAppSearchBatchResultCallback callback, AppSearchBatchResult<?, ?> result) {
IAppSearchBatchResultCallback callback, AppSearchBatchResult<String, ?> result) {
try {
callback.onResult(result);
callback.onResult(new AppSearchBatchResultParcel<>(result));
} catch (RemoteException e) {
Log.e(TAG, "Unable to send result to the callback", e);
}
@@ -891,8 +893,9 @@ public class AppSearchManagerService extends SystemService {
* <p>The throwable is convert to a {@link AppSearchResult};
*/
private void invokeCallbackOnError(IAppSearchResultCallback callback, Throwable throwable) {
AppSearchResult<?> result = throwableToFailedResult(throwable);
try {
callback.onResult(throwableToFailedResult(throwable));
callback.onResult(new AppSearchResultParcel<>(result));
} catch (RemoteException e) {
Log.e(TAG, "Unable to send result to the callback", e);
}
@@ -905,8 +908,9 @@ public class AppSearchManagerService extends SystemService {
*/
private void invokeCallbackOnError(
@NonNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable) {
AppSearchResult<?> result = throwableToFailedResult(throwable);
try {
callback.onSystemError(throwableToFailedResult(throwable));
callback.onSystemError(new AppSearchResultParcel<>(result));
} catch (RemoteException e) {
Log.e(TAG, "Unable to send error to the callback", e);
}

View File

@@ -50,12 +50,14 @@ import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.IAppSearchBatchResultCallback;
import android.app.appsearch.IAppSearchManager;
import android.app.appsearch.IAppSearchResultCallback;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.aidl.AppSearchBatchResultParcel;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -675,7 +677,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
}
final SetSchemaResponse response = new SetSchemaResponse.Builder().build();
callback.onResult(AppSearchResult.newSuccessfulResult(response.getBundle()));
callback.onResult(
new AppSearchResultParcel(
AppSearchResult.newSuccessfulResult(response.getBundle())));
}
@Override
@@ -711,7 +715,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
docMap.put(doc.getId(), doc);
}
callback.onResult(builder.build());
callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
}
@Override
@@ -737,7 +741,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
}
}
callback.onResult(builder.build());
callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
}
@Override
@@ -749,7 +753,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
final Bundle page = new Bundle();
page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
callback.onResult(AppSearchResult.newSuccessfulResult(page));
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
return;
}
final List<GenericDocument> documents = new ArrayList<>(mDocumentMap.get(key).values());
@@ -765,7 +770,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
resultBundles.add(resultBundle);
}
page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
callback.onResult(AppSearchResult.newSuccessfulResult(page));
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
}
@Override
@@ -780,7 +786,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
final Bundle page = new Bundle();
page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
callback.onResult(AppSearchResult.newSuccessfulResult(page));
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
}
@Override
@@ -835,7 +842,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
}
}
callback.onResult(builder.build());
callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
}
@Override
@@ -844,11 +851,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
throws RemoteException {
final String key = getKey(userId, databaseName);
if (!mDocumentMap.containsKey(key)) {
callback.onResult(AppSearchResult.newSuccessfulResult(null));
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
return;
}
mDocumentMap.get(key).clear();
callback.onResult(AppSearchResult.newSuccessfulResult(null));
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
}
@Override
@@ -878,7 +887,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
private void ignore(IAppSearchResultCallback callback) throws RemoteException {
callback.onResult(AppSearchResult.newSuccessfulResult(null));
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
}
}