Skip to content

Conversation

@Jignesh-dimagi
Copy link
Contributor

Camera permission before starting camera

Technical Summary

https://dimagi.atlassian.net/browse/CCCT-1304

Labels and Review

  • Do we need to enhance the manual QA test coverage ? If yes, the "QA Note" label is set correctly
  • Does the PR introduce any major changes worth communicating ? If yes, the "Release Note" label is set and a "Release Note" is specified in PR description.
  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

Camera permission before starting camera
@coderabbitai
Copy link

coderabbitai bot commented Jun 3, 2025

📝 Walkthrough

Walkthrough

This change introduces explicit runtime camera permission handling to the MicroImageActivity using the AndroidX Activity Result API, ensuring the camera is only started after permission is granted. The activity now implements the RuntimePermissionRequester interface and includes logic to request permission, show rationales, and handle denial cases. New localized string resources related to camera permission are added to both the Android resource XML and translatable strings files. Additionally, biometric authentication flows are updated: the BiometricsHelper methods now accept a parameter to control whether fallback authentication options (like PIN) are allowed, and related classes are updated to use this parameter and simplify authentication state tracking.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MicroImageActivity
    participant Android OS
    participant Camera

    User->>MicroImageActivity: Launch activity
    MicroImageActivity->>Android OS: Check camera permission
    alt Permission granted
        MicroImageActivity->>Camera: Start camera
    else Permission not granted
        MicroImageActivity->>Android OS: Request camera permission
        Android OS-->>User: Show permission dialog
        User-->>Android OS: Grant or deny permission
        Android OS-->>MicroImageActivity: Return permission result
        alt User granted permission
            MicroImageActivity->>Camera: Start camera
        else User denied permission
            MicroImageActivity->>MicroImageActivity: Log error, finish activity
        end
    end
Loading
sequenceDiagram
    participant User
    participant PersonalIdBiometricConfigFragment
    participant BiometricsHelper
    participant BiometricPrompt

    User->>PersonalIdBiometricConfigFragment: Click "Authenticate"
    PersonalIdBiometricConfigFragment->>BiometricsHelper: authenticateFingerprint(..., allowOtherOptions)
    BiometricsHelper->>BiometricPrompt: Show prompt (biometric only or with PIN fallback)
    User-->>BiometricPrompt: Authenticate (biometric or PIN)
    BiometricPrompt-->>BiometricsHelper: Return result
    BiometricsHelper-->>PersonalIdBiometricConfigFragment: Callback with result
    PersonalIdBiometricConfigFragment->>PersonalIdBiometricConfigFragment: Handle success or error
Loading

Possibly related PRs

  • Implement Personal ID face capture Page #3116: Implements the initial MicroImageActivity and Personal ID face capture UI; the current PR's permission and string changes directly support and extend the features introduced in this PR.

Suggested reviewers

  • OrangeAndGreen
  • shubham1g5
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java (1)

35-36: New imports added but not actively used in visible code.

The error code imports ERROR_NEGATIVE_BUTTON and ERROR_USER_CANCELED are added but don't appear to be used in the current implementation. This might indicate they're intended for future use or were part of removed logic.

If these imports are not needed, consider removing them to keep the code clean:

-import static androidx.biometric.BiometricPrompt.ERROR_NEGATIVE_BUTTON;
-import static androidx.biometric.BiometricPrompt.ERROR_USER_CANCELED;
app/src/org/commcare/fragments/MicroImageActivity.java (3)

74-83: Fix formatting and improve error handling in permission launcher.

The ActivityResultLauncher implementation is functionally correct but has minor issues:

Apply this diff to fix the formatting and improve readability:

