Skip to content
All posts
5 min read

Detect Missed Calls Without READ_CALL_LOG: What 3 Play Store Rejections Taught Me

Amit Raz

Amit Raz

Founder, RZ AI Labs

Three identical rejections from Google Play, one denied appeal, and then a rebuild that got approved within an hour. The whole story ended with a single line added to the instructions of every project I run, and that line is the actual point of this post.

The original version was a tweet in Hebrew.

The feature: a task for every missed call

I wanted to add a small feature to my task app, Sticky Tasks: when you miss a call, the app creates a task that says "call X back" with a dial button on it. Miss a call while driving, get a task that nags you until you return it. Simple, useful, done in an afternoon.

I talked it through with Claude, and we went with the obvious route: READ_CALL_LOG. Read the call log, see who called, create the task. It is the technically correct API for exactly this data.

Why READ_CALL_LOG gets your app rejected

READ_CALL_LOG is a restricted permission. To ship it you file a declaration against a closed list of approved use case categories that Google maintains. I filed the declaration, and when Google asked for a video demonstrating the feature, I sent that too.

Three rejections, identical word for word. None of them said the one thing that mattered. Only the appeal, which was also denied, finally quoted the category definition and made it clear: a task and reminder app is simply not on the list. It would never have passed, no matter how much I polished the listing or the video.

At that point Claude suggested we revert the code and move on. I was not ready to give up on the feature, so I asked a different question: is there another way to get the same result?

The route that worked: NotificationListenerService

A few ping-pongs later we had it. When you miss a call, the dialer itself posts a notification with the caller's name. A NotificationListenerService can read that notification. No restricted permission, no declaration form, no closed category list.

Same result, minus one edge case: calls you actively decline do not always post a missed call notification. I can live with that. The rebuild took under an hour, the update was approved within an hour, and the feature is live in version 2.7.1.

Almost the same feature, none of the problems. The only difference is which door I walked in through.

The line I added to every project's instructions

The expensive part was not the code. The rebuild was an hour. The expensive part was the weeks of declaration forms, review cycles, and a denied appeal for a route that was dead on arrival, while a policy-free route to the same feature sat there the whole time.

So this went into the standing instructions of every project I run:

Before proposing or implementing a feature, lay out every route to the
result, including the platform policy and review risk of each route,
and only then choose. Rank routes by rejection risk, not by ease of
implementation.

Coding agents default to the technically obvious API because it is the cleanest path to the data. Platform policy is not part of their instinct, and it will not enter the conversation unless your instructions force the comparison before any code is written. This rule lives next to the rest of my standing rules, the ones I described in OPERATING_RULES.md, and once a route is chosen, the reasons go into a decisions.md file so nobody quietly reverts to the "correct" API later.

The comparison would have taken an hour. Skipping it cost weeks.

What this means beyond Android

This is not really an Android story. Every platform with a review gate has a version of it: App Store entitlements, Chrome extension permissions, OAuth scopes, payment provider rules. The cheapest route to build is often the one that fails review, and the route that survives review is rarely the one the model proposes first.

Feature planning that starts with "what are all the doors, and which ones actually open" is a habit I now bring into every app project I build for clients, because the second most expensive thing in software is building the right feature through the wrong door.

FAQ

Can a to-do or reminder app get the READ_CALL_LOG permission on Google Play?

In my experience, no. READ_CALL_LOG is a restricted permission with a closed list of approved use case categories, and a task or reminder app does not fit any of them. No amount of listing polish or demo videos changes that: my three rejections were identical, and the denied appeal finally quoted the category definition to make it clear it would never pass.

How do you detect missed calls on Android without READ_CALL_LOG?

Use a NotificationListenerService. When a call is missed, the dialer itself posts a notification that includes the caller's name, and your app can read that notification instead of the call log. No restricted permission and no declaration form. The one edge case you lose is calls the user actively declines, since those may not post a missed call notification.

Building something with AI?

I help teams ship custom agents, AI strategy, and software that works.