1 /*
2  * Copyright (C) 2024 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.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
20 import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
21 
22 import android.annotation.CallbackExecutor;
23 import android.annotation.FlaggedApi;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.RequiresPermission;
27 import android.content.DialogInterface;
28 import android.hardware.biometrics.BiometricPrompt.ButtonInfo;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.concurrent.Executor;
36 
37 /**
38  * Contains the information of the template of content view with a more options button for
39  * Biometric Prompt.
40  * <p>
41  * This button should be used to provide more options for sign in or other purposes, such as when a
42  * user needs to select between multiple app-specific accounts or profiles that are available for
43  * sign in.
44  * <p>
45  * Apps should avoid using this when possible because it will create additional steps that the user
46  * must navigate through - clicking the more options button will dismiss the prompt, provide the app
47  * an opportunity to ask the user for the correct option, and finally allow the app to decide how to
48  * proceed once selected.
49  *
50  * <p>
51  * Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric
52  * Prompt:
53  * <pre class="prettyprint">
54  * BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(...)
55  *     .setTitle(...)
56  *     .setSubTitle(...)
57  *     .setContentView(new PromptContentViewWithMoreOptionsButton.Builder()
58  *         .setDescription("test description")
59  *         .setMoreOptionsButtonListener(executor, listener)
60  *         .build())
61  *     .build();
62  * </pre>
63  */
64 @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
65 public final class PromptContentViewWithMoreOptionsButton implements PromptContentViewParcelable {
66     private static final String TAG = "PromptContentViewWithMoreOptionsButton";
67     @VisibleForTesting
68     static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
69 
70     private final String mDescription;
71     private DialogInterface.OnClickListener mListener;
72     private ButtonInfo mButtonInfo;
73 
PromptContentViewWithMoreOptionsButton( @onNull String description, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)74     private PromptContentViewWithMoreOptionsButton(
75             @NonNull String description, @NonNull @CallbackExecutor Executor executor,
76             @NonNull DialogInterface.OnClickListener listener) {
77         mDescription = description;
78         mListener = listener;
79         mButtonInfo = new ButtonInfo(executor, listener);
80     }
81 
PromptContentViewWithMoreOptionsButton(Parcel in)82     private PromptContentViewWithMoreOptionsButton(Parcel in) {
83         mDescription = in.readString();
84     }
85 
86     /**
87      * Gets the description for the content view, as set by
88      * {@link PromptContentViewWithMoreOptionsButton.Builder#setDescription(String)}.
89      *
90      * @return The description for the content view, or null if the content view has no description.
91      */
92     @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
93     @Nullable
getDescription()94     public String getDescription() {
95         return mDescription;
96     }
97 
98     /**
99      * Gets the click listener for the more options button on the content view, as set by
100      * {@link PromptContentViewWithMoreOptionsButton.Builder#setMoreOptionsButtonListener(Executor,
101      * DialogInterface.OnClickListener)}.
102      *
103      * @return The click listener for the more options button on the content view.
104      */
105     @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
106     @NonNull
getMoreOptionsButtonListener()107     public DialogInterface.OnClickListener getMoreOptionsButtonListener() {
108         return mListener;
109     }
110 
getButtonInfo()111     ButtonInfo getButtonInfo() {
112         return mButtonInfo;
113     }
114 
115     @Override
describeContents()116     public int describeContents() {
117         return 0;
118     }
119 
120     @Override
writeToParcel(@onNull Parcel dest, int flags)121     public void writeToParcel(@NonNull Parcel dest, int flags) {
122         dest.writeString(mDescription);
123     }
124 
125     /**
126      * @see Parcelable.Creator
127      */
128     @NonNull
129     public static final Creator<PromptContentViewWithMoreOptionsButton> CREATOR = new Creator<>() {
130         @Override
131         public PromptContentViewWithMoreOptionsButton createFromParcel(Parcel in) {
132             return new PromptContentViewWithMoreOptionsButton(in);
133         }
134 
135         @Override
136         public PromptContentViewWithMoreOptionsButton[] newArray(int size) {
137             return new PromptContentViewWithMoreOptionsButton[size];
138         }
139     };
140 
141     /**
142      * A builder that collects arguments to be shown on the content view with more options button.
143      */
144     public static final class Builder {
145         private String mDescription;
146         private Executor mExecutor;
147         private DialogInterface.OnClickListener mListener;
148 
149         /**
150          * Optional: Sets a description that will be shown on the content view.
151          *
152          * @param description The description to display.
153          * @return This builder.
154          */
155         @NonNull
156         @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
setDescription(@onNull String description)157         public Builder setDescription(@NonNull String description) {
158             if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
159                 Log.w(TAG, "The character number of description exceeds "
160                         + MAX_DESCRIPTION_CHARACTER_NUMBER);
161             }
162             mDescription = description;
163             return this;
164         }
165 
166         /**
167          * Required: Sets the executor and click listener for the more options button on the
168          * prompt content.
169          *
170          * @param executor Executor that will be used to run the on click callback.
171          * @param listener Listener containing a callback to be run when the button is pressed.
172          * @return This builder.
173          */
174         @NonNull
175         @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
setMoreOptionsButtonListener(@onNull @allbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)176         public Builder setMoreOptionsButtonListener(@NonNull @CallbackExecutor Executor executor,
177                 @NonNull DialogInterface.OnClickListener listener) {
178             mExecutor = executor;
179             mListener = listener;
180             return this;
181         }
182 
183 
184         /**
185          * Creates a {@link PromptContentViewWithMoreOptionsButton}.
186          *
187          * @return An instance of {@link PromptContentViewWithMoreOptionsButton}.
188          * @throws IllegalArgumentException If the executor of more options button is null, or the
189          *                                  listener of more options button is null.
190          */
191         @NonNull
192         @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
build()193         public PromptContentViewWithMoreOptionsButton build() {
194             if (mExecutor == null) {
195                 throw new IllegalArgumentException(
196                         "The executor for the listener of more options button on prompt content "
197                                 + "must be set and non-null if "
198                                 + "PromptContentViewWithMoreOptionsButton is used.");
199             }
200             if (mListener == null) {
201                 throw new IllegalArgumentException(
202                         "The listener of more options button on prompt content must be set and "
203                                 + "non-null if PromptContentViewWithMoreOptionsButton is used.");
204             }
205             return new PromptContentViewWithMoreOptionsButton(mDescription, mExecutor, mListener);
206         }
207     }
208 }
209