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.credentials;
18 
19 import android.annotation.NonNull;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.service.credentials.CredentialEntry;
23 
24 import com.android.internal.util.AnnotationValidations;
25 import com.android.internal.util.Preconditions;
26 
27 import java.util.ArrayList;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Objects;
31 import java.util.Set;
32 
33 /**
34  * Represents the type and contained data fields of a {@link Credential}.
35  */
36 public final class CredentialDescription implements Parcelable {
37 
38     private static final int MAX_ALLOWED_ENTRIES_PER_DESCRIPTION = 16;
39 
40     /**
41      * The credential type.
42      */
43     @NonNull
44     private final String mType;
45 
46     /**
47      * Keys of elements to match with Credential requests.
48      */
49     @NonNull
50     private final Set<String> mSupportedElementKeys;
51 
52     /**
53      * The credential entries to be used in the UI.
54      */
55     @NonNull
56     private final List<CredentialEntry> mCredentialEntries;
57 
58     /**
59      * Constructs a {@link CredentialDescription}.
60      *
61      * @param type the type of the credential returned.
62      * @param supportedElementKeys Keys of elements to match with Credential requests.
63      * @param credentialEntries a list of {@link CredentialEntry}s that are to be shown on the
64      *                          account selector if a credential matches with this description.
65      *                          Each entry contains information to be displayed within an
66      *                          entry on the UI, as well as a {@link android.app.PendingIntent}
67      *                          that will be invoked if the user selects this entry.
68      *
69      * @throws IllegalArgumentException If type is empty.
70      */
CredentialDescription(@onNull String type, @NonNull Set<String> supportedElementKeys, @NonNull List<CredentialEntry> credentialEntries)71     public CredentialDescription(@NonNull String type,
72             @NonNull Set<String> supportedElementKeys,
73             @NonNull List<CredentialEntry> credentialEntries) {
74         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
75         mSupportedElementKeys = Objects.requireNonNull(supportedElementKeys);
76         mCredentialEntries = Objects.requireNonNull(credentialEntries);
77         Preconditions.checkArgument(credentialEntries.size()
78                         <= MAX_ALLOWED_ENTRIES_PER_DESCRIPTION,
79                 "The number of Credential Entries exceed 16.");
80         Preconditions.checkArgument(compareEntryTypes(type, credentialEntries) == 0,
81                 "Credential Entry type(s) do not match the request type.");
82     }
83 
CredentialDescription(@onNull Parcel in)84     private CredentialDescription(@NonNull Parcel in) {
85         String type = in.readString8();
86         List<String> descriptions = in.createStringArrayList();
87         List<CredentialEntry> entries = new ArrayList<>();
88         in.readTypedList(entries, CredentialEntry.CREATOR);
89 
90         mType = type;
91         AnnotationValidations.validate(android.annotation.NonNull.class, null, mType);
92         mSupportedElementKeys = new HashSet<>(descriptions);
93         AnnotationValidations.validate(android.annotation.NonNull.class, null,
94                 mSupportedElementKeys);
95         mCredentialEntries = entries;
96         AnnotationValidations.validate(android.annotation.NonNull.class, null,
97                 mCredentialEntries);
98     }
99 
compareEntryTypes(@onNull String type, @NonNull List<CredentialEntry> credentialEntries)100     private static int compareEntryTypes(@NonNull String type,
101             @NonNull List<CredentialEntry> credentialEntries) {
102         return credentialEntries.stream()
103                 .filter(credentialEntry ->
104                         !credentialEntry.getType().equals(type)).toList().size();
105 
106     }
107 
108     public static final @NonNull Parcelable.Creator<CredentialDescription> CREATOR =
109             new Parcelable.Creator<CredentialDescription>() {
110                 @Override
111                 public CredentialDescription createFromParcel(Parcel in) {
112                     return new CredentialDescription(in);
113                 }
114 
115                 @Override
116                 public CredentialDescription[] newArray(int size) {
117                     return new CredentialDescription[size];
118                 }
119             };
120 
121     @Override
describeContents()122     public int describeContents() {
123         return 0;
124     }
125 
126     @Override
writeToParcel(@onNull Parcel dest, int flags)127     public void writeToParcel(@NonNull Parcel dest, int flags) {
128         dest.writeString8(mType);
129         dest.writeStringList(mSupportedElementKeys.stream().toList());
130         dest.writeTypedList(mCredentialEntries, flags);
131     }
132 
133     /**
134      * Returns the type of the Credential described.
135      */
136     @NonNull
getType()137     public String getType() {
138         return mType;
139     }
140 
141     /**
142      * Returns the flattened JSON string that will be matched with requests.
143      */
144     @NonNull
getSupportedElementKeys()145     public Set<String> getSupportedElementKeys() {
146         return new HashSet<>(mSupportedElementKeys);
147     }
148 
149     /**
150      * Returns the credential entries to be used in the UI.
151      */
152     @NonNull
getCredentialEntries()153     public List<CredentialEntry> getCredentialEntries() {
154         return mCredentialEntries;
155     }
156 
157     /**
158      * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor
159      * enforces {@link CredentialEntry} to have the same type and
160      * {@link android.app.slice.Slice} contained by the entry can not be hashed.
161      */
162     @Override
hashCode()163     public int hashCode() {
164         return Objects.hash(mType, mSupportedElementKeys);
165     }
166 
167     /**
168      * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check.
169      */
170     @Override
equals(Object obj)171     public boolean equals(Object obj) {
172         if (!(obj instanceof CredentialDescription)) {
173             return false;
174         }
175         CredentialDescription other = (CredentialDescription) obj;
176         return mType.equals(other.mType)
177                 && mSupportedElementKeys.equals(other.mSupportedElementKeys);
178     }
179 }
180