-                }else{
+                } else {

The permission flow logic is sound - granting permission starts the camera, while denial logs an error and exits appropriately.


302-302: Fix typo in method name.

There's a typo in the method name that should be corrected for consistency and readability.

Apply this diff to fix the typo:

-            finishWithResul(finalImageAsBase64);
+            finishWithResult(finalImageAsBase64);

Note: This will also require updating the method declaration at line 319.


319-319: Update method declaration to match the corrected method name.

The method declaration needs to be updated to match the corrected method name.

Apply this diff to fix the method declaration:

-    private void finishWithResul(String finalImageAsBase64) {
+    private void finishWithResult(String finalImageAsBase64) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f1931f and 68e0c40.

📒 Files selected for processing (6)
  • app/assets/locales/android_translatable_strings.txt (1 hunks)
  • app/res/values/strings.xml (1 hunks)
  • app/src/org/commcare/connect/PersonalIdManager.java (1 hunks)
  • app/src/org/commcare/fragments/MicroImageActivity.java (5 hunks)
  • app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java (4 hunks)
  • app/src/org/commcare/utils/BiometricsHelper.java (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/src/org/commcare/connect/PersonalIdManager.java (1)
app/src/org/commcare/utils/BiometricsHelper.java (1)
  • BiometricsHelper (31-257)
🔇 Additional comments (13)
app/src/org/commcare/connect/PersonalIdManager.java (1)

198-199: LGTM! Clear logic for controlling authentication fallback options.

The implementation correctly determines whether to allow PIN fallback during fingerprint authentication based on PIN availability. This aligns well with the PR objective to prevent fallback to PIN when configured for biometric-only authentication.

app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java (3)

107-107: Improved authentication method detection.

The change from tracking fingerprint attempts via isAttemptingFingerprint to determining the method directly from BiometricPrompt.AuthenticationResult is a cleaner approach. It eliminates state management complexity and provides more accurate authentication method reporting.


119-121: Method signature updated to accept explicit authentication method.

The reportAuthSuccess method now takes an explicit method parameter instead of relying on implicit state tracking. This makes the method more predictable and easier to test.


202-202:

Details

❓ Verification inconclusive

Verify the authentication flow logic for biometric-only requirements.

The boolean expression !BIOMETRIC_TYPE.equals(personalIdSessionDataViewModel.getPersonalIdSessionData().getRequiredLock()) determines whether other options are allowed. This means:

  • If required lock is BIOMETRIC_TYPE → allowOtherOptions = false (biometric only)
  • If required lock is PIN → allowOtherOptions = true (allow PIN fallback)

This correctly implements the intended behavior to prevent PIN fallback when biometric authentication is specifically required.

Let me verify this logic by checking the session data constants:


🏁 Script executed:

#!/bin/bash
# Description: Verify the PersonalIdSessionData constants to confirm the lock type values

# Search for the lock type constants
ast-grep --pattern 'class PersonalIdSessionData {
  $$$
  public static final String BIOMETRIC_TYPE = $_;
  $$$
  public static final String PIN = $_;
  $$$
}'

# Also search for any other references to these constants
rg -A 2 -B 2 "BIOMETRIC_TYPE|PIN.*=" --type java

Length of output: 13538


Verify lock-type constant definitions in PersonalIdSessionData

Before approving, please confirm that the string constants and null-safety align with your logic:

• In app/src/org/commcare/android/database/connect/models/PersonalIdSessionData.java, ensure
– BIOMETRIC_TYPE is defined as the exact value you expect for biometric-only flows
– PIN is defined as the exact fallback value
getRequiredLock() never returns null (or add a null check) so that
!BIOMETRIC_TYPE.equals(getRequiredLock()) behaves as intended

The call in
app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java (line 202)

BiometricsHelper.authenticateFingerprint(
    requireActivity(),
    biometricManager,
    biometricCallback,
    !BIOMETRIC_TYPE.equals(
        personalIdSessionDataViewModel
            .getPersonalIdSessionData()
            .getRequiredLock()
    )
);

correctly toggles “allowOtherOptions” based on the required lock.

app/src/org/commcare/utils/BiometricsHelper.java (4)

80-82: Method signature updated to support authentication flow control.

The addition of the allowOtherOptions parameter enables callers to control whether PIN fallback is available during fingerprint authentication. The parameter is correctly passed through to the underlying implementation.


126-127: PIN authentication correctly allows all options.

The authenticatePin method correctly passes true for allowOtherOptions, which makes sense since PIN authentication should naturally allow device credentials.


130-130: Core method signature updated with new parameter.

The authenticatePinOrBiometric method now accepts the allowOtherOptions parameter, enabling conditional prompt configuration based on authentication requirements.


136-148:

Details

✅ Verification successful

Well-implemented conditional biometric prompt configuration.

The implementation correctly handles both authentication scenarios:

When allowOtherOptions = true:

  • Allows device credentials (PIN) + strong/weak biometrics
  • System handles the UI for multiple options

When allowOtherOptions = false:

  • Restricts to biometric authentication only
  • Provides explicit "Cancel" button for user control

This design properly implements the requirement to prevent PIN fallback when biometric-only authentication is required.

Let me verify that the string resources are properly defined:


🏁 Script executed:

#!/bin/bash
# Description: Verify that the required string resources exist

# Check for the title and message strings
rg "connect_unlock_title|connect_unlock_message|cancel" app/res/values/strings.xml

# Also check if these strings exist in any other resource files
fd "strings.xml" --exec grep -l "connect_unlock_title\|connect_unlock_message\|cancel" {}

Length of output: 1082


Conditional Biometric Prompt and String Resources Verified

The biometric prompt logic in app/src/org/commcare/utils/BiometricsHelper.java (lines 136–148) correctly handles both scenarios:

  • allowOtherOptions = true: allows device credentials + strong/weak biometrics
  • allowOtherOptions = false: restricts to biometrics only and provides a “Cancel” button

All referenced string resources (connect_unlock_title, connect_unlock_message, cancel) are defined in app/res/values/strings.xml and present in each locale folder.

No further changes needed—approving these updates.

app/assets/locales/android_translatable_strings.txt (1)

1024-1025:

Details

❌ Incorrect review comment

New locale key for camera permission denial
The entry microimage.camera.permission.denied clearly informs users when camera permission is denied. Please verify that the code references this exact key and that the XML resource (see above) uses the matching name.

To confirm, run:


🏁 Script executed:

#!/bin/bash
# Verify the denial string key is referenced in code
rg -n "microimage_camera_permission_denied" -A3 app/src

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Check that the Android string resource is defined with the correct key
rg -n '<string name="microimage_camera_permission_denied"' -g 'app/src/**/*.xml'

# Check that the code actually references this string resource in Java/Kotlin
rg -n "microimage_camera_permission_denied" -g 'app/src/**/*.{java,kt}'

Length of output: 303


🏁 Script executed:

#!/bin/bash
# Search for XML resource definition across entire repo
rg -n '<string name="microimage_camera_permission_denied"' --type xml

# Search Java/Kotlin references to the generated resource
rg -n 'R\.string\.microimage_camera_permission_denied' -g '*.{java,kt}'

# Search direct dotted key usage in code
rg -n 'microimage\.camera\.permission\.denied' -g '*.{java,kt}'

Length of output: 388


Ignore Android XML resource suggestion
The microimage.camera.permission.denied key is defined in your runtime locale file and is correctly referenced in code—no Android XML <string> entry is required here.

• Locale definition:

  • app/assets/locales/android_translatable_strings.txt contains
    microimage.camera.permission.denied=Go to settings and enable camera permission to use this feature  
    

• Code reference:

  • app/src/org/commcare/fragments/MicroImageActivity.java:80
    logErrorAndExit("Error acquiring camera permission", "microimage.camera.permission.denied", null);

CommCare uses these asset-based locale files at runtime rather than Android XML resources for translatable strings. You can safely disregard the XML‐resource check.

Likely an incorrect or invalid review comment.

app/src/org/commcare/fragments/MicroImageActivity.java (4)

3-3: LGTM! Proper imports and interface implementation for permission handling.

The new imports correctly include necessary classes for runtime permission handling (Manifest, RuntimePermissionRequester, Permissions, dialog helpers, and ActivityResultLauncher). The class now properly implements RuntimePermissionRequester interface to handle permission requests in a standardized way.

Also applies to: 27-27, 33-33, 35-36, 44-45, 60-60


106-107: Excellent security improvement - proper permission checking before camera access.

Replacing the direct startCamera() call with checkForCameraPermission() ensures the app follows Android's runtime permission model, preventing potential crashes or permission denial issues.


109-124: Well-implemented permission checking logic with proper UX flow.

The permission checking method correctly:

  • Checks for missing camera permission using utility methods
  • Shows rationale dialog when appropriate (follows Android UX guidelines)
  • Requests permission directly when rationale isn't needed
  • Falls back to starting camera when permission is already granted

The implementation follows Android best practices for runtime permissions and provides good user experience.


126-129: Correct implementation of RuntimePermissionRequester interface.

The requestNeededPermissions method properly launches the camera permission request via the ActivityResultLauncher, following the modern Android permission request pattern.

Comment on lines +935 to +936
<string name="camera_permission_title">Permission for camera</string>
<string name="camera_permission_msg">In order to take a picture, CommCare needs permission to use your device camera.</string>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing denial string resource
You’ve added a translation key microimage.camera.permission.denied in the locale bundle, but there’s no corresponding <string> in strings.xml. Please add an entry, for example:

+    <string name="microimage_camera_permission_denied">
+        Go to settings and enable camera permission to use this feature
+    </string>

This ensures the denial message can be resolved at runtime.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<string name="camera_permission_title">Permission for camera</string>
<string name="camera_permission_msg">In order to take a picture, CommCare needs permission to use your device camera.</string>
<string name="camera_permission_title">Permission for camera</string>
<string name="camera_permission_msg">In order to take a picture, CommCare needs permission to use your device camera.</string>
+ <string name="microimage_camera_permission_denied">
+ Go to settings and enable camera permission to use this feature
+ </string>
🤖 Prompt for AI Agents
In app/res/values/strings.xml around lines 935 to 936, you need to add a missing
string resource for the key microimage.camera.permission.denied. Add a new
<string> entry with this name and a suitable denial message to ensure the app
can resolve this string at runtime without errors.

🛠️ Refactor suggestion

Consolidate camera permission strings
The newly added camera_permission_title and camera_permission_msg are functionally identical to the existing permission.camera.title and permission.camera.message resources elsewhere in this file. To avoid duplication and ensure consistency, please reuse the existing permission keys in MicroImageActivity (or remove the duplicates).

🤖 Prompt for AI Agents
In app/res/values/strings.xml around lines 935 to 936, the new strings
camera_permission_title and camera_permission_msg duplicate existing keys
permission.camera.title and permission.camera.message. Remove these duplicate
strings and update MicroImageActivity to reference the existing
permission.camera.title and permission.camera.message keys to maintain
consistency and avoid redundancy.

Copy link
Contributor

@OrangeAndGreen OrangeAndGreen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All looks good so far, but as discussed today we should add user storage for the required unlock level (specified by the server) and enforce it during unlocks after successful device configuration.

new ActivityResultContracts.RequestPermission(),
isGranted -> {
if (isGranted) {
startCamera();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code formatting is off here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

getString(R.string.camera_permission_msg));
dialog.showNonPersistentDialog(this);
} else {
requestNeededPermissions(-1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we take constant value for this -1 looks odd

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1 as not used anymore due to permission / activity launcher

if(allowOtherOptions){
builder.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL |
BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.BIOMETRIC_WEAK);
}else{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code formatting is off and secondly BiometricManager.Authenticators.BIOMETRIC_WEAK is not required because it allows face unlock also and i dont think we need that BIOMETRIC_STRONG is enough

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code format done
When Biometric from server, app can have face or fingerprint so we can leave upto Android OS to decide.

Jignesh-dimagi added 2 commits June 4, 2025 11:52
Copy link
Contributor

@shubham1g5 shubham1g5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me and nice job figuring out the db migrations, one suggestion for future work - it's generally better to not have composite PRs like this touching multiple things. This I think should have been 2 separate PRs atleast - one for camera permission change and another for biometric changes.

private boolean isDemo;

@Persisting(value = 15)
private String required_lock = PersonalIdSessionData.PIN;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use camel case here - requiredLock

builder.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL |
BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.BIOMETRIC_WEAK);
} else {
builder.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.BIOMETRIC_WEAK);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:code formattting seems off

@Jignesh-dimagi
Copy link
Contributor Author

@shubham1g5 Completely agree for single PR for each feature/bug but this time had created a common ticket so I was stuck. In future, separate ticket -> separate PR. Thanks for pointing out.

@Jignesh-dimagi Jignesh-dimagi requested a review from shubham1g5 June 4, 2025 11:47
shubham1g5
shubham1g5 previously approved these changes Jun 4, 2025
OrangeAndGreen
OrangeAndGreen previously approved these changes Jun 4, 2025
@shubham1g5 shubham1g5 dismissed stale reviews from OrangeAndGreen and themself via ea8a36e June 4, 2025 13:33
@Jignesh-dimagi Jignesh-dimagi requested a review from shubham1g5 June 4, 2025 13:36
@shubham1g5 shubham1g5 merged commit 04645aa into master Jun 4, 2025
5 of 8 checks passed
@shubham1g5 shubham1g5 deleted the jignesh/ccct-1304 branch June 4, 2025 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-integration-tests Skip android tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants