Table of Contents

    Book an Appointment

    How Did We Discover the Need to Deprecate READ_CONTACTS in Our Enterprise App?

    While working on a major SDK upgrade for a secure healthcare communication platform, our engineering team encountered a common but frustrating mobile architecture challenge. The application relies heavily on email-based messaging, allowing hospital staff to broadcast critical updates to multiple recipients. Historically, the app requested the READ_CONTACTS permission, fetched all email addresses, and populated a highly optimized, custom multi-select auto-complete dialog.

    During our preparation to target the latest Android API levels, we realized we needed to address Google Play’s increasingly strict policies regarding sensitive permissions. The new guidelines strongly advise removing broad data access permissions like READ_CONTACTS in favor of permissionless, system-provided intent pickers. However, while modern Android versions offer streamlined ways to access single contact data points without broad permissions, these features are not fully backported. We encountered a situation where complying with modern security guidelines severely degraded the user experience on older devices. This architectural friction inspired this article to help teams looking to hire android developers for enterprise mobility navigate the complexities of backward compatibility.

    Why Does Backward Compatibility Make Removing READ_CONTACTS So Difficult?

    The core business requirement was simple: users need to select multiple email recipients quickly and accurately. In our legacy architecture, having full read access to the local contacts database meant we could aggregate, filter, and display contacts instantly. Users could type a few letters, see a filtered list of only contacts with valid email addresses, and check multiple boxes to compose a group message.

    When you remove the READ_CONTACTS permission, you lose the ability to build this custom, in-memory data layer. The platform expects you to delegate the selection process to the OS using system intents. In an ideal world, the operating system would provide a unified, permissionless UI that perfectly matches the app’s needs. However, the reality of Android ecosystem fragmentation—especially when supporting down to Android 10 (API 29)—means that delegating to the OS introduces significant behavioral inconsistencies across different device manufacturers.

    What Broke When We Tried to Remove Android Contact Permissions Across All OS Versions?

    To test a fully permissionless approach, we stripped READ_CONTACTS from the Manifest and implemented Intent.ACTION_PICK targeted at the Contacts content provider. The symptoms of this architectural shift surfaced immediately during QA testing on devices running Android 10 through 13.

    • Loss of Multi-Selection: The standard system contact picker on older Android versions natively struggles with multi-selection for specific data kinds (like emails). Users were forced to open the picker, select one email, return to the app, and repeat the process for every recipient.
    • Poor Filtering Capabilities: Depending on the OEM (Original Equipment Manufacturer), the system picker did not reliably filter out contacts lacking email addresses. Users had to scroll through phone-only contacts, leading to high friction.
    • Ambiguous Data Returns: When a user selected a contact with multiple email addresses (e.g., work and personal), the legacy ACTION_PICK flow often returned a generic contact URI, requiring additional complex queries—which sometimes failed without the broad read permission—to resolve the exact email the user intended to pick.

    How Did We Evaluate Solutions for Permissionless Contact Selection?

    We knew that simply ripping out the custom auto-complete UI was unacceptable for our enterprise users. To find the right balance between Play Store compliance, security, and user experience, we evaluated several architectural paths.

    Did We Consider Keeping the Permission and Delaying the SDK Upgrade?

    One immediate thought was to cap our target SDK at API 35 and maintain the legacy behavior indefinitely. We considered keeping the READ_CONTACTS permission and justifying it in the Play Console. However, relying on exceptions is a temporary band-aid. Delaying SDK upgrades leads to technical debt, prevents the adoption of new performance features, and ultimately risks app suspension if policy enforcement tightens. When organizations look to hire software developer teams, they expect forward-looking solutions, not legacy life-support.

    Could We Rely Solely on System Intents Like ACTION_PICK?

    We explored aggressive customization of ACTION_PICK using Intent.EXTRA_ALLOW_MULTIPLE and specific MIME type filters. While this worked reasonably well on standard Pixel devices running Android 14+, it failed spectacularly on heavily modified OEM skins running Android 10. The lack of standard UI enforcement across older devices meant we could not guarantee a stable business flow.

    What About Building a Custom Backend Contact Sync?

    We also analyzed the possibility of shifting contact management entirely to our backend, bypassing the device’s local address book. However, this introduced massive data privacy implications, HIPAA compliance overhead, and required users to manually upload their contacts to our cloud—a complete non-starter for user adoption.

    How Did We Implement a Hybrid Contact Picker Architecture Across Android 10 to 15?

    Our final implementation embraced a hybrid strategy. We realized we could not force modern permissionless paradigms onto legacy OS versions without breaking the UX. Instead, we used the maxSdkVersion attribute on the permission declaration to retain our highly optimized legacy flow on older devices, while fully adopting the permissionless, secure flow on modern Android versions.

    Here is how we structured the solution:

    <!-- In AndroidManifest.xml -->
    <!-- Retain permission ONLY for older devices where system pickers lack required UX -->
    <uses-permission 
        android:name="android.permission.READ_CONTACTS" 
        android:maxSdkVersion="33" />
    

    At the code level, we implemented a Strategy pattern to route the user to the correct UI experience based on the OS version.

    public interface ContactSelectionStrategy {
        void launchContactSelector(Activity activity, int requestCode);
        List<String> processResult(Intent data);
    }
    // For Android 14+ (API 34+)
    public class PermissionlessSystemPickerStrategy implements ContactSelectionStrategy {
        @Override
        public void launchContactSelector(Activity activity, int requestCode) {
            Intent intent = new Intent(Intent.ACTION_PICK);
            intent.setType(ContactsContract.CommonDataKinds.Email.CONTENT_TYPE);
            // Modern OS handles email-specific picking without app-level permissions
            activity.startActivityForResult(intent, requestCode);
        }
        
        @Override
        public List<String> processResult(Intent data) {
            // Extract temporary URI permissions granted by the OS
            return extractEmailsFromUri(data.getData());
        }
    }
    // For Android 10-13 (API 29-33)
    public class LegacyAutoCompleteStrategy implements ContactSelectionStrategy {
        @Override
        public void launchContactSelector(Activity activity, int requestCode) {
            // Verify READ_CONTACTS is granted, then launch our custom multi-select UI
            if (hasReadContactsPermission(activity)) {
                activity.startActivityForResult(new Intent(activity, CustomMultiSelectActivity.class), requestCode);
            } else {
                requestReadContactsPermission(activity);
            }
        }
        
        @Override
        public List<String> processResult(Intent data) {
            // Return emails selected from our custom UI
            return data.getStringArrayListExtra("SELECTED_EMAILS");
        }
    }
    

    This approach allowed us to fully comply with Google’s directive to remove broad permissions on modern devices while preserving the critical multi-select UX on older devices. As users naturally upgrade their hardware over the next few years, the legacy path will organically phase out.

    What Can Engineering Teams Learn from Handling Android Permission Deprecations?

    Managing OS-level deprecations requires more than just updating an API call; it demands architectural foresight. Here are the key lessons engineering teams should apply:

    • Leverage Conditional Permissions: Use android:maxSdkVersion to gracefully sunset broad permissions. This proves to app stores that you are minimizing data access on modern devices while supporting legacy constraints.
    • Abstract OS Dependencies: Never hardcode intent launches directly into your UI layer. By abstracting the contact picking mechanism behind an interface, we seamlessly swapped implementations based on the runtime environment.
    • Test Across OEM Skins: A feature that works flawlessly on an emulator or a stock Android device might fail completely on a custom manufacturer OS. Always validate intent-based workflows across diverse hardware, especially on older API levels.
    • Accept UX Degradation as a Security Trade-off: On modern devices, shifting to permissionless APIs might mean losing custom UI branding. Communicate this trade-off to product stakeholders early. Security and compliance must eventually override bespoke UI preferences.
    • Plan for Ecosystem Evolution: If you are looking to hire app developer to create a mobile app, ensure they understand how to architect for future OS restrictions. Building tightly coupled dependencies on global system states (like the entire contact database) is an anti-pattern in modern mobile engineering.

    How Can You Modernize Your Mobile App Architecture Effectively?

    Balancing backward compatibility with modern security requirements is a hallmark of mature mobile engineering. By utilizing a hybrid architectural approach, we successfully navigated Google’s READ_CONTACTS deprecation without alienating our existing user base on older devices. Embracing OS-level delegation on modern devices not only ensures compliance but also builds user trust by minimizing unnecessary data access.

    If your organization is struggling with technical debt, complex SDK upgrades, or migrating legacy applications to modern security standards, contact us to explore how our dedicated engineering teams can help future-proof your mobile architecture.

    Social Hashtags

    #AndroidDevelopment #READCONTACTS #AndroidSecurity #AndroidPermissions #MobileAppDevelopment #AndroidDevelopers #AppSecurity #EnterpriseMobility #AndroidArchitecture #SoftwareDevelopment #MobileSecurity #AndroidDev

     

    Frequently Asked Questions