1 /*
2  * Copyright 2023 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 
17 package androidx.appsearch.safeparcel;
18 
19 
20 import android.os.Bundle;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 
24 import androidx.annotation.RestrictTo;
25 
26 import org.jspecify.annotations.NonNull;
27 import org.jspecify.annotations.Nullable;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * An implemented creator for {@link GenericDocumentParcel}.
34  *
35  * <p>In Jetpack, in order to serialize
36  * {@link GenericDocumentParcel} for {@link androidx.appsearch.app.GenericDocument},
37  * {@link PropertyParcel} needs to be a real {@link Parcelable}.
38  */
39 // @exportToFramework:skipFile()
40 @RestrictTo(RestrictTo.Scope.LIBRARY)
41 public class GenericDocumentParcelCreator implements
42         Parcelable.Creator<GenericDocumentParcel> {
43     private static final String PROPERTIES_FIELD = "properties";
44     private static final String SCHEMA_TYPE_FIELD = "schemaType";
45     private static final String ID_FIELD = "id";
46     private static final String SCORE_FIELD = "score";
47     private static final String TTL_MILLIS_FIELD = "ttlMillis";
48     private static final String CREATION_TIMESTAMP_MILLIS_FIELD = "creationTimestampMillis";
49     private static final String NAMESPACE_FIELD = "namespace";
50     private static final String PARENT_TYPES_FIELD = "parentTypes";
51 
52     /** Creates a {@link GenericDocumentParcel} from a {@link Bundle}. */
createGenericDocumentParcelFromBundle( @onNull Bundle genericDocumentParcelBundle)53     private static @NonNull GenericDocumentParcel createGenericDocumentParcelFromBundle(
54             @NonNull Bundle genericDocumentParcelBundle) {
55         // Get namespace, id, and schema type
56         String namespace = genericDocumentParcelBundle.getString(NAMESPACE_FIELD);
57         String id = genericDocumentParcelBundle.getString(ID_FIELD);
58         String schemaType = genericDocumentParcelBundle.getString(SCHEMA_TYPE_FIELD);
59 
60         // Those three can NOT be null.
61         if (namespace == null || id == null || schemaType == null) {
62             throw new IllegalArgumentException("GenericDocumentParcel bundle doesn't have "
63                     + "namespace, id, or schemaType.");
64         }
65 
66         GenericDocumentParcel.Builder builder = new GenericDocumentParcel.Builder(namespace,
67                 id, schemaType);
68         List<String> parentTypes =
69                 genericDocumentParcelBundle.getStringArrayList(PARENT_TYPES_FIELD);
70         if (parentTypes != null) {
71             builder.setParentTypes(parentTypes);
72         }
73         builder.setScore(genericDocumentParcelBundle.getInt(SCORE_FIELD));
74         builder.setCreationTimestampMillis(
75                 genericDocumentParcelBundle.getLong(CREATION_TIMESTAMP_MILLIS_FIELD));
76         builder.setTtlMillis(genericDocumentParcelBundle.getLong(TTL_MILLIS_FIELD));
77 
78         // properties
79         Bundle propertyBundle = genericDocumentParcelBundle.getBundle(PROPERTIES_FIELD);
80         if (propertyBundle != null) {
81             for (String propertyName : propertyBundle.keySet()) {
82                 // SuppressWarnings can be applied on a local variable, but not any
83                 // single line of code.
84                 @SuppressWarnings("deprecation")
85                 PropertyParcel propertyParcel = propertyBundle.getParcelable(propertyName);
86                 builder.putInPropertyMap(propertyName, propertyParcel);
87             }
88         }
89 
90         return builder.build();
91     }
92 
93     /** Creates a {@link Bundle} from a {@link GenericDocumentParcel}. */
createBundleFromGenericDocumentParcel( @onNull GenericDocumentParcel genericDocumentParcel)94     private static @NonNull Bundle createBundleFromGenericDocumentParcel(
95             @NonNull GenericDocumentParcel genericDocumentParcel) {
96         Bundle genericDocumentParcelBundle = new Bundle();
97 
98         // Common fields
99         genericDocumentParcelBundle.putString(NAMESPACE_FIELD,
100                 genericDocumentParcel.getNamespace());
101         genericDocumentParcelBundle.putString(ID_FIELD, genericDocumentParcel.getId());
102         genericDocumentParcelBundle.putString(SCHEMA_TYPE_FIELD,
103                 genericDocumentParcel.getSchemaType());
104         genericDocumentParcelBundle.putStringArrayList(PARENT_TYPES_FIELD,
105                 (ArrayList<String>) genericDocumentParcel.getParentTypes());
106         genericDocumentParcelBundle.putInt(SCORE_FIELD, genericDocumentParcel.getScore());
107         genericDocumentParcelBundle.putLong(CREATION_TIMESTAMP_MILLIS_FIELD,
108                 genericDocumentParcel.getCreationTimestampMillis());
109         genericDocumentParcelBundle.putLong(TTL_MILLIS_FIELD,
110                 genericDocumentParcel.getTtlMillis());
111 
112         // Properties
113         Bundle properties = new Bundle();
114         List<PropertyParcel> propertyParcels = genericDocumentParcel.getProperties();
115         for (int i = 0; i < propertyParcels.size(); ++i) {
116             PropertyParcel propertyParcel = propertyParcels.get(i);
117             properties.putParcelable(propertyParcel.getPropertyName(), propertyParcel);
118         }
119         genericDocumentParcelBundle.putBundle(PROPERTIES_FIELD, properties);
120 
121         return genericDocumentParcelBundle;
122     }
123 
124     @Override
createFromParcel(Parcel in)125     public @Nullable GenericDocumentParcel createFromParcel(Parcel in) {
126         Bundle bundle = in.readBundle(getClass().getClassLoader());
127         return createGenericDocumentParcelFromBundle(bundle);
128     }
129 
130     @Override
newArray(int size)131     public GenericDocumentParcel[] newArray(int size) {
132         return new GenericDocumentParcel[size];
133     }
134 
135     /** Writes a {@link GenericDocumentParcel} to a {@link Parcel}. */
writeToParcel(@onNull GenericDocumentParcel genericDocumentParcel, android.os.@NonNull Parcel parcel, int flags)136     public static void writeToParcel(@NonNull GenericDocumentParcel genericDocumentParcel,
137             android.os.@NonNull Parcel parcel, int flags) {
138         parcel.writeBundle(createBundleFromGenericDocumentParcel(genericDocumentParcel));
139     }
140 }
141