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.hardware.biometrics;
18 
19 import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
20 
21 import android.annotation.FlaggedApi;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 
34 /**
35  * Contains the information of the template of vertical list content view for Biometric Prompt.
36  * <p>
37  * Here's how you'd set a <code>PromptVerticalListContentView</code> on a Biometric Prompt:
38  * <pre class="prettyprint">
39  * BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(...)
40  *     .setTitle(...)
41  *     .setSubTitle(...)
42  *     .setContentView(new PromptVerticalListContentView.Builder()
43  *         .setDescription("test description")
44  *         .addListItem(new PromptContentItemPlainText("test item 1"))
45  *         .addListItem(new PromptContentItemPlainText("test item 2"))
46  *         .addListItem(new PromptContentItemBulletedText("test item 3"))
47  *         .build())
48  *     .build();
49  * </pre>
50  */
51 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
52 public final class PromptVerticalListContentView implements PromptContentViewParcelable {
53     private static final String TAG = "PromptVerticalListContentView";
54     @VisibleForTesting
55     static final int MAX_ITEM_NUMBER = 20;
56     @VisibleForTesting
57     static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
58     @VisibleForTesting
59     static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
60 
61     private final List<PromptContentItemParcelable> mContentList;
62     private final String mDescription;
63 
PromptVerticalListContentView( @onNull List<PromptContentItemParcelable> contentList, @NonNull String description)64     private PromptVerticalListContentView(
65             @NonNull List<PromptContentItemParcelable> contentList,
66             @NonNull String description) {
67         mContentList = contentList;
68         mDescription = description;
69     }
70 
PromptVerticalListContentView(Parcel in)71     private PromptVerticalListContentView(Parcel in) {
72         mContentList = in.readArrayList(
73                 PromptContentItemParcelable.class.getClassLoader(),
74                 PromptContentItemParcelable.class);
75         mDescription = in.readString();
76     }
77 
78     /**
79      * Returns the maximum count of the list items.
80      */
getMaxItemCount()81     public static int getMaxItemCount() {
82         return MAX_ITEM_NUMBER;
83     }
84 
85     /**
86      * Returns the maximum number of characters allowed for each item's text.
87      */
getMaxEachItemCharacterNumber()88     public static int getMaxEachItemCharacterNumber() {
89         return MAX_EACH_ITEM_CHARACTER_NUMBER;
90     }
91 
92     /**
93      * Gets the description for the content view, as set by
94      * {@link PromptVerticalListContentView.Builder#setDescription(String)}.
95      *
96      * @return The description for the content view, or null if the content view has no description.
97      */
98     @Nullable
getDescription()99     public String getDescription() {
100         return mDescription;
101     }
102 
103     /**
104      * Gets the list of items on the content view, as set by
105      * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentItem)}.
106      *
107      * @return The item list on the content view.
108      */
109     @NonNull
getListItems()110     public List<PromptContentItem> getListItems() {
111         return new ArrayList<>(mContentList);
112     }
113 
114     /**
115      * {@inheritDoc}
116      */
117     @Override
describeContents()118     public int describeContents() {
119         return 0;
120     }
121 
122     /**
123      * {@inheritDoc}
124      */
125     @Override
writeToParcel(@ndroidx.annotation.NonNull Parcel dest, int flags)126     public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
127         dest.writeList(mContentList);
128         dest.writeString(mDescription);
129     }
130 
131     /**
132      * @see Parcelable.Creator
133      */
134     @NonNull
135     public static final Creator<PromptVerticalListContentView> CREATOR = new Creator<>() {
136         @Override
137         public PromptVerticalListContentView createFromParcel(Parcel in) {
138             return new PromptVerticalListContentView(in);
139         }
140 
141         @Override
142         public PromptVerticalListContentView[] newArray(int size) {
143             return new PromptVerticalListContentView[size];
144         }
145     };
146 
147 
148     /**
149      * A builder that collects arguments to be shown on the vertical list view.
150      */
151     public static final class Builder {
152         private final List<PromptContentItemParcelable> mContentList = new ArrayList<>();
153         private String mDescription;
154 
155         /**
156          * Optional: Sets a description that will be shown on the content view.
157          *
158          * @param description The description to display.
159          * @return This builder.
160          */
161         @NonNull
setDescription(@onNull String description)162         public Builder setDescription(@NonNull String description) {
163             if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
164                 Log.w(TAG, "The character number of description exceeds "
165                         + MAX_DESCRIPTION_CHARACTER_NUMBER);
166             }
167             mDescription = description;
168             return this;
169         }
170 
171         /**
172          * Optional: Adds a list item in the current row.
173          *
174          * @param listItem The list item view to display
175          * @return This builder.
176          * @throws IllegalArgumentException If the number of list items exceeds certain limit.
177          */
178         @NonNull
addListItem(@onNull PromptContentItem listItem)179         public Builder addListItem(@NonNull PromptContentItem listItem) {
180             mContentList.add((PromptContentItemParcelable) listItem);
181             checkItemLimits(listItem);
182             return this;
183         }
184 
185         /**
186          * Optional: Adds a list item in the current row.
187          *
188          * @param listItem The list item view to display
189          * @param index    The position at which to add the item
190          * @return This builder.
191          * @throws IllegalArgumentException If the number of list items exceeds certain limit.
192          */
193         @NonNull
addListItem(@onNull PromptContentItem listItem, int index)194         public Builder addListItem(@NonNull PromptContentItem listItem, int index) {
195             mContentList.add(index, (PromptContentItemParcelable) listItem);
196             checkItemLimits(listItem);
197             return this;
198         }
199 
checkItemLimits(@onNull PromptContentItem listItem)200         private void checkItemLimits(@NonNull PromptContentItem listItem) {
201             if (doesListItemExceedsCharLimit(listItem)) {
202                 Log.w(TAG, "The character number of list item exceeds "
203                         + MAX_EACH_ITEM_CHARACTER_NUMBER);
204             }
205             if (mContentList.size() > MAX_ITEM_NUMBER) {
206                 throw new IllegalArgumentException(
207                         "The number of list items exceeds " + MAX_ITEM_NUMBER);
208             }
209         }
210 
doesListItemExceedsCharLimit(PromptContentItem listItem)211         private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) {
212             if (listItem instanceof PromptContentItemPlainText) {
213                 return ((PromptContentItemPlainText) listItem).getText().length()
214                         > MAX_EACH_ITEM_CHARACTER_NUMBER;
215             } else if (listItem instanceof PromptContentItemBulletedText) {
216                 return ((PromptContentItemBulletedText) listItem).getText().length()
217                         > MAX_EACH_ITEM_CHARACTER_NUMBER;
218             } else {
219                 return false;
220             }
221         }
222 
223 
224         /**
225          * Creates a {@link PromptVerticalListContentView}.
226          *
227          * @return An instance of {@link PromptVerticalListContentView}.
228          */
229         @NonNull
build()230         public PromptVerticalListContentView build() {
231             return new PromptVerticalListContentView(mContentList, mDescription);
232         }
233     }
234 }
235