@@ -19,6 +19,8 @@ import android.app.PendingIntent
19
19
import android.content.Context
20
20
import android.content.Intent
21
21
import android.content.SharedPreferences
22
+ import android.hardware.biometrics.BiometricManager
23
+ import android.os.Build
22
24
import android.os.Bundle
23
25
import androidx.credentials.provider.BeginCreateCredentialRequest
24
26
import androidx.credentials.provider.BeginCreateCredentialResponse
@@ -28,6 +30,7 @@ import androidx.credentials.provider.BeginGetCredentialRequest
28
30
import androidx.credentials.provider.BeginGetCredentialResponse.Builder
29
31
import androidx.credentials.provider.BeginGetPasswordOption
30
32
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
33
+ import androidx.credentials.provider.BiometricPromptData
31
34
import androidx.credentials.provider.CreateEntry
32
35
import androidx.credentials.provider.PasswordCredentialEntry
33
36
import androidx.credentials.provider.PublicKeyCredentialEntry
@@ -39,14 +42,18 @@ import java.time.Instant
39
42
import java.util.concurrent.atomic.AtomicInteger
40
43
41
44
/* *
42
- * This class is responsible for creating and retrieving credential entries (password & passkey) to & from the database
45
+ * Manages the creation and retrieval of credential entries (passwords and passkeys)
46
+ * to and from the MyVault Provider. This class also configures the biometric prompt
47
+ * for Android API level 35 and higher.
43
48
*/
44
49
class CredentialsRepository (
45
50
private val sharedPreferences : SharedPreferences ,
46
51
private val credentialsDataSource : CredentialsDataSource ,
47
52
private val applicationContext : Context ,
48
53
) {
49
54
private val requestCode: AtomicInteger = AtomicInteger ()
55
+ private val allowedAuthenticator =
56
+ BiometricManager .Authenticators .BIOMETRIC_WEAK or BiometricManager .Authenticators .BIOMETRIC_STRONG or BiometricManager .Authenticators .DEVICE_CREDENTIAL
50
57
51
58
/* *
52
59
* This method queries credentials from your database, create passkey and password entries to populate.
@@ -153,19 +160,20 @@ class CredentialsRepository(
153
160
val passwordItemCurrent = it.next()
154
161
155
162
// Create Password entry
156
- val entry = PasswordCredentialEntry .Builder (
157
- applicationContext,
158
- passwordItemCurrent.username,
159
- createNewPendingIntent(
160
- passwordItemCurrent.username,
161
- GET_PASSWORD_INTENT ,
162
- ),
163
- option,
164
- )
165
- .setDisplayName(" display-${passwordItemCurrent.username} " )
166
- .setIcon(AppDependencies .providerIcon!! )
167
- .setLastUsedTime(Instant .ofEpochMilli(passwordItemCurrent.lastUsedTimeMs))
168
- .build()
163
+ val entryBuilder =
164
+ configurePasswordCredentialEntryBuilder(passwordItemCurrent, option)
165
+
166
+ // Configure own biometric prompt data
167
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .VANILLA_ICE_CREAM ) {
168
+ entryBuilder.setBiometricPromptData(
169
+ BiometricPromptData (
170
+ cryptoObject = null ,
171
+ allowedAuthenticators = allowedAuthenticator,
172
+ ),
173
+ )
174
+ }
175
+
176
+ val entry = entryBuilder.build()
169
177
// Add the entry to the response builder.
170
178
responseBuilder.addCredentialEntry(entry)
171
179
}
@@ -175,6 +183,32 @@ class CredentialsRepository(
175
183
return true
176
184
}
177
185
186
+ /* *
187
+ * Configures a {@link PasswordCredentialEntry.Builder} for a given password item.
188
+ *
189
+ * @param passwordItemCurrent The {@link PasswordItem} containing the password details.
190
+ * @param option The {@link BeginGetPasswordOption} containing the request parameters.
191
+ * @return A {@link PasswordCredentialEntry.Builder} configured with the provided
192
+ * password details and request options.
193
+ */
194
+ private fun configurePasswordCredentialEntryBuilder (
195
+ passwordItemCurrent : PasswordItem ,
196
+ option : BeginGetPasswordOption ,
197
+ ): PasswordCredentialEntry .Builder {
198
+ val entryBuilder = PasswordCredentialEntry .Builder (
199
+ applicationContext,
200
+ passwordItemCurrent.username,
201
+ createNewPendingIntent(
202
+ passwordItemCurrent.username,
203
+ GET_PASSWORD_INTENT ,
204
+ ),
205
+ option,
206
+ ).setDisplayName(" display-${passwordItemCurrent.username} " )
207
+ .setIcon(AppDependencies .providerIcon!! )
208
+ .setLastUsedTime(Instant .ofEpochMilli(passwordItemCurrent.lastUsedTimeMs))
209
+ return entryBuilder
210
+ }
211
+
178
212
/* *
179
213
* This method queries credentials from your database, create passkey and password entries to populate.
180
214
*
@@ -207,18 +241,21 @@ class CredentialsRepository(
207
241
)
208
242
209
243
// Create a PublicKeyCredentialEntry object to represent the passkey
210
- val entryBuilder = PublicKeyCredentialEntry .Builder (
211
- applicationContext,
212
- passkey.username,
213
- pendingIntent,
214
- option,
215
- )
216
- .setDisplayName(passkey.displayName)
217
- .setLastUsedTime(Instant .ofEpochMilli(passkey.lastUsedTimeMs))
218
- .setIcon(AppDependencies .providerIcon!! )
244
+ val entryBuilder =
245
+ configurePublicKeyCredentialEntryBuilder(passkey, pendingIntent, option)
219
246
220
- val entry = entryBuilder
221
- .build()
247
+ // Configure biometric prompt data
248
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .VANILLA_ICE_CREAM ) {
249
+ entryBuilder.setBiometricPromptData(
250
+ BiometricPromptData (
251
+ cryptoObject = null ,
252
+ allowedAuthenticators = allowedAuthenticator,
253
+ ),
254
+ )
255
+ }
256
+
257
+ val entry = entryBuilder.build()
258
+ // Add the entry to the response builder.
222
259
responseBuilder.addCredentialEntry(entry)
223
260
}
224
261
} catch (e: IOException ) {
@@ -227,6 +264,33 @@ class CredentialsRepository(
227
264
return true
228
265
}
229
266
267
+ /* *
268
+ * Creates a {@link PublicKeyCredentialEntry.Builder} for a given passkey.
269
+ *
270
+ * @param passkey The {@link PasskeyItem} containing the passkey details.
271
+ * @param pendingIntent The {@link PendingIntent} to be associated with the entry,
272
+ * used to launch the passkey retrieval process.
273
+ * @param option The {@link BeginGetPublicKeyCredentialOption} containing the
274
+ * request parameters for the passkey retrieval.
275
+ * @return A {@link PublicKeyCredentialEntry.Builder} configured with the provided
276
+ * passkey details, pending intent, and request options.
277
+ */
278
+ private fun configurePublicKeyCredentialEntryBuilder (
279
+ passkey : PasskeyItem ,
280
+ pendingIntent : PendingIntent ,
281
+ option : BeginGetPublicKeyCredentialOption ,
282
+ ): PublicKeyCredentialEntry .Builder {
283
+ val entryBuilder = PublicKeyCredentialEntry .Builder (
284
+ applicationContext,
285
+ passkey.username,
286
+ pendingIntent,
287
+ option,
288
+ ).setDisplayName(passkey.displayName)
289
+ .setLastUsedTime(Instant .ofEpochMilli(passkey.lastUsedTimeMs))
290
+ .setIcon(AppDependencies .providerIcon!! )
291
+ return entryBuilder
292
+ }
293
+
230
294
/* *
231
295
* Creates a new PendingIntent for the given action and account ID.
232
296
*
@@ -261,15 +325,18 @@ class CredentialsRepository(
261
325
}
262
326
263
327
/* *
264
- * Adds a CreateEntry to the BeginCreateCredentialResponse .
328
+ * Handles the creation of a credential query response .
265
329
*
266
- * Each CreateEntry should correspond to an account where the credential can be saved,
267
- * and must have a PendingIntent set along with other required metadata.
330
+ * <p>This method constructs a {@link BeginCreateCredentialResponse} that
331
+ * includes a {@link CreateEntry} for an account where credentials can be
332
+ * saved. The {@link CreateEntry} contains a {@link PendingIntent} and other
333
+ * metadata required for the credential creation process.
268
334
*
269
335
* @param passwordCount The number of password credentials associated with the account.
270
- * @param passkeyCount The number of passkey credentials associated with the account.
271
- * @param intentType The type of intent to be used for the PendingIntent.
272
- * @return A BeginCreateCredentialResponse with the CreateEntry added.
336
+ * @param passkeyCount The number of passkey credentials associated with the account.
337
+ * @param intentType The type of intent to be used for the {@link PendingIntent}.
338
+ * @return A {@link BeginCreateCredentialResponse} containing the created
339
+ * {@link CreateEntry}.
273
340
*/
274
341
private fun handleCreateCredentialQuery (
275
342
passwordCount : Int ,
@@ -278,46 +345,69 @@ class CredentialsRepository(
278
345
): BeginCreateCredentialResponse {
279
346
// Each CreateEntry should correspond to an account where the credential can be saved,
280
347
// and must have a PendingIntent set along with other required metadata.
281
- return BeginCreateCredentialResponse .Builder ()
282
- .addCreateEntry(
283
- createEntry(
284
- intentType,
285
- passwordCount,
286
- passkeyCount,
287
- ),
288
- ).build()
348
+
349
+ // Create the CreateEntry using the provided parameters.
350
+ val createCredentialEntry = createEntry(
351
+ intentType,
352
+ passwordCount,
353
+ passkeyCount,
354
+ )
355
+ // Build and return the BeginCreateCredentialResponse with the created CreateEntry.
356
+ return BeginCreateCredentialResponse .Builder ().addCreateEntry(
357
+ createCredentialEntry,
358
+ ).build()
289
359
}
290
360
291
361
/* *
292
- * Creates a CreateEntry object for the user account based on their credential preferences.
362
+ * Creates a {@link CreateEntry} object for the user account.
363
+ *
364
+ * <p>This method constructs a {@link CreateEntry} that represents an account
365
+ * where credentials can be saved. It sets various properties of the
366
+ * {@link CreateEntry}, including the account identifier, a {@link PendingIntent}
367
+ * for credential creation, the last used time, the number of password and
368
+ * passkey credentials, the total credential count, and a description.
369
+ * Additionally, it configures biometric prompt data if the device is running
370
+ * Android API level 35 or higher.
293
371
*
294
- * @param intentType The type of intent to be used for the PendingIntent.
372
+ * @param intentType The type of intent to be used for the {@link PendingIntent} .
295
373
* @param passwordCount The number of password credentials associated with the account.
296
- * @param passkeyCount The number of passkey credentials associated with the account.
297
- * @return A CreateEntry object.
374
+ * @param passkeyCount The number of passkey credentials associated with the account.
375
+ * @return A {@link CreateEntry} object configured with the specified parameters .
298
376
*/
299
377
private fun createEntry (
300
378
intentType : String ,
301
379
passwordCount : Int ,
302
380
passkeyCount : Int ,
303
- ) = CreateEntry .Builder (
304
- USER_ACCOUNT ,
305
- createNewPendingIntent(USER_ACCOUNT , intentType),
306
- ).setLastUsedTime(
307
- Instant .ofEpochMilli(
308
- sharedPreferences.getLong(
309
- KEY_ACCOUNT_LAST_USED_MS ,
310
- 0L ,
381
+ ): CreateEntry {
382
+ // Create a CreateEntry.Builder with the user account and a PendingIntent.
383
+ val createEntryBuilder = CreateEntry .Builder (
384
+ USER_ACCOUNT ,
385
+ createNewPendingIntent(USER_ACCOUNT , intentType),
386
+ ).setLastUsedTime(
387
+ Instant .ofEpochMilli(
388
+ sharedPreferences.getLong(
389
+ KEY_ACCOUNT_LAST_USED_MS ,
390
+ 0L ,
391
+ ),
311
392
),
312
- ),
313
- )
314
- .setPasswordCredentialCount(passwordCount)
315
- .setPublicKeyCredentialCount(passkeyCount)
316
- .setTotalCredentialCount(passwordCount + passkeyCount)
317
- .setDescription(
318
- CREDENTIAL_DESCRIPTION ,
319
- )
320
- .build()
393
+ ).setPasswordCredentialCount(passwordCount).setPublicKeyCredentialCount(passkeyCount)
394
+ .setTotalCredentialCount(passwordCount + passkeyCount).setDescription(
395
+ CREDENTIAL_DESCRIPTION ,
396
+ )
397
+
398
+ // Configure biometric prompt data if the device is running Android API level 35 or higher.
399
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .VANILLA_ICE_CREAM ) {
400
+ createEntryBuilder.setBiometricPromptData(
401
+ BiometricPromptData (
402
+ cryptoObject = null ,
403
+ allowedAuthenticators = allowedAuthenticator,
404
+ ),
405
+ )
406
+ }
407
+
408
+ // Build and return the CreateEntry.
409
+ return createEntryBuilder.build()
410
+ }
321
411
322
412
companion object {
323
413
private const val CREATE_PASSWORD_INTENT =
0 commit comments