1 /*
2  * Copyright (C) 2017 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.service.autofill;
18 
19 import static android.view.autofill.Helper.sDebug;
20 
21 import android.annotation.NonNull;
22 import android.os.Parcel;
23 import android.view.autofill.Helper;
24 
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.List;
29 import java.util.Objects;
30 
31 /**
32  * Represents the <a href="AutofillService.html#FieldClassification">field classification</a>
33  * results for a given field.
34  */
35 public final class FieldClassification {
36 
37     private final ArrayList<Match> mMatches;
38 
39     /** @hide */
FieldClassification(@onNull ArrayList<Match> matches)40     public FieldClassification(@NonNull ArrayList<Match> matches) {
41         mMatches = Objects.requireNonNull(matches);
42         Collections.sort(mMatches, new Comparator<Match>() {
43             @Override
44             public int compare(Match o1, Match o2) {
45                 if (o1.mScore > o2.mScore) return -1;
46                 if (o1.mScore < o2.mScore) return 1;
47                 return 0;
48             }}
49         );
50     }
51 
52     /**
53      * Gets the {@link Match matches} with the highest {@link Match#getScore() scores} (sorted in
54      * descending order).
55      *
56      * <p><b>Note:</b> There's no guarantee of how many matches will be returned. In fact,
57      * the Android System might return just the top match to minimize the impact of field
58      * classification in the device's health.
59      */
60     @NonNull
getMatches()61     public List<Match> getMatches() {
62         return mMatches;
63     }
64 
65     @Override
toString()66     public String toString() {
67         if (!sDebug) return super.toString();
68 
69         return "FieldClassification: " + mMatches;
70     }
71 
writeToParcel(Parcel parcel)72     private void writeToParcel(Parcel parcel) {
73         parcel.writeInt(mMatches.size());
74         for (int i = 0; i < mMatches.size(); i++) {
75             mMatches.get(i).writeToParcel(parcel);
76         }
77     }
78 
readFromParcel(Parcel parcel)79     private static FieldClassification readFromParcel(Parcel parcel) {
80         final int size = parcel.readInt();
81         final ArrayList<Match> matches = new ArrayList<>();
82         for (int i = 0; i < size; i++) {
83             matches.add(i, Match.readFromParcel(parcel));
84         }
85 
86         return new FieldClassification(matches);
87     }
88 
readArrayFromParcel(Parcel parcel)89     static FieldClassification[] readArrayFromParcel(Parcel parcel) {
90         final int length = parcel.readInt();
91         final FieldClassification[] fcs = new FieldClassification[length];
92         for (int i = 0; i < length; i++) {
93             fcs[i] = readFromParcel(parcel);
94         }
95         return fcs;
96     }
97 
writeArrayToParcel(@onNull Parcel parcel, @NonNull FieldClassification[] fcs)98     static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull FieldClassification[] fcs) {
99         parcel.writeInt(fcs.length);
100         for (int i = 0; i < fcs.length; i++) {
101             fcs[i].writeToParcel(parcel);
102         }
103     }
104 
105     /**
106      * Represents the score of a {@link UserData} entry for the field.
107      */
108     public static final class Match {
109 
110         private final String mCategoryId;
111         private final float mScore;
112 
113         /** @hide */
Match(String categoryId, float score)114         public Match(String categoryId, float score) {
115             mCategoryId = Objects.requireNonNull(categoryId);
116             mScore = score;
117         }
118 
119         /**
120          * Gets the category id of the {@link UserData} entry.
121          */
122         @NonNull
getCategoryId()123         public String getCategoryId() {
124             return mCategoryId;
125         }
126 
127         /**
128          * Gets a classification score for the value of this field compared to the value of the
129          * {@link UserData} entry.
130          *
131          * <p>The score is based in a comparison of the field value and the user data entry, and it
132          * ranges from {@code 0.0F} to {@code 1.0F}:
133          * <ul>
134          *   <li>{@code 1.0F} represents a full match ({@code 100%}).
135          *   <li>{@code 0.0F} represents a full mismatch ({@code 0%}).
136          *   <li>Any other value is a partial match.
137          * </ul>
138          *
139          * <p>How the score is calculated depends on the
140          * {@link UserData.Builder#setFieldClassificationAlgorithm(String, android.os.Bundle)
141          * algorithm} used.
142          */
getScore()143         public float getScore() {
144             return mScore;
145         }
146 
147         @Override
toString()148         public String toString() {
149             if (!sDebug) return super.toString();
150 
151             final StringBuilder string = new StringBuilder("Match: categoryId=");
152             Helper.appendRedacted(string, mCategoryId);
153             return string.append(", score=").append(mScore).toString();
154         }
155 
writeToParcel(@onNull Parcel parcel)156         private void writeToParcel(@NonNull Parcel parcel) {
157             parcel.writeString(mCategoryId);
158             parcel.writeFloat(mScore);
159         }
160 
readFromParcel(@onNull Parcel parcel)161         private static Match readFromParcel(@NonNull Parcel parcel) {
162             return new Match(parcel.readString(), parcel.readFloat());
163         }
164     }
165 }
166