1 /*
2  * Copyright 2022 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.selection;
18 
19 import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
20 
21 import android.annotation.FlaggedApi;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.SuppressLint;
26 import android.annotation.SystemApi;
27 import android.app.slice.Slice;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.credentials.GetCredentialRequest;
31 import android.os.CancellationSignal;
32 import android.os.OutcomeReceiver;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 
36 import com.android.internal.util.AnnotationValidations;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.concurrent.Executor;
41 
42 /**
43  * An authentication entry.
44  *
45  * Applicable only for
46  * {@link android.credentials.CredentialManager#getCredential(Context, GetCredentialRequest,
47  * CancellationSignal, Executor, OutcomeReceiver)} flow, authentication entries are a special type
48  * of entries that require the user to unlock the given provider before its credential options
49  * can be fully rendered.
50  *
51  * @hide
52  */
53 @SystemApi
54 @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
55 public final class AuthenticationEntry implements Parcelable {
56     @NonNull
57     private final String mKey;
58     @NonNull
59     private final String mSubkey;
60     @NonNull
61     private final @Status int mStatus;
62     @Nullable
63     private Intent mFrameworkExtrasIntent;
64     @NonNull
65     private final Slice mSlice;
66 
67     /** @hide **/
68     @IntDef(prefix = {"STATUS_"}, value = {
69             STATUS_LOCKED,
70             STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT,
71             STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT,
72     })
73     @Retention(RetentionPolicy.SOURCE)
74     public @interface Status {
75     }
76 
77     /**
78      * This entry is still locked, as initially supplied by the provider.
79      *
80      * This entry should be rendered in a way to signal that it is still locked, and when chosen
81      * will lead to an unlock challenge (e.g. draw a trailing lock icon on this entry).
82      */
83     public static final int STATUS_LOCKED = 0;
84     /**
85      * This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
86      * there is another such entry that was unlocked more recently.
87      *
88      * This entry should be rendered in a way to signal that it was unlocked but turns out to
89      * contain no credential that can be used, and as a result, it should be unclickable.
90      */
91     public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
92     /**
93      * This is the most recent entry that was unlocked but didn't contain any credential.
94      *
95      * There will be at most one authentication entry with this status.
96      *
97      * This entry should be rendered in a way to signal that it was unlocked but turns out to
98      * contain no credential that can be used, and as a result, it should be unclickable.
99      *
100      * If this was the last clickable option prior to unlocking, then the UI should display an
101      * information that all options are exhausted then gracefully finish itself.
102      */
103     public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
104 
AuthenticationEntry(@onNull Parcel in)105     private AuthenticationEntry(@NonNull Parcel in) {
106         mKey = in.readString8();
107         mSubkey = in.readString8();
108         mStatus = in.readInt();
109         mSlice = in.readTypedObject(Slice.CREATOR);
110         mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
111 
112         AnnotationValidations.validate(NonNull.class, null, mKey);
113         AnnotationValidations.validate(NonNull.class, null, mSubkey);
114         AnnotationValidations.validate(NonNull.class, null, mSlice);
115     }
116 
117     /**
118      * Constructor to be used for an entry that requires a pending intent to be invoked
119      * when clicked.
120      *
121      * @param key    the identifier of this entry that's unique within the context of the given
122      *               CredentialManager request. This is used when constructing the
123      *               {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
124      *                String providerId, String entryKey, String entrySubkey,
125      *                ProviderPendingIntentResponse providerPendingIntentResponse)}
126      *
127      * @param subkey the sub-identifier of this entry that's unique within the context of the
128      *               {@code key}. This is used when constructing the
129      *               {@link android.credentials.selection.UserSelectionResult#UserSelectionResult(
130      *                String providerId, String entryKey, String entrySubkey,
131      *                ProviderPendingIntentResponse providerPendingIntentResponse)}
132      * @param intent the intent containing extra data that has to be filled in when launching this
133      *               entry's provider PendingIntent
134      * @param slice  the Slice to be displayed
135      * @param status the entry status, depending on which the entry should be rendered differently
136      */
AuthenticationEntry(@onNull String key, @NonNull String subkey, @NonNull Slice slice, @Status int status, @NonNull Intent intent)137     public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
138             @Status int status, @NonNull Intent intent) {
139         mKey = key;
140         mSubkey = subkey;
141         mSlice = slice;
142         mStatus = status;
143         mFrameworkExtrasIntent = intent;
144     }
145 
146     /**
147      * Returns the identifier of this entry that's unique within the context of the given
148      * CredentialManager request.
149      */
150     @NonNull
getKey()151     public String getKey() {
152         return mKey;
153     }
154 
155     /**
156      * Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
157      */
158     @NonNull
getSubkey()159     public String getSubkey() {
160         return mSubkey;
161     }
162 
163     /** Returns the Slice to be rendered. */
164     @NonNull
getSlice()165     public Slice getSlice() {
166         return mSlice;
167     }
168 
169     /** Returns the entry status, depending on which the entry should be rendered differently. */
170     @NonNull
171     @Status
getStatus()172     public int getStatus() {
173         return mStatus;
174     }
175 
176     /**
177      * Returns the intent containing extra data that has to be filled in when launching this
178      * entry's provider PendingIntent.
179      *
180      * If null, the provider PendingIntent can be launched without any fill in intent.
181      */
182     @Nullable
183     @SuppressLint("IntentBuilderName") // Not building a new intent.
getFrameworkExtrasIntent()184     public Intent getFrameworkExtrasIntent() {
185         return mFrameworkExtrasIntent;
186     }
187 
188     @Override
writeToParcel(@onNull Parcel dest, int flags)189     public void writeToParcel(@NonNull Parcel dest, int flags) {
190         dest.writeString8(mKey);
191         dest.writeString8(mSubkey);
192         dest.writeInt(mStatus);
193         dest.writeTypedObject(mSlice, flags);
194         dest.writeTypedObject(mFrameworkExtrasIntent, flags);
195     }
196 
197     @Override
describeContents()198     public int describeContents() {
199         return 0;
200     }
201 
202     public static final @NonNull Creator<AuthenticationEntry> CREATOR = new Creator<>() {
203         @Override
204         public AuthenticationEntry createFromParcel(@NonNull Parcel in) {
205             return new AuthenticationEntry(in);
206         }
207 
208         @Override
209         public AuthenticationEntry[] newArray(int size) {
210             return new AuthenticationEntry[size];
211         }
212     };
213 }
214