RepoPilotOpen in app →

googlesamples/easypermissions

Simplify Android M system permissions

Healthy

Healthy across all four use cases

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • 15 active contributors
  • Apache-2.0 licensed
  • CI configured
Show all 6 evidence items →
  • Tests present
  • Stale — last commit 3y ago
  • Concentrated ownership — top contributor handles 56% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/googlesamples/easypermissions)](https://repopilot.app/r/googlesamples/easypermissions)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/googlesamples/easypermissions on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: googlesamples/easypermissions

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/googlesamples/easypermissions shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • 15 active contributors
  • Apache-2.0 licensed
  • CI configured
  • Tests present
  • ⚠ Stale — last commit 3y ago
  • ⚠ Concentrated ownership — top contributor handles 56% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live googlesamples/easypermissions repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/googlesamples/easypermissions.

What it runs against: a local clone of googlesamples/easypermissions — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in googlesamples/easypermissions | Confirms the artifact applies here, not a fork | | 2 | License is still Apache-2.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 1041 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>googlesamples/easypermissions</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of googlesamples/easypermissions. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/googlesamples/easypermissions.git
#   cd easypermissions
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of googlesamples/easypermissions and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "googlesamples/easypermissions(\\.git)?\\b" \\
  && ok "origin remote is googlesamples/easypermissions" \\
  || miss "origin remote is not googlesamples/easypermissions (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is Apache-2.0" \\
  || miss "license drift — was Apache-2.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java" \\
  && ok "easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java" \\
  || miss "missing critical file: easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java"
test -f "easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java" \\
  && ok "easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java" \\
  || miss "missing critical file: easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java"
test -f "easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java" \\
  && ok "easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java" \\
  || miss "missing critical file: easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java"
test -f "easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java" \\
  && ok "easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java" \\
  || miss "missing critical file: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java"
test -f "easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java" \\
  && ok "easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java" \\
  || miss "missing critical file: easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 1041 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~1011d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/googlesamples/easypermissions"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

EasyPermissions is a lightweight Android wrapper library that simplifies runtime permission handling for Android M (API 23+) and higher. It eliminates boilerplate code for requesting, checking, and handling system permission grants by providing clean Java APIs and an @AfterPermissionGranted annotation to automatically invoke methods once permissions are granted. Two-module structure: easypermissions/ is the core library module (located at easypermissions/src/main/java/pub/devrel/easypermissions/) containing EasyPermissions.java, AfterPermissionGranted.java, AppSettingsDialog.java, and support activity. app/ is a sample Android application demonstrating usage with MainActivity.java and MainFragment.java.

👥Who it's for

Android developers (particularly those using AndroidX in Activities and Fragments) who need to implement runtime permissions for features like camera, location, or file access without writing repetitive permission-checking and callback-handling code.

🌱Maturity & risk

Production-ready and actively maintained. The library is published to Maven Central, has reached v3.0.0 with AndroidX support, includes CI/CD via GitHub Actions (.github/workflows/test.yml), and is owned by Google Samples. The codebase is stable and widely used in production Android apps.

Low risk. The library has a minimal dependency footprint (only AndroidX core, appcompat, and annotation libraries), a clear, audited codebase of ~126KB, and regular maintenance. No single-maintainer risk due to Google backing. Main consideration: it only wraps the native Android permissions API, so it's bound to Android framework limitations.

Active areas of work

Active maintenance with CI tests running on push (via .github/workflows/test.yml). The project maintains compatibility with current Android API levels (compileSdkVersion 30 visible in build config). No specific open PRs or major features are evident, suggesting stability-focused maintenance.

🚀Get running

git clone https://github.com/googlesamples/easypermissions.git
cd easypermissions
./gradlew build
./gradlew installDebug  # or run via Android Studio

Daily commands: Open the project in Android Studio, sync Gradle, then run the app module on an emulator or device. Alternatively: ./gradlew :app:assembleDebug && ./gradlew :app:installDebug.

🗺️Map of the codebase

  • easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java — Core public API entry point that implements the permission request logic, rationale dialogs, and result handling—all contributors must understand the main permission flow here.
  • easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java — Abstract base interface defining the permission helper contract that all platform-specific helpers implement—critical for understanding the strategy pattern used throughout.
  • easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java — Data model that encapsulates a permission request with permissions, request code, and rationale—essential for understanding how requests are constructed and passed through the library.
  • easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java — Modern dialog fragment implementation for displaying rationale explanations before requesting permissions—critical for the UX flow and androidx compatibility.
  • easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java — Activity-level permission helper that handles direct Activity-based permission requests—foundational for understanding how helpers differ by component type.
  • easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java — Dialog that navigates users to app settings when permissions are permanently denied—essential for the recovery/fallback flow.
  • easypermissions/easypermissions/build.gradle — Library module build configuration with AndroidX and support library versions—critical for dependency management and version compatibility.

🛠️How to make changes

Add support for a new Android component type (e.g., Fragment subclass)

  1. Create a new helper class extending PermissionHelper or BaseSupportPermissionsHelper (easypermissions/src/main/java/pub/devrel/easypermissions/helper/NewComponentPermissionHelper.java)
  2. Implement requestPermissions() to call your component's requestPermissions() method (easypermissions/src/main/java/pub/devrel/easypermissions/helper/NewComponentPermissionHelper.java)
  3. Add factory logic to PermissionHelper.newInstance() to detect and instantiate your helper (easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java)
  4. Add unit tests in the testhelper package to verify onRequestPermissionsResult() is correctly dispatched (easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestNewComponent.java)

Customize rationale dialog appearance (text, buttons, theme)

  1. Create a RationaleDialogConfig object with custom message, positive/negative button text, and theme resource (easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogConfig.java)
  2. Pass the config to EasyPermissions.requestPermissions() via PermissionRequest.setRationaleDialogConfig() (easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java)
  3. The RationaleDialogFragmentCompat or RationaleDialogFragment will apply the config when rendering (easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java)

Handle permanently denied permissions (app settings flow)

  1. After permission denial, check if permission is permanently denied using EasyPermissions.somePermissionPermanentlyDenied() (easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java)
  2. Create and show AppSettingsDialog with a custom rationale message (easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java)
  3. The dialog will have an 'Open Settings' button that launches Settings via intent with FLAG_ACTIVITY_NEW_TASK (easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java)

Add a callback that runs after specific permissions are granted

  1. Annotate a method in your Activity or Fragment with @AfterPermissionGranted and assign a requestCode (easypermissions/src/main/java/pub/devrel/easypermissions/AfterPermissionGranted.java)
  2. Call EasyPermissions.onRequestPermissionsResult() in your activity/fragment's onRequestPermissionsResult() (easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java)
  3. EasyPermissions uses reflection to find and invoke your annotated method if all permissions for that requestCode were granted (easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java)

🪤Traps & gotchas

  • Annotation processing via reflection requires methods annotated with @AfterPermissionGranted to be void and parameterless; attempting to use instance state requires manual save/restore via onSaveInstanceState().
  • The library only handles runtime permissions (Android M+); apps targeting minSdkVersion < 23 must still handle manifest-only permissions separately.
  • AppSettingsDialog.java uses AppSettingsDialogHolderActivity as a holder; this hidden activity must be declared in your manifest (check that the library's manifest is properly merged).
  • Denying a permission with 'don't ask again' requires manual guidance to Settings; there is no programmatic override.

🏗️Architecture

💡Concepts to learn

  • Android M Runtime Permissions Model — EasyPermissions wraps the Android M (API 23) permission system; understanding the distinction between manifest permissions and runtime permission requests is core to using this library
  • Annotation Processing & Reflection — The @AfterPermissionGranted annotation uses reflection at runtime to discover and invoke matching methods; understanding this pattern helps debug and extend the library
  • Activity Result Contract Pattern — EasyPermissions abstracts the legacy onRequestPermissionsResult() callback model; modern Android uses ActivityResultContracts, and understanding the evolution is useful for maintenance
  • Permission Denial Rationale UI — The library includes AppSettingsDialog to guide users to app settings after denial with 'don't ask again'; understanding Android's permission lifecycle (denied vs. permanently denied) is essential
  • Fragment Lifecycle & Permission Callbacks — EasyPermissions supports both Activities and Fragments; fragments have distinct lifecycle and permission callback handling that must be properly coordinated

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add unit tests for PermissionHelper interface implementations

The easypermissions/src/test directory exists but appears empty. The helper classes (ActivityPermissionHelper, AppCompatActivityPermissionsHelper, SupportFragmentPermissionHelper, LowApiPermissionsHelper) lack test coverage. These are critical permission-handling components that should have comprehensive unit tests covering edge cases like denied permissions, permission groups, and API level variations.

  • [ ] Create test classes in easypermissions/src/test/java/pub/devrel/easypermissions/helper/ for each PermissionHelper implementation
  • [ ] Add tests for shouldShowRequestPermissionRationale() behavior across API levels
  • [ ] Add tests for requestPermissions() with various permission combinations
  • [ ] Add tests for hasPermissions() validation logic
  • [ ] Run tests with testOptions.unitTests.includeAndroidResources enabled to validate AndroidManifest behavior

Add GitHub Actions workflow for Android instrumentation tests

The .github/workflows/test.yml exists but there's no visible instrumentation test workflow. The sample app (app/src/main/java/pub/devrel/easypermissions/sample/) contains MainActivity and MainFragment which should have integration tests verifying the permission request flow end-to-end on actual Android versions.

  • [ ] Create .github/workflows/instrumentation-tests.yml that runs on API 30 (matching compileSdkVersion)
  • [ ] Configure Firebase Test Lab or Android Emulator with appropriate API levels (14, 23, 30+) to verify backward compatibility
  • [ ] Add test steps for sample app permission flows: requesting CAMERA, LOCATION, and multi-permission scenarios
  • [ ] Ensure tests validate RationaleDialogFragment and AppSettingsDialog behaviors

Add integration tests for RationaleDialogFragment and AppSettingsDialog

RationaleDialogFragment, RationaleDialogFragmentCompat, and AppSettingsDialog are UI components critical to the permission flow but lack visible test coverage. These dialog components need tests to verify proper initialization, configuration handling, and click listener behavior with different RationaleDialogConfig scenarios.

  • [ ] Create easypermissions/src/test/java/pub/devrel/easypermissions/RationaleDialogFragmentTest.java
  • [ ] Add tests for RationaleDialogConfig properties (title, message, positiveButton, negativeButton, cancelable)
  • [ ] Create AppSettingsDialogTest.java to verify dialog launch behavior and permission checking
  • [ ] Test both FragmentCompat and regular Fragment versions with different AndroidX/Support Library scenarios
  • [ ] Add FragmentScenario or FragmentFactory tests for proper fragment lifecycle handling

🌿Good first issues

  • Add unit tests for EasyPermissions.hasPermissions() with edge cases (empty array, duplicate permissions, null checks) in a new test file under easypermissions/src/test/
  • Document the annotation processing pipeline with inline code comments in EasyPermissions.java explaining how @AfterPermissionGranted methods are discovered and invoked via reflection
  • Create a Kotlin extension library or sample demonstrating idiomatic Kotlin usage (e.g., suspend functions or Flow-based callbacks) as a companion to the existing Java API

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 2b2b5c4 — Move to GitHub Actions (#326) (samtstern)
  • 1d8c6e0 — Add the @IntRange annotation to requestCode to prevent invalid input (#325) (tall-nuts)
  • 8952eac — Move from JCenter to Maven Central (#323) (samtstern)
  • 5da4f24 — Add link to easypermissions-ktx (samtstern)
  • bf3cd48 — Changed getContext() to requireContext(), to ensure it is not null. (#309) (seventhmoon)
  • 4f4d305 — Updating module 'app' to Java 8 syntax (#308) (seventhmoon)
  • eba11f4 — Updating project to support Android Studio 4.0 (#307) (seventhmoon)
  • 43e63a5 — Put in defense for issue 278 (#290) (samtstern)
  • 2d46e44 — Fix typo in README (#287) (mikebayles)
  • 67d611b — Migrate to AndroidX (#272) (samtstern)

🔒Security observations

The EasyPermissions library codebase has a moderate security posture with several concerns primarily related to outdated dependencies and configuration. The most critical issues are: (1) significantly outdated AndroidX dependencies with known vulnerabilities, (2) deprecated Android SDK versions not meeting Google Play requirements, and (3) excessive legacy API support (minSdkVersion 14). The codebase itself appears to be a permissions wrapper library without obvious code-level vulnerabilities like injection risks or hardcoded secrets. However, the dependency and build configuration issues could expose applications using this library to known vulnerabilities. Immediate action should focus on updating AndroidX dependencies and modernizing the target/compile SDK versions.

  • High · Outdated AndroidX Dependencies — app/build.gradle (lines with androidx dependencies). The build.gradle file specifies androidx.appcompat:appcompat:1.1.0 and androidx.annotation:annotation:1.1.0, which are significantly outdated. These versions were released in 2019 and contain known security vulnerabilities that have been patched in newer releases. Modern versions provide critical security fixes for UI rendering, data handling, and framework vulnerabilities. Fix: Update to the latest stable versions of androidx dependencies. Use androidx.appcompat:appcompat:1.6.x or higher and androidx.annotation:annotation:1.7.x or higher. Run 'gradle dependencyUpdates' to identify all outdated dependencies.
  • Medium · Proguard Configuration Exposure Risk — app/proguard-rules.pro and easypermissions/proguard-rules.pro. The ProGuard configuration files (app/proguard-rules.pro and easypermissions/proguard-rules.pro) exist in the repository. If these files contain keep rules that are too permissive, they could expose sensitive class names, method signatures, or prevent important security-related obfuscation. Fix: Review ProGuard keep rules to ensure they only preserve what is necessary for functionality. Avoid using wildcards like '-keep class * { *; }'. Ensure sensitive permission-related classes are properly obfuscated while maintaining required API surfaces.
  • Medium · Deprecated Compile SDK Version — app/build.gradle (compileSdkVersion 30). The project uses compileSdkVersion 30, which is outdated. As of 2024, Google Play requires apps to target API 34 or higher. Using outdated SDKs misses critical security patches, privacy improvements, and security-related API changes in newer Android versions. Fix: Update compileSdkVersion to 34 or higher, and update targetSdkVersion accordingly. Ensure compatibility testing is performed after upgrading.
  • Medium · Low Target SDK Version — app/build.gradle (targetSdkVersion 30). The targetSdkVersion is set to 30, which is outdated. Google Play requires targetSdkVersion 34 or higher for new apps, and existing apps must comply with this requirement. Lower target SDK versions miss important permission model changes, runtime permission enhancements, and security hardening introduced in newer Android versions. Fix: Update targetSdkVersion to 34 or higher. Test the application thoroughly against the new Android version's behaviors and permission models.
  • Low · Missing Gradle Wrapper Verification — gradle/wrapper/ directory and gradle/wrapper/gradle-wrapper.properties. The Gradle wrapper is present but there is no verification mechanism (gradle-wrapper.properties SHA-256 checksum or wrapper-validation-action) visible. This could allow potential Man-in-the-Middle attacks or tampering with the gradle wrapper jar. Fix: Implement Gradle wrapper checksum validation using GitHub Actions 'gradle/wrapper-validation-action' or add SHA-256 verification in the build process. Document expected wrapper checksums in the project.
  • Low · MinSdkVersion 14 is Extremely Legacy — app/build.gradle (minSdkVersion 14). The minSdkVersion is set to 14 (Android 4.0, released 2011). Supporting such ancient API levels introduces significant security risks as those devices never receive security updates and lack basic security features available in modern Android. Fix: Increase minSdkVersion to at least 21 (Android 5.0) or higher. This aligns with industry standards and reduces the attack surface. Most devices in use today are on API 24 or higher.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · googlesamples/easypermissions — RepoPilot