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