/*
* 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 static android.app.appsearch.AppSearchResult.RESULT_INTERNAL_ERROR;
import static android.app.appsearch.ParcelableUtil.WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL;
import static android.app.appsearch.ParcelableUtil.WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchBlobHandle;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.ParcelableUtil;
import android.app.appsearch.safeparcel.AbstractSafeParcelable;
import android.app.appsearch.safeparcel.SafeParcelable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import com.android.appsearch.flags.Flags;
import java.util.Map;
import java.util.Objects;
/**
* Parcelable wrapper around {@link AppSearchBatchResult}.
*
*
{@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} and provides parcelability of the whole structure.
*
*
Compare to deprecated {@link AppSearchBatchResultParcel}, this class could config how to write
* it to the parcel. Therefore binder objects and {@link ParcelFileDescriptor} is supported in this
* class. This class could also support general type as KeyType.
*
* @see ParcelableUtil#WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB
* @see ParcelableUtil#WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL
* @param The type of the keys for which the results will be reported. We are passing the
* class name of the KeyType to parcelable. Do not rename the class of KeyType, since that may
* cause compatibility issue for GmsCore.
* @param The type of result object for successful calls. Must be a parcelable type.
* @hide
*/
@SafeParcelable.Class(creator = "AppSearchBatchResultParcelV2Creator", creatorIsFinal = false)
public final class AppSearchBatchResultParcelV2 extends AbstractSafeParcelable {
private static final String TAG = "AppSearchBatchResultPar";
@NonNull
// Provide ClassLoader when read from bundle in getResult() method
@SuppressWarnings("rawtypes")
public static final Parcelable.Creator CREATOR =
new AppSearchBatchResultParcelV2Creator() {
@Override
public AppSearchBatchResultParcelV2 createFromParcel(Parcel in) {
int writeParcelModel = in.readInt();
switch (writeParcelModel) {
case WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB:
byte[] dataBlob = Objects.requireNonNull(ParcelableUtil.readBlob(in));
Parcel unmarshallParcel = Parcel.obtain();
try {
unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length);
unmarshallParcel.setDataPosition(0);
return directlyReadFromParcel(unmarshallParcel);
} finally {
unmarshallParcel.recycle();
}
case WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL:
return directlyReadFromParcel(in);
default:
throw new UnsupportedOperationException(
"Cannot write AppSearchBatchResultParcelV2 to Parcel with "
+ "unknown model: "
+ writeParcelModel);
}
}
};
/** The Creator used to directly write to parcel with calling {@link Parcel#writeBlob}. */
@NonNull
private static final Parcelable.Creator CREATOR_WITHOUT_BLOB =
new AppSearchBatchResultParcelV2Creator();
static AppSearchBatchResultParcelV2, ?> directlyReadFromParcel(@NonNull Parcel data) {
return CREATOR_WITHOUT_BLOB.createFromParcel(data);
}
static void directlyWriteToParcel(
@NonNull AppSearchBatchResultParcelV2, ?> result, @NonNull Parcel data, int flags) {
AppSearchBatchResultParcelV2Creator.writeToParcel(result, data, flags);
}
/**
* The flags indicate how we write this object to parcel and read it.
*
* @see ParcelableUtil#WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB
* @see ParcelableUtil#WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL
*/
@Field(id = 1)
@ParcelableUtil.WriteParcelMode
int mWriteParcelModel;
// No longer used, we shouldn't use class name to generate clazz file since proguard will
// mutate the class name.
@Field(id = 2)
@NonNull
final String mKeyClassName;
// Map stores keys of AppSearchBatchResult. The key will be an integer of a consecutive
// increasing sequence. Associated with mAppSearchResultValueBundle.
@Field(id = 3)
@NonNull
final Bundle mKeyBundle;
// Map stores keys of AppSearchBatchResult. The key will be an integer of a consecutive
// increasing sequence. Associated with mKeyBundle.
@Field(id = 4)
@NonNull
final Bundle mAppSearchResultValueBundle;
@Nullable private AppSearchBatchResult mResultCached;
@Constructor
AppSearchBatchResultParcelV2(
@Param(id = 1) @ParcelableUtil.WriteParcelMode int writeParcelModel,
@Param(id = 2) String keyClassName,
@Param(id = 3) Bundle keyBundle,
@Param(id = 4) Bundle appSearchResultValueBundle) {
mWriteParcelModel = writeParcelModel;
mKeyClassName = keyClassName;
mKeyBundle = keyBundle;
mAppSearchResultValueBundle = appSearchResultValueBundle;
// We need to set the bundle's class loader otherwise it may return null in getParcelable.
// Normally all AppSearch's classes should be under the same classLoader, using
// AppSearchResultParcelV2.class.getClassLoader() here.
ClassLoader classLoader = AppSearchResultParcelV2.class.getClassLoader();
mKeyBundle.setClassLoader(classLoader);
mAppSearchResultValueBundle.setClassLoader(classLoader);
}
/**
* Creates a new {@link AppSearchBatchResultParcel} from the given {@link AppSearchBatchResult}
* results which has {@link AppSearchBlobHandle} as keys and {@link ParcelFileDescriptor} as
* values.
*/
@SuppressWarnings("unchecked")
@NonNull
@FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
public static AppSearchBatchResultParcelV2
fromBlobHandleToPfd(
@NonNull
AppSearchBatchResult
result) {
Bundle keyAppSearchResultBundle = new Bundle();
Bundle valueAppSearchResultBundle = new Bundle();
int i = 0;
for (Map.Entry> entry :
result.getAll().entrySet()) {
AppSearchResultParcelV2 valueAppSearchBinderResultParcel;
// Create result from value in success case and errorMessage in failure case.
if (entry.getValue().isSuccess()) {
valueAppSearchBinderResultParcel =
AppSearchResultParcelV2.fromParcelFileDescriptor(
entry.getValue().getResultValue());
} else {
valueAppSearchBinderResultParcel =
AppSearchResultParcelV2.fromFailedResult(entry.getValue());
}
keyAppSearchResultBundle.putParcelable(String.valueOf(i), entry.getKey());
valueAppSearchResultBundle.putParcelable(
String.valueOf(i), valueAppSearchBinderResultParcel);
++i;
}
// We cannot marshall PFD!! We have to directly write this object to parcel.
return new AppSearchBatchResultParcelV2<>(
WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL,
AppSearchBlobHandle.class.getName(),
keyAppSearchResultBundle,
valueAppSearchResultBundle);
}
/**
* Creates a new {@link AppSearchBatchResultParcel} from the given {@link AppSearchBatchResult}
* results which has {@link AppSearchBlobHandle} as keys and {@code Void} as values.
*/
@NonNull
@FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
public static AppSearchBatchResultParcelV2 fromBlobHandleToVoid(
@NonNull AppSearchBatchResult result) {
Bundle keyAppSearchResultBundle = new Bundle();
Bundle valueAppSearchResultBundle = new Bundle();
int i = 0;
for (Map.Entry> entry :
result.getAll().entrySet()) {
AppSearchResultParcelV2 valueAppSearchResultParcel;
// Create result from value in success case and errorMessage in failure case.
if (entry.getValue().isSuccess()) {
valueAppSearchResultParcel = AppSearchResultParcelV2.fromVoid();
} else {
valueAppSearchResultParcel =
AppSearchResultParcelV2.fromFailedResult(entry.getValue());
}
keyAppSearchResultBundle.putParcelable(String.valueOf(i), entry.getKey());
valueAppSearchResultBundle.putParcelable(String.valueOf(i), valueAppSearchResultParcel);
++i;
}
return new AppSearchBatchResultParcelV2<>(
WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB,
AppSearchBlobHandle.class.getName(),
keyAppSearchResultBundle,
valueAppSearchResultBundle);
}
/** Gets the {@link AppSearchBatchResult} out of this {@link AppSearchBatchResultParcelV2}. */
@NonNull
@SuppressWarnings({"unchecked", "deprecation"})
public AppSearchBatchResult getResult() {
if (mResultCached == null) {
AppSearchBatchResult.Builder builder =
new AppSearchBatchResult.Builder<>();
for (String key : mKeyBundle.keySet()) {
KeyType keyType = (KeyType) mKeyBundle.getParcelable(key);
AppSearchResultParcelV2 valueTypeResult =
(AppSearchResultParcelV2)
mAppSearchResultValueBundle.getParcelable(key);
if (keyType == null) {
// keyType is null means the type of key doesn't match keyClazz, which
// is impossible.
throw new IllegalArgumentException(
"AppSearchResultParcelV2's key type doesn't match.");
} else if (valueTypeResult == null) {
// valueTypeResult is null means the type of value isn't
// AppSearchResultParcelV2, which is impossible.
builder.setResult(
keyType,
AppSearchResult.newFailedResult(
RESULT_INTERNAL_ERROR,
"Cannot read value parcelable from bundle."));
} else {
builder.setResult(keyType, valueTypeResult.getResult());
}
}
mResultCached = builder.build();
}
return mResultCached;
}
/** @hide */
@Override
@SuppressWarnings("unchecked")
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mWriteParcelModel);
switch (mWriteParcelModel) {
case WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB:
byte[] bytes;
// Create a parcel object to serialize results. So that we can use
// Parcel.writeBlob() to
// send data. WriteBlob() could take care of whether to pass data via binder
// directly or
// Android shared memory if the data is large.
Parcel data = Parcel.obtain();
try {
directlyWriteToParcel(this, data, flags);
bytes = data.marshall();
} finally {
data.recycle();
}
ParcelableUtil.writeBlob(dest, bytes);
break;
case WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL:
directlyWriteToParcel(this, dest, flags);
break;
default:
throw new UnsupportedOperationException(
"Cannot read AppSearchBatchResultParcelV2 from Parcel with "
+ "unknown model: "
+ mWriteParcelModel);
}
}
}