1 /*
2  * Copyright (C) 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 android.adservices.common;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.NonNull;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 
24 import com.android.adservices.AdServicesParcelableUtil;
25 import com.android.adservices.flags.Flags;
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import org.json.JSONArray;
29 import org.json.JSONException;
30 import org.json.JSONObject;
31 
32 import java.nio.charset.StandardCharsets;
33 import java.util.HashSet;
34 import java.util.Objects;
35 import java.util.Set;
36 
37 // TODO(b/266837113) link to setAppInstallAdvertisers once unhidden.
38 
39 /**
40  * A container for the ad filters that are based on app install state.
41  *
42  * <p>App install filters filter out ads based on the presence of packages installed on the device.
43  * In order for filtering to work, a package must call the setAppInstallAdvertisers API with the
44  * identifier of the adtech who owns this ad. If that call has been made, and the ad contains an
45  * {@link AppInstallFilters} object whose package name set contains the name of the package, the ad
46  * will be removed from the auction.
47  *
48  * <p>Note that the filtering is based on any package with one of the listed package names being on
49  * the device. It is possible that the package holding the package name is not the application
50  * targeted by the ad.
51  */
52 @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
53 public final class AppInstallFilters implements Parcelable {
54     /** @hide */
55     @VisibleForTesting public static final String PACKAGE_NAMES_FIELD_NAME = "package_names";
56 
57     @NonNull private final Set<String> mPackageNames;
58 
59     @NonNull
60     public static final Creator<AppInstallFilters> CREATOR =
61             new Creator<AppInstallFilters>() {
62                 @NonNull
63                 @Override
64                 public AppInstallFilters createFromParcel(@NonNull Parcel in) {
65                     Objects.requireNonNull(in);
66                     return new AppInstallFilters(in);
67                 }
68 
69                 @NonNull
70                 @Override
71                 public AppInstallFilters[] newArray(int size) {
72                     return new AppInstallFilters[size];
73                 }
74             };
75 
AppInstallFilters(@onNull Builder builder)76     private AppInstallFilters(@NonNull Builder builder) {
77         Objects.requireNonNull(builder);
78 
79         mPackageNames = builder.mPackageNames;
80     }
81 
AppInstallFilters(@onNull Parcel in)82     private AppInstallFilters(@NonNull Parcel in) {
83         Objects.requireNonNull(in);
84 
85         mPackageNames = AdServicesParcelableUtil.readStringSetFromParcel(in);
86     }
87 
88     /**
89      * Gets the list of package names this ad is filtered on.
90      *
91      * <p>The ad containing this filter will be removed from the ad auction if any of the package
92      * names are present on the device and have called setAppInstallAdvertisers.
93      */
94     @NonNull
getPackageNames()95     public Set<String> getPackageNames() {
96         return mPackageNames;
97     }
98 
99     /**
100      * @return The estimated size of this object, in bytes using UTF_8 encoding.
101      * @hide
102      */
getSizeInBytes()103     public int getSizeInBytes() {
104         int totalSize = 0;
105         for (String packageName : mPackageNames) {
106             totalSize += packageName.getBytes(StandardCharsets.UTF_8).length;
107         }
108         return totalSize;
109     }
110 
111     /**
112      * A JSON serializer.
113      *
114      * @return A JSON serialization of this object.
115      * @hide
116      */
toJson()117     public JSONObject toJson() throws JSONException {
118         JSONObject toReturn = new JSONObject();
119         JSONArray packageNames = new JSONArray();
120         for (String packageName : mPackageNames) {
121             packageNames.put(packageName);
122         }
123         toReturn.put(PACKAGE_NAMES_FIELD_NAME, packageNames);
124         return toReturn;
125     }
126 
127     /**
128      * A JSON de-serializer.
129      *
130      * @param json A JSON representation of an {@link AppInstallFilters} object as would be
131      *     generated by {@link #toJson()}.
132      * @return An {@link AppInstallFilters} object generated from the given JSON.
133      * @hide
134      */
fromJson(JSONObject json)135     public static AppInstallFilters fromJson(JSONObject json) throws JSONException {
136         JSONArray serializedPackageNames = json.getJSONArray(PACKAGE_NAMES_FIELD_NAME);
137         Set<String> packageNames = new HashSet<>();
138         for (int i = 0; i < serializedPackageNames.length(); i++) {
139             Object packageName = serializedPackageNames.get(i);
140             if (packageName instanceof String) {
141                 packageNames.add((String) packageName);
142             } else {
143                 throw new JSONException(
144                         "Found non-string package name when de-serializing AppInstallFilters");
145             }
146         }
147         return new Builder().setPackageNames(packageNames).build();
148     }
149 
150     @Override
writeToParcel(@onNull Parcel dest, int flags)151     public void writeToParcel(@NonNull Parcel dest, int flags) {
152         Objects.requireNonNull(dest);
153         AdServicesParcelableUtil.writeStringSetToParcel(dest, mPackageNames);
154     }
155 
156     /** @hide */
157     @Override
describeContents()158     public int describeContents() {
159         return 0;
160     }
161 
162     /** Checks whether the {@link AppInstallFilters} objects contain the same information. */
163     @Override
equals(Object o)164     public boolean equals(Object o) {
165         if (this == o) return true;
166         if (!(o instanceof AppInstallFilters)) return false;
167         AppInstallFilters that = (AppInstallFilters) o;
168         return mPackageNames.equals(that.mPackageNames);
169     }
170 
171     /** Returns the hash of the {@link AppInstallFilters} object's data. */
172     @Override
hashCode()173     public int hashCode() {
174         return Objects.hash(mPackageNames);
175     }
176 
177     @Override
toString()178     public String toString() {
179         return "AppInstallFilters{" + "mPackageNames=" + mPackageNames + '}';
180     }
181 
182     /** Builder for creating {@link AppInstallFilters} objects. */
183     public static final class Builder {
184         @NonNull private Set<String> mPackageNames = new HashSet<>();
185 
Builder()186         public Builder() {}
187 
188         /**
189          * Gets the list of package names this ad is filtered on.
190          *
191          * <p>See {@link #getPackageNames()} for more information.
192          */
193         @NonNull
setPackageNames(@onNull Set<String> packageNames)194         public Builder setPackageNames(@NonNull Set<String> packageNames) {
195             Objects.requireNonNull(packageNames);
196             mPackageNames = packageNames;
197             return this;
198         }
199 
200         /** Builds and returns a {@link AppInstallFilters} instance. */
201         @NonNull
build()202         public AppInstallFilters build() {
203             return new AppInstallFilters(this);
204         }
205     }
206 }
207