1 /*
2  * Copyright (C) 2018 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 package android.service.autofill;
17 
18 import static android.view.autofill.Helper.sDebug;
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.TestApi;
23 import android.os.Bundle;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.ArrayMap;
27 
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Objects;
31 
32 /**
33  * Holds both a generic and package-specific userData used for
34  * <a href="AutofillService.html#FieldClassification">field classification</a>.
35  *
36  * @hide
37  */
38 @TestApi
39 public final class CompositeUserData implements FieldClassificationUserData, Parcelable {
40 
41     private final UserData mGenericUserData;
42     private final UserData mPackageUserData;
43 
44     private final String[] mCategories;
45     private final String[] mValues;
46 
CompositeUserData(@ullable UserData genericUserData, @NonNull UserData packageUserData)47     public CompositeUserData(@Nullable UserData genericUserData,
48             @NonNull UserData packageUserData) {
49         mGenericUserData = genericUserData;
50         mPackageUserData = packageUserData;
51 
52         final String[] packageCategoryIds = mPackageUserData.getCategoryIds();
53         final String[] packageValues = mPackageUserData.getValues();
54 
55         final ArrayList<String> categoryIds = new ArrayList<>(packageCategoryIds.length);
56         final ArrayList<String> values = new ArrayList<>(packageValues.length);
57 
58         Collections.addAll(categoryIds, packageCategoryIds);
59         Collections.addAll(values, packageValues);
60 
61         if (mGenericUserData != null) {
62             final String[] genericCategoryIds = mGenericUserData.getCategoryIds();
63             final String[] genericValues = mGenericUserData.getValues();
64             final int size = mGenericUserData.getCategoryIds().length;
65             for (int i = 0; i < size; i++) {
66                 if (!categoryIds.contains(genericCategoryIds[i])) {
67                     categoryIds.add(genericCategoryIds[i]);
68                     values.add(genericValues[i]);
69                 }
70             }
71         }
72 
73         mCategories = new String[categoryIds.size()];
74         categoryIds.toArray(mCategories);
75         mValues = new String[values.size()];
76         values.toArray(mValues);
77     }
78 
79     @Nullable
80     @Override
getFieldClassificationAlgorithm()81     public String getFieldClassificationAlgorithm() {
82         final String packageDefaultAlgo = mPackageUserData.getFieldClassificationAlgorithm();
83         if (packageDefaultAlgo != null) {
84             return packageDefaultAlgo;
85         } else {
86             return mGenericUserData == null ? null :
87                     mGenericUserData.getFieldClassificationAlgorithm();
88         }
89     }
90 
91     @Override
getDefaultFieldClassificationArgs()92     public Bundle getDefaultFieldClassificationArgs() {
93         final Bundle packageDefaultArgs = mPackageUserData.getDefaultFieldClassificationArgs();
94         if (packageDefaultArgs != null) {
95             return packageDefaultArgs;
96         } else {
97             return mGenericUserData == null ? null :
98                     mGenericUserData.getDefaultFieldClassificationArgs();
99         }
100     }
101 
102     @Nullable
103     @Override
getFieldClassificationAlgorithmForCategory(@onNull String categoryId)104     public String getFieldClassificationAlgorithmForCategory(@NonNull String categoryId) {
105         Objects.requireNonNull(categoryId);
106         final ArrayMap<String, String> categoryAlgorithms = getFieldClassificationAlgorithms();
107         if (categoryAlgorithms == null || !categoryAlgorithms.containsKey(categoryId)) {
108             return null;
109         }
110         return categoryAlgorithms.get(categoryId);
111     }
112 
113     @Override
getFieldClassificationAlgorithms()114     public ArrayMap<String, String> getFieldClassificationAlgorithms() {
115         final ArrayMap<String, String> packageAlgos = mPackageUserData
116                 .getFieldClassificationAlgorithms();
117         final ArrayMap<String, String> genericAlgos = mGenericUserData == null ? null :
118                 mGenericUserData.getFieldClassificationAlgorithms();
119 
120         ArrayMap<String, String> categoryAlgorithms = null;
121         if (packageAlgos != null || genericAlgos != null) {
122             categoryAlgorithms = new ArrayMap<>();
123             if (genericAlgos != null) {
124                 categoryAlgorithms.putAll(genericAlgos);
125             }
126             if (packageAlgos != null) {
127                 categoryAlgorithms.putAll(packageAlgos);
128             }
129         }
130 
131         return categoryAlgorithms;
132     }
133 
134     @Override
getFieldClassificationArgs()135     public ArrayMap<String, Bundle> getFieldClassificationArgs() {
136         final ArrayMap<String, Bundle> packageArgs = mPackageUserData.getFieldClassificationArgs();
137         final ArrayMap<String, Bundle> genericArgs = mGenericUserData == null ? null :
138                 mGenericUserData.getFieldClassificationArgs();
139 
140         ArrayMap<String, Bundle> categoryArgs = null;
141         if (packageArgs != null || genericArgs != null) {
142             categoryArgs = new ArrayMap<>();
143             if (genericArgs != null) {
144                 categoryArgs.putAll(genericArgs);
145             }
146             if (packageArgs != null) {
147                 categoryArgs.putAll(packageArgs);
148             }
149         }
150 
151         return categoryArgs;
152     }
153 
154     @Override
getCategoryIds()155     public String[] getCategoryIds() {
156         return mCategories;
157     }
158 
159     @Override
getValues()160     public String[] getValues() {
161         return mValues;
162     }
163 
164     /////////////////////////////////////
165     // Object "contract" methods. //
166     /////////////////////////////////////
167     @Override
toString()168     public String toString() {
169         if (!sDebug) return super.toString();
170 
171         // OK to print UserData because UserData.toString() is PII-aware
172         final StringBuilder builder = new StringBuilder("genericUserData=")
173                 .append(mGenericUserData)
174                 .append(", packageUserData=").append(mPackageUserData);
175         return builder.toString();
176     }
177 
178     /////////////////////////////////////
179     // Parcelable "contract" methods. //
180     /////////////////////////////////////
181 
182     @Override
describeContents()183     public int describeContents() {
184         return 0;
185     }
186 
187     @Override
writeToParcel(Parcel parcel, int flags)188     public void writeToParcel(Parcel parcel, int flags) {
189         parcel.writeParcelable(mGenericUserData, 0);
190         parcel.writeParcelable(mPackageUserData, 0);
191     }
192 
193     public static final @android.annotation.NonNull Parcelable.Creator<CompositeUserData> CREATOR =
194             new Parcelable.Creator<CompositeUserData>() {
195                 @Override
196                 public CompositeUserData createFromParcel(Parcel parcel) {
197                     // Always go through the builder to ensure the data ingested by
198                     // the system obeys the contract of the builder to avoid attacks
199                     // using specially crafted parcels.
200                     final UserData genericUserData = parcel.readParcelable(null, android.service.autofill.UserData.class);
201                     final UserData packageUserData = parcel.readParcelable(null, android.service.autofill.UserData.class);
202                     return new CompositeUserData(genericUserData, packageUserData);
203                 }
204 
205                 @Override
206                 public CompositeUserData[] newArray(int size) {
207                     return new CompositeUserData[size];
208                 }
209             };
210 }
211