1 /*
2  * Copyright (C) 2022 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 android.adservices.topics;
18 
19 import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
20 
21 import android.adservices.common.AdServicesResponse;
22 import android.adservices.common.AdServicesStatusUtils;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Objects;
33 
34 /**
35  * Represent the result from the getTopics API.
36  *
37  * @hide
38  */
39 public final class GetTopicsResult extends AdServicesResponse {
40     private final List<Long> mTaxonomyVersions;
41     private final List<Long> mModelVersions;
42     private final List<Integer> mTopics;
43     private final List<byte[]> mEncryptedTopics;
44     private final List<String> mEncryptionKeys;
45     private final List<byte[]> mEncapsulatedKeys;
46 
GetTopicsResult( @dServicesStatusUtils.StatusCode int resultCode, String errorMessage, List<Long> taxonomyVersions, List<Long> modelVersions, List<Integer> topics, List<byte[]> encryptedTopics, List<String> encryptionKeys, List<byte[]> encapsulatedKeys)47     private GetTopicsResult(
48             @AdServicesStatusUtils.StatusCode int resultCode,
49             String errorMessage,
50             List<Long> taxonomyVersions,
51             List<Long> modelVersions,
52             List<Integer> topics,
53             List<byte[]> encryptedTopics,
54             List<String> encryptionKeys,
55             List<byte[]> encapsulatedKeys) {
56         super(resultCode, errorMessage);
57         mTaxonomyVersions = taxonomyVersions;
58         mModelVersions = modelVersions;
59         mTopics = topics;
60         mEncryptedTopics = encryptedTopics;
61         mEncryptionKeys = encryptionKeys;
62         mEncapsulatedKeys = encapsulatedKeys;
63     }
64 
GetTopicsResult(@onNull Parcel in)65     private GetTopicsResult(@NonNull Parcel in) {
66         super(in.readInt(), in.readString());
67 
68         mTaxonomyVersions = Collections.unmodifiableList(readLongList(in));
69         mModelVersions = Collections.unmodifiableList(readLongList(in));
70         mTopics = Collections.unmodifiableList(readIntegerList(in));
71         mEncryptedTopics = Collections.unmodifiableList(readByteArrayList(in));
72         mEncryptionKeys = Collections.unmodifiableList(readStringList(in));
73         mEncapsulatedKeys = Collections.unmodifiableList(readByteArrayList(in));
74     }
75 
76     public static final @NonNull Creator<GetTopicsResult> CREATOR =
77             new Parcelable.Creator<GetTopicsResult>() {
78                 @Override
79                 public GetTopicsResult createFromParcel(Parcel in) {
80                     return new GetTopicsResult(in);
81                 }
82 
83                 @Override
84                 public GetTopicsResult[] newArray(int size) {
85                     return new GetTopicsResult[size];
86                 }
87             };
88 
89     /** @hide */
90     @Override
describeContents()91     public int describeContents() {
92         return 0;
93     }
94 
95     /** @hide */
96     @Override
writeToParcel(@onNull Parcel out, int flags)97     public void writeToParcel(@NonNull Parcel out, int flags) {
98         out.writeInt(mStatusCode);
99         out.writeString(mErrorMessage);
100         writeLongList(out, mTaxonomyVersions);
101         writeLongList(out, mModelVersions);
102         writeIntegerList(out, mTopics);
103         writeByteArrayList(out, mEncryptedTopics);
104         writeStringList(out, mEncryptionKeys);
105         writeByteArrayList(out, mEncapsulatedKeys);
106     }
107 
108     /**
109      * Returns {@code true} if {@link #getResultCode} equals {@link
110      * AdServicesStatusUtils#STATUS_SUCCESS}.
111      */
isSuccess()112     public boolean isSuccess() {
113         return getResultCode() == STATUS_SUCCESS;
114     }
115 
116     /** Returns one of the {@code RESULT} constants defined in {@link GetTopicsResult}. */
getResultCode()117     public @AdServicesStatusUtils.StatusCode int getResultCode() {
118         return mStatusCode;
119     }
120 
121     /**
122      * Returns the error message associated with this result.
123      *
124      * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
125      * message may be {@code null} even if {@link #isSuccess} is {@code false}.
126      */
127     @Nullable
getErrorMessage()128     public String getErrorMessage() {
129         return mErrorMessage;
130     }
131 
132     /** Get the Taxonomy Versions. */
getTaxonomyVersions()133     public List<Long> getTaxonomyVersions() {
134         return mTaxonomyVersions;
135     }
136 
137     /** Get the Model Versions. */
getModelVersions()138     public List<Long> getModelVersions() {
139         return mModelVersions;
140     }
141 
142     @NonNull
getTopics()143     public List<Integer> getTopics() {
144         return mTopics;
145     }
146 
147     @NonNull
getEncryptedTopics()148     public List<byte[]> getEncryptedTopics() {
149         return mEncryptedTopics;
150     }
151 
152     @NonNull
getEncryptionKeys()153     public List<String> getEncryptionKeys() {
154         return mEncryptionKeys;
155     }
156 
157     @NonNull
getEncapsulatedKeys()158     public List<byte[]> getEncapsulatedKeys() {
159         return mEncapsulatedKeys;
160     }
161 
162     @Override
toString()163     public String toString() {
164         return "GetTopicsResult{"
165                 + "mResultCode="
166                 + mStatusCode
167                 + ", mErrorMessage='"
168                 + mErrorMessage
169                 + '\''
170                 + ", mTaxonomyVersions="
171                 + mTaxonomyVersions
172                 + ", mModelVersions="
173                 + mModelVersions
174                 + ", mTopics="
175                 + mTopics
176                 + ", mEncryptedTopics="
177                 + prettyPrint(mEncryptedTopics)
178                 + ", mEncryptionKeys="
179                 + mEncryptionKeys
180                 + ", mEncapsulatedKeys="
181                 + prettyPrint(mEncapsulatedKeys)
182                 + '}';
183     }
184 
prettyPrint(List<byte[]> listOfByteArrays)185     private String prettyPrint(List<byte[]> listOfByteArrays) {
186         StringBuilder stringBuilder = new StringBuilder();
187         stringBuilder.append("[");
188         for (int index = 0; index < listOfByteArrays.size(); index++) {
189             stringBuilder.append(Arrays.toString(listOfByteArrays.get(index)));
190             if (index != listOfByteArrays.size() - 1) {
191                 stringBuilder.append(", ");
192             }
193         }
194         stringBuilder.append("]");
195         return stringBuilder.toString();
196     }
197 
198     @Override
equals(Object o)199     public boolean equals(Object o) {
200         if (this == o) {
201             return true;
202         }
203 
204         if (!(o instanceof GetTopicsResult)) {
205             return false;
206         }
207 
208         GetTopicsResult that = (GetTopicsResult) o;
209 
210         return mStatusCode == that.mStatusCode
211                 && Objects.equals(mErrorMessage, that.mErrorMessage)
212                 && mTaxonomyVersions.equals(that.mTaxonomyVersions)
213                 && mModelVersions.equals(that.mModelVersions)
214                 && mTopics.equals(that.mTopics)
215                 && equals(mEncryptedTopics, that.mEncryptedTopics)
216                 && mEncryptionKeys.equals(that.mEncryptionKeys);
217     }
218 
equals(List<byte[]> list1, List<byte[]> list2)219     private static boolean equals(List<byte[]> list1, List<byte[]> list2) {
220         if (list1 == null || list2 == null) {
221             return false;
222         }
223         if (list1.size() != list2.size()) {
224             return false;
225         }
226         for (int i = 0; i < list1.size(); i++) {
227             if (!Arrays.equals(list1.get(i), list2.get(i))) {
228                 return false;
229             }
230         }
231         return true;
232     }
233 
234     @Override
hashCode()235     public int hashCode() {
236         return Objects.hash(
237                 mStatusCode,
238                 mErrorMessage,
239                 mTaxonomyVersions,
240                 mModelVersions,
241                 mTopics,
242                 hashCode(mEncryptedTopics),
243                 mEncryptionKeys,
244                 hashCode(mEncryptedTopics));
245     }
246 
hashCode(List<byte[]> list)247     private static int hashCode(List<byte[]> list) {
248         int hash = 0;
249         for (byte[] bytes : list) {
250             hash += Arrays.hashCode(bytes);
251         }
252         return hash;
253     }
254 
255     // Read the list of long from parcel.
readLongList(@onNull Parcel in)256     private static List<Long> readLongList(@NonNull Parcel in) {
257         List<Long> list = new ArrayList<>();
258 
259         int toReadCount = in.readInt();
260         // Negative toReadCount is handled implicitly
261         for (int i = 0; i < toReadCount; i++) {
262             list.add(in.readLong());
263         }
264 
265         return list;
266     }
267 
268     // Read the list of integer from parcel.
readIntegerList(@onNull Parcel in)269     private static List<Integer> readIntegerList(@NonNull Parcel in) {
270         List<Integer> list = new ArrayList<>();
271 
272         int toReadCount = in.readInt();
273         // Negative toReadCount is handled implicitly
274         for (int i = 0; i < toReadCount; i++) {
275             list.add(in.readInt());
276         }
277 
278         return list;
279     }
280 
281     // Read the list of integer from parcel.
readStringList(@onNull Parcel in)282     private static List<String> readStringList(@NonNull Parcel in) {
283         List<String> list = new ArrayList<>();
284 
285         int toReadCount = in.readInt();
286         // Negative toReadCount is handled implicitly
287         for (int i = 0; i < toReadCount; i++) {
288             list.add(in.readString());
289         }
290 
291         return list;
292     }
293 
294     // Read the list of byte arrays from parcel.
readByteArrayList(@onNull Parcel in)295     private static List<byte[]> readByteArrayList(@NonNull Parcel in) {
296         List<byte[]> list = new ArrayList<>();
297 
298         int toReadCount = in.readInt();
299         // Negative toReadCount is handled implicitly
300         for (int i = 0; i < toReadCount; i++) {
301             list.add(in.createByteArray());
302         }
303 
304         return list;
305     }
306 
307     // Write a List of Long to parcel.
writeLongList(@onNull Parcel out, @Nullable List<Long> val)308     private static void writeLongList(@NonNull Parcel out, @Nullable List<Long> val) {
309         if (val == null) {
310             out.writeInt(-1);
311             return;
312         }
313         out.writeInt(val.size());
314         for (Long l : val) {
315             out.writeLong(l);
316         }
317     }
318 
319     // Write a List of Integer to parcel.
writeIntegerList(@onNull Parcel out, @Nullable List<Integer> val)320     private static void writeIntegerList(@NonNull Parcel out, @Nullable List<Integer> val) {
321         if (val == null) {
322             out.writeInt(-1);
323             return;
324         }
325         out.writeInt(val.size());
326         for (Integer integer : val) {
327             out.writeInt(integer);
328         }
329     }
330 
331     // Write a List of String to parcel.
writeStringList(@onNull Parcel out, @Nullable List<String> val)332     private static void writeStringList(@NonNull Parcel out, @Nullable List<String> val) {
333         if (val == null) {
334             out.writeInt(-1);
335             return;
336         }
337         out.writeInt(val.size());
338         for (String string : val) {
339             out.writeString(string);
340         }
341     }
342 
343     // Write a List of byte array to parcel.
writeByteArrayList(@onNull Parcel out, @Nullable List<byte[]> val)344     private static void writeByteArrayList(@NonNull Parcel out, @Nullable List<byte[]> val) {
345         if (val == null) {
346             out.writeInt(-1);
347             return;
348         }
349         out.writeInt(val.size());
350         for (byte[] bytes : val) {
351             out.writeByteArray(bytes);
352         }
353     }
354 
355     /**
356      * Builder for {@link GetTopicsResult} objects.
357      *
358      * @hide
359      */
360     public static final class Builder {
361         private @AdServicesStatusUtils.StatusCode int mResultCode;
362         private String mErrorMessage;
363         private List<Long> mTaxonomyVersions = new ArrayList<>();
364         private List<Long> mModelVersions = new ArrayList<>();
365         private List<Integer> mTopics = new ArrayList<>();
366         private List<byte[]> mEncryptedTopics = new ArrayList<>();
367         private List<String> mEncryptionKeys = new ArrayList<>();
368         private List<byte[]> mEncapsulatedKeys = new ArrayList<>();
369 
Builder()370         public Builder() {}
371 
372         /** Set the Result Code. */
373         @NonNull
setResultCode(@dServicesStatusUtils.StatusCode int resultCode)374         public Builder setResultCode(@AdServicesStatusUtils.StatusCode int resultCode) {
375             mResultCode = resultCode;
376             return this;
377         }
378 
379         /** Set the Error Message. */
380         @NonNull
setErrorMessage(@ullable String errorMessage)381         public Builder setErrorMessage(@Nullable String errorMessage) {
382             mErrorMessage = errorMessage;
383             return this;
384         }
385 
386         /** Set the Taxonomy Version. */
387         @NonNull
setTaxonomyVersions(@onNull List<Long> taxonomyVersions)388         public Builder setTaxonomyVersions(@NonNull List<Long> taxonomyVersions) {
389             mTaxonomyVersions = taxonomyVersions;
390             return this;
391         }
392 
393         /** Set the Model Version. */
394         @NonNull
setModelVersions(@onNull List<Long> modelVersions)395         public Builder setModelVersions(@NonNull List<Long> modelVersions) {
396             mModelVersions = modelVersions;
397             return this;
398         }
399 
400         /** Set the list of the returned Topics */
401         @NonNull
setTopics(@onNull List<Integer> topics)402         public Builder setTopics(@NonNull List<Integer> topics) {
403             mTopics = topics;
404             return this;
405         }
406 
407         /** Set the list of the returned encrypted topics */
408         @NonNull
setEncryptedTopics(@onNull List<byte[]> encryptedTopics)409         public Builder setEncryptedTopics(@NonNull List<byte[]> encryptedTopics) {
410             mEncryptedTopics = encryptedTopics;
411             return this;
412         }
413 
414         /** Set the list of the encryption keys */
415         @NonNull
setEncryptionKeys(@onNull List<String> encryptionKeys)416         public Builder setEncryptionKeys(@NonNull List<String> encryptionKeys) {
417             mEncryptionKeys = encryptionKeys;
418             return this;
419         }
420 
421         /** Set the list of encapsulated keys generated via encryption */
422         @NonNull
setEncapsulatedKeys(@onNull List<byte[]> encapsulatedKeys)423         public Builder setEncapsulatedKeys(@NonNull List<byte[]> encapsulatedKeys) {
424             mEncapsulatedKeys = encapsulatedKeys;
425             return this;
426         }
427 
428         /**
429          * Builds a {@link GetTopicsResult} instance.
430          *
431          * <p>throws IllegalArgumentException if any of the params are null or there is any mismatch
432          * in the size of lists.
433          */
434         @NonNull
build()435         public GetTopicsResult build() {
436             if (mTopics == null
437                     || mTaxonomyVersions == null
438                     || mModelVersions == null
439                     || mEncryptedTopics == null
440                     || mEncryptionKeys == null) {
441                 throw new IllegalArgumentException(
442                         "One of the mandatory params of GetTopicsResult is null");
443             }
444 
445             if (mTopics.size() != mTaxonomyVersions.size()
446                     || mTopics.size() != mModelVersions.size()) {
447                 throw new IllegalArgumentException("Size mismatch in Topics");
448             }
449 
450             if (mEncryptedTopics.size() != mEncryptionKeys.size()
451                     || mEncryptedTopics.size() != mEncapsulatedKeys.size()) {
452                 throw new IllegalArgumentException("Size mismatch in EncryptedTopic lists");
453             }
454 
455             return new GetTopicsResult(
456                     mResultCode,
457                     mErrorMessage,
458                     mTaxonomyVersions,
459                     mModelVersions,
460                     mTopics,
461                     mEncryptedTopics,
462                     mEncryptionKeys,
463                     mEncapsulatedKeys);
464         }
465     }
466 }
467