feat(interop): one exported door for the whole AlarmClock contract
`AlarmClockActivity` is the only exported surface that takes a platform intent, guarded by the caller-side `SET_ALARM` permission — which cannot go on `MainActivity`, because the system checks it against the Launcher too. It is invisible, does its work, and finishes. Two intent-filters, not one: a filter carrying a `<data>` element never matches an intent without data, so the deeplink actions need a filter of their own. `IntentExtrasReader` turns the `Bundle` into a plain map and guards the unparcel, because an exported door is reachable by anything on the device and a hostile Parcelable must not crash the launch.
This commit is contained in:
@@ -71,6 +71,57 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- The one exported door for android.provider.AlarmClock: every other
|
||||
app on the device drives Clockula through it. Guarded by the
|
||||
caller's own SET_ALARM permission, which AlarmClock's documentation
|
||||
asks for and which is protection-level `normal`, so a legitimate
|
||||
caller is granted it at install and a drive-by startActivity from an
|
||||
app that never declared an interest in alarms is kept out. The
|
||||
permission is on *this* activity and not on MainActivity, where it
|
||||
would also be checked against the Launcher (M9 D2, D3).
|
||||
|
||||
Two intent-filters, deliberately: a filter with a <data> element
|
||||
never matches an intent without data, and one without never matches
|
||||
an intent with it — and DISMISS_ALARM/DISMISS_TIMER may arrive
|
||||
either way. android.intent.category.VOICE is *not* declared: it
|
||||
advertises the VoiceInteractor follow-on flows, which v1 does not
|
||||
implement.
|
||||
|
||||
No window: translucent, out of recents, no history, no task of its
|
||||
own. The children carry explicit closing tags rather than being
|
||||
self-closed, so the whole declaration reads as one block — which is
|
||||
how ManifestRulesTest reads it too.
|
||||
|
||||
M9 requests no permission of its own: the one named below is a
|
||||
requirement on the *caller*, never something Clockula asks for. -->
|
||||
<activity
|
||||
android:name=".interop.AlarmClockActivity"
|
||||
android:exported="true"
|
||||
android:permission="com.android.alarm.permission.SET_ALARM"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/Theme.Clockula.Invisible">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SET_ALARM"></action>
|
||||
<action android:name="android.intent.action.SET_TIMER"></action>
|
||||
<action android:name="android.intent.action.SHOW_ALARMS"></action>
|
||||
<action android:name="android.intent.action.SHOW_TIMERS"></action>
|
||||
<action android:name="android.intent.action.DISMISS_ALARM"></action>
|
||||
<action android:name="android.intent.action.DISMISS_TIMER"></action>
|
||||
<action android:name="android.intent.action.SNOOZE_ALARM"></action>
|
||||
<category android:name="android.intent.category.DEFAULT"></category>
|
||||
</intent-filter>
|
||||
<!-- The deeplink form: clockula://alarm/{id} and clockula://timer/{id},
|
||||
which the contract lets a caller carry as the intent's data. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.DISMISS_ALARM"></action>
|
||||
<action android:name="android.intent.action.DISMISS_TIMER"></action>
|
||||
<category android:name="android.intent.category.DEFAULT"></category>
|
||||
<data android:scheme="clockula"></data>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Standalone crash-report surface; MainActivity routes here on a
|
||||
startup crash-loop. Not exported, kept out of recents. -->
|
||||
<activity
|
||||
|
||||
Reference in New Issue
Block a user