-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwithRestrictedBootReceivers.js
More file actions
98 lines (88 loc) · 3.63 KB
/
Copy pathwithRestrictedBootReceivers.js
File metadata and controls
98 lines (88 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
const { withAndroidManifest, AndroidConfig } = require('expo/config-plugins');
const TOOLS_NAMESPACE = 'http://schemas.android.com/tools';
/**
* Android 15 (targetSdk 35+) forbids launching restricted foreground service types
* (dataSync, camera, mediaPlayback, phoneCall, mediaProjection, microphone,
* specialUse, systemExempted) from a BOOT_COMPLETED broadcast receiver. Doing so
* throws ForegroundServiceStartNotAllowedException and crashes the app.
*
* This app declares several of those types (notifee's ForegroundService is
* microphone|connectedDevice, CallKeep's VoiceConnectionService is phoneCall,
* react-native-webrtc contributes mediaProjection), and expo-notifications registers a receiver for
* BOOT_COMPLETED that can reach `startForegroundService`:
*
* - expo.modules.notifications.service.NotificationsService — on boot it re-arms
* scheduled local notifications.
*
* That boot path is not needed here: the app never schedules local notifications — all
* notifications are push-delivered.
*
* Library manifests are merged in by Gradle, so the boot actions cannot be edited
* directly. Instead we re-declare the receiver in the app manifest with
* tools:node="replace", which makes the manifest merger take OUR element — attributes
* and intent-filters — verbatim in place of the library's.
*
* NotificationsService MUST stay declared: it is resolved with queryBroadcastReceivers()
* on the expo.modules.notifications.NOTIFICATION_EVENT action — dropping that filter
* would kill ALL notification delivery, so it is preserved here.
*
* MY_PACKAGE_REPLACED is kept: the Android 15 restriction is specific to BOOT_COMPLETED.
*/
const RECEIVER_OVERRIDES = [
{
name: 'expo.modules.notifications.service.NotificationsService',
attributes: {
'android:enabled': 'true',
'android:exported': 'false',
},
intentFilters: [
{
attributes: { 'android:priority': '-1' },
actions: ['expo.modules.notifications.NOTIFICATION_EVENT', 'android.intent.action.MY_PACKAGE_REPLACED'],
},
],
},
];
const buildReceiver = (override) => ({
$: {
'android:name': override.name,
...override.attributes,
'tools:node': 'replace',
},
'intent-filter': override.intentFilters.map((filter) => ({
$: { ...filter.attributes },
action: filter.actions.map((action) => ({ $: { 'android:name': action } })),
})),
});
/**
* Pure manifest transform, exported for tests.
*
* @param {object} androidManifest parsed AndroidManifest.xml (xml2js shape)
* @returns {object} the same manifest, mutated
*/
const applyBootReceiverOverrides = (androidManifest) => {
if (!androidManifest.manifest.$['xmlns:tools']) {
androidManifest.manifest.$['xmlns:tools'] = TOOLS_NAMESPACE;
}
const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(androidManifest);
const receivers = mainApplication.receiver ?? [];
RECEIVER_OVERRIDES.forEach((override) => {
const replacement = buildReceiver(override);
const existingIndex = receivers.findIndex((receiver) => receiver.$?.['android:name'] === override.name);
if (existingIndex >= 0) {
receivers[existingIndex] = replacement;
} else {
receivers.push(replacement);
}
});
mainApplication.receiver = receivers;
return androidManifest;
};
const withRestrictedBootReceivers = (config) =>
withAndroidManifest(config, (config) => {
config.modResults = applyBootReceiverOverrides(config.modResults);
return config;
});
module.exports = withRestrictedBootReceivers;
module.exports.applyBootReceiverOverrides = applyBootReceiverOverrides;
module.exports.RECEIVER_OVERRIDES = RECEIVER_OVERRIDES;