@mentra/engine 3.2.0-dev.180 → 3.2.0-dev.187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/build/engine.d.ts +8 -0
- package/build/engine.d.ts.map +1 -1
- package/build/facades/pairing.d.ts +3 -1
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +9 -0
- package/build/facades/pairing.js.map +1 -1
- package/build/facades/phoneNotifications.d.ts +9 -0
- package/build/facades/phoneNotifications.d.ts.map +1 -1
- package/build/facades/phoneNotifications.js +15 -0
- package/build/facades/phoneNotifications.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/NativeNotificationPolicy.d.ts +13 -0
- package/build/services/NativeNotificationPolicy.d.ts.map +1 -0
- package/build/services/NativeNotificationPolicy.js +12 -0
- package/build/services/NativeNotificationPolicy.js.map +1 -0
- package/build/services/PhoneNotificationsSync.d.ts +13 -0
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +102 -26
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +30 -0
- package/build/stores/settings.js.map +1 -1
- package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
- package/build/types/capabilities/even-realities-g2.js +1 -0
- package/build/types/capabilities/even-realities-g2.js.map +1 -1
- package/build/types/hardware.d.ts +2 -0
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js.map +1 -1
- package/package.json +7 -7
- package/src/facades/pairing.ts +16 -1
- package/src/facades/phoneNotifications.ts +21 -0
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/services/NativeNotificationPolicy.ts +24 -0
- package/src/services/PhoneNotificationsSync.ts +115 -31
- package/src/stores/settings.ts +34 -7
- package/src/types/capabilities/even-realities-g2.ts +1 -0
- package/src/types/hardware.ts +2 -0
|
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "3.2.0",
|
|
15
|
-
"releaseIdentity": "3.2.0-dev.
|
|
16
|
-
"releaseSetId": "mentra-3.2.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
18
|
-
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.
|
|
19
|
-
"otaManifestSha256": "
|
|
15
|
+
"releaseIdentity": "3.2.0-dev.187",
|
|
16
|
+
"releaseSetId": "mentra-3.2.0-dev.187",
|
|
17
|
+
"sourceCommit": "d8b34892f11593baa3c075b94380253b06eb874d",
|
|
18
|
+
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.187.json",
|
|
19
|
+
"otaManifestSha256": "cf21f4ab6347acb12eefa38d2f0a3b433bc0e05428c527bcda57a8693d7a72f9"
|
|
20
20
|
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type {NativeNotificationConfig} from "@mentra/bluetooth-sdk"
|
|
2
|
+
|
|
3
|
+
export interface NotificationPolicyInput {
|
|
4
|
+
supported: boolean
|
|
5
|
+
selected: boolean
|
|
6
|
+
presentationActive: boolean
|
|
7
|
+
sourceEnabled: boolean
|
|
8
|
+
autoDisplay: boolean
|
|
9
|
+
durationSeconds: number
|
|
10
|
+
doNotDisturb: boolean
|
|
11
|
+
blockedApps: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function nativeNotificationConfig(input: NotificationPolicyInput): NativeNotificationConfig {
|
|
15
|
+
return {
|
|
16
|
+
enabled: input.supported && input.selected && input.presentationActive && input.sourceEnabled,
|
|
17
|
+
autoDisplay: input.autoDisplay,
|
|
18
|
+
durationSeconds: Number.isFinite(input.durationSeconds)
|
|
19
|
+
? Math.min(30, Math.max(1, Math.round(input.durationSeconds)))
|
|
20
|
+
: 5,
|
|
21
|
+
doNotDisturb: input.doNotDisturb,
|
|
22
|
+
blockedApps: input.blockedApps,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,46 +1,130 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* operational kill switch + config (`android_notification_listener_enabled`
|
|
4
|
-
* and `notifications_blocklist`) to the native
|
|
5
|
-
* NotificationListener via `CrustModule.setNotificationConfig`, so
|
|
6
|
-
* the blocklist reaches the listener for ANY host — not just the Mentra App,
|
|
7
|
-
* where this used to live in MantleManager. The Android service is desired by
|
|
8
|
-
* default now that its cold start is isolated from React Native. Native code
|
|
9
|
-
* keeps the component discoverable in Android's notification-access Settings,
|
|
10
|
-
* but only binds the service after access is granted. The debug setting remains
|
|
11
|
-
* a hidden emergency kill switch; Notify's running state gates presentation,
|
|
12
|
-
* not capture. Android-only; a no-op elsewhere. Started by `engine.start()`.
|
|
13
|
-
*/
|
|
1
|
+
/** One engine-owned notification policy: capture remains independent of presentation. */
|
|
2
|
+
import {Platform} from "react-native"
|
|
14
3
|
import {shallow} from "zustand/shallow"
|
|
15
4
|
import CrustModule from "@mentra/crust"
|
|
5
|
+
import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
|
|
6
|
+
import type {PhoneNotificationEvent} from "@mentra/crust"
|
|
16
7
|
|
|
17
8
|
import {useSettingsStore, SETTINGS} from "../stores/settings"
|
|
9
|
+
import {useGlassesStore, isGlassesReady} from "../stores/glasses"
|
|
10
|
+
import {DeviceTypes} from "../types/enums"
|
|
11
|
+
import {getModelCapabilities} from "../types/hardware"
|
|
12
|
+
import {nativeNotificationConfig} from "./NativeNotificationPolicy"
|
|
18
13
|
|
|
19
|
-
let
|
|
14
|
+
let unsubscribers: Array<() => void> = []
|
|
15
|
+
let presentationActive = false
|
|
20
16
|
|
|
21
|
-
function
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
export function nativeNotificationCapabilities() {
|
|
18
|
+
const supported = Boolean(
|
|
19
|
+
getModelCapabilities(useGlassesStore.getState().deviceModel as DeviceTypes)?.hasNativeNotifications,
|
|
20
|
+
)
|
|
21
|
+
return {
|
|
22
|
+
supported,
|
|
23
|
+
source: Platform.OS === "ios" ? ("ancs" as const) : ("phone" as const),
|
|
24
|
+
contentAccess:
|
|
25
|
+
Platform.OS === "android" ? ("full" as const) : supported ? ("app_identity" as const) : ("unsupported" as const),
|
|
26
|
+
perAppFiltering: Platform.OS === "android",
|
|
27
|
+
removal: false, // The verified G2 protocol does not yet include a removal command.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function desiredConfig() {
|
|
32
|
+
const settings = useSettingsStore.getState()
|
|
33
|
+
return nativeNotificationConfig({
|
|
34
|
+
supported: nativeNotificationCapabilities().supported,
|
|
35
|
+
selected: Boolean(settings.getSetting(SETTINGS.native_notifications_enabled.key)),
|
|
36
|
+
presentationActive,
|
|
37
|
+
sourceEnabled:
|
|
38
|
+
Platform.OS !== "android" || Boolean(settings.getSetting(SETTINGS.android_notification_listener_enabled.key)),
|
|
39
|
+
autoDisplay: Boolean(settings.getSetting(SETTINGS.native_notifications_auto_display.key)),
|
|
40
|
+
durationSeconds: Number(settings.getSetting(SETTINGS.native_notifications_duration.key)),
|
|
41
|
+
doNotDisturb: Boolean(settings.getSetting(SETTINGS.native_notifications_do_not_disturb.key)),
|
|
42
|
+
blockedApps: Platform.OS === "android" ? settings.getSetting(SETTINGS.notifications_blocklist.key) : [],
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function pushNativeConfig(): void {
|
|
47
|
+
if (!nativeNotificationCapabilities().supported) return
|
|
48
|
+
void BluetoothSdk.configureNativeNotifications(desiredConfig()).catch((error: unknown) => {
|
|
49
|
+
if (isGlassesReady(useGlassesStore.getState().connection)) {
|
|
50
|
+
console.warn("PhoneNotificationsSync: native notification configuration failed", error)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Hosts activate their presentation owner (Notify in the Mentra App) through this seam. */
|
|
56
|
+
export function setPhoneNotificationPresentationActive(active: boolean): void {
|
|
57
|
+
if (active === presentationActive) return
|
|
58
|
+
presentationActive = active
|
|
59
|
+
pushNativeConfig()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function usesNativeNotificationPresentation(): boolean {
|
|
63
|
+
return (
|
|
64
|
+
nativeNotificationCapabilities().supported &&
|
|
65
|
+
presentationActive &&
|
|
66
|
+
Boolean(useSettingsStore.getState().getSetting(SETTINGS.native_notifications_enabled.key))
|
|
27
67
|
)
|
|
28
68
|
}
|
|
29
69
|
|
|
70
|
+
/** Return true when the selected firmware presentation owns this event, including quiet/history mode. */
|
|
71
|
+
export async function presentNativePhoneNotification(event: PhoneNotificationEvent): Promise<boolean> {
|
|
72
|
+
if (!usesNativeNotificationPresentation()) return false
|
|
73
|
+
if (!desiredConfig().enabled) return true
|
|
74
|
+
if (Platform.OS !== "android") return true // G2 receives ANCS directly; never upload metadata back.
|
|
75
|
+
const blocked = useSettingsStore.getState().getSetting(SETTINGS.notifications_blocklist.key)
|
|
76
|
+
if (Array.isArray(blocked) && blocked.includes(event.packageName)) return true
|
|
77
|
+
if (!isGlassesReady(useGlassesStore.getState().connection)) return true
|
|
78
|
+
await BluetoothSdk.sendPhoneNotification({
|
|
79
|
+
notificationId: String(event.notificationId ?? ""),
|
|
80
|
+
packageName: String(event.packageName ?? ""),
|
|
81
|
+
appName: String(event.app ?? ""),
|
|
82
|
+
title: String(event.title ?? ""),
|
|
83
|
+
subtitle: "",
|
|
84
|
+
body: String(event.content ?? ""),
|
|
85
|
+
timestampMs: Number(event.timestamp ?? Date.now()),
|
|
86
|
+
action: 0,
|
|
87
|
+
})
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function pushConfig(): void {
|
|
92
|
+
const settings = useSettingsStore.getState()
|
|
93
|
+
if (Platform.OS === "android") {
|
|
94
|
+
const enabled = Boolean(settings.getSetting(SETTINGS.android_notification_listener_enabled.key))
|
|
95
|
+
const blocklist = settings.getSetting(SETTINGS.notifications_blocklist.key)
|
|
96
|
+
void CrustModule.setNotificationConfig(enabled, Array.isArray(blocklist) ? blocklist : []).catch((error: unknown) =>
|
|
97
|
+
console.warn("PhoneNotificationsSync: listener configuration failed", error),
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
pushNativeConfig()
|
|
101
|
+
}
|
|
102
|
+
|
|
30
103
|
export function startPhoneNotificationsSync(): void {
|
|
31
|
-
if (
|
|
32
|
-
pushConfig()
|
|
33
|
-
|
|
34
|
-
(
|
|
35
|
-
|
|
36
|
-
|
|
104
|
+
if (unsubscribers.length) return
|
|
105
|
+
pushConfig()
|
|
106
|
+
unsubscribers = [
|
|
107
|
+
useSettingsStore.subscribe(
|
|
108
|
+
(state) =>
|
|
109
|
+
[
|
|
110
|
+
SETTINGS.android_notification_listener_enabled,
|
|
111
|
+
SETTINGS.notifications_blocklist,
|
|
112
|
+
SETTINGS.native_notifications_enabled,
|
|
113
|
+
SETTINGS.native_notifications_auto_display,
|
|
114
|
+
SETTINGS.native_notifications_duration,
|
|
115
|
+
SETTINGS.native_notifications_do_not_disturb,
|
|
116
|
+
].map((setting) => state.getSetting(setting.key)),
|
|
117
|
+
pushConfig,
|
|
118
|
+
{equalityFn: shallow},
|
|
119
|
+
),
|
|
120
|
+
useGlassesStore.subscribe((state) => ({model: state.deviceModel, connection: state.connection}), pushNativeConfig, {
|
|
121
|
+
equalityFn: shallow,
|
|
37
122
|
}),
|
|
38
|
-
|
|
39
|
-
{equalityFn: shallow},
|
|
40
|
-
)
|
|
123
|
+
]
|
|
41
124
|
}
|
|
42
125
|
|
|
43
126
|
export function stopPhoneNotificationsSync(): void {
|
|
44
|
-
|
|
45
|
-
unsubscribe
|
|
127
|
+
setPhoneNotificationPresentationActive(false)
|
|
128
|
+
unsubscribers.forEach((unsubscribe) => unsubscribe())
|
|
129
|
+
unsubscribers = []
|
|
46
130
|
}
|
package/src/stores/settings.ts
CHANGED
|
@@ -166,6 +166,36 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
166
166
|
saveOnServer: false,
|
|
167
167
|
persist: true,
|
|
168
168
|
},
|
|
169
|
+
// Select firmware-owned presentation instead of the Mentra card. Notify still
|
|
170
|
+
// controls whether presentation is running on either phone platform.
|
|
171
|
+
native_notifications_enabled: {
|
|
172
|
+
key: "native_notifications_enabled",
|
|
173
|
+
defaultValue: () => false,
|
|
174
|
+
writable: true,
|
|
175
|
+
saveOnServer: false,
|
|
176
|
+
persist: true,
|
|
177
|
+
},
|
|
178
|
+
native_notifications_auto_display: {
|
|
179
|
+
key: "native_notifications_auto_display",
|
|
180
|
+
defaultValue: () => true,
|
|
181
|
+
writable: true,
|
|
182
|
+
saveOnServer: false,
|
|
183
|
+
persist: true,
|
|
184
|
+
},
|
|
185
|
+
native_notifications_duration: {
|
|
186
|
+
key: "native_notifications_duration",
|
|
187
|
+
defaultValue: () => 5,
|
|
188
|
+
writable: true,
|
|
189
|
+
saveOnServer: false,
|
|
190
|
+
persist: true,
|
|
191
|
+
},
|
|
192
|
+
native_notifications_do_not_disturb: {
|
|
193
|
+
key: "native_notifications_do_not_disturb",
|
|
194
|
+
defaultValue: () => false,
|
|
195
|
+
writable: true,
|
|
196
|
+
saveOnServer: false,
|
|
197
|
+
persist: true,
|
|
198
|
+
},
|
|
169
199
|
china_deployment: {
|
|
170
200
|
key: "china_deployment",
|
|
171
201
|
defaultValue: () => (process.env.EXPO_PUBLIC_DEPLOYMENT_REGION === "china" ? true : false),
|
|
@@ -793,13 +823,10 @@ export interface SettingsState {
|
|
|
793
823
|
}
|
|
794
824
|
|
|
795
825
|
const getDefaultSettings = () =>
|
|
796
|
-
Object.keys(SETTINGS).reduce(
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
},
|
|
801
|
-
{} as Record<string, any>,
|
|
802
|
-
)
|
|
826
|
+
Object.keys(SETTINGS).reduce((acc, key) => {
|
|
827
|
+
acc[key] = SETTINGS[key].defaultValue()
|
|
828
|
+
return acc
|
|
829
|
+
}, {} as Record<string, any>)
|
|
803
830
|
|
|
804
831
|
// Single-flight for loadAllSettings: the host fires it at module load and
|
|
805
832
|
// engine.start()'s device-store hydration awaits it — without the memo the
|
|
@@ -88,4 +88,5 @@ export const evenRealitiesG2: Capabilities = {
|
|
|
88
88
|
// Dashboard - G2 renders Even Realities' native dashboard in firmware, so
|
|
89
89
|
// MentraOS does not manage the dashboard or expose dashboard settings for it
|
|
90
90
|
hasNativeDashboard: true,
|
|
91
|
+
hasNativeNotifications: true,
|
|
91
92
|
};
|
package/src/types/hardware.ts
CHANGED
|
@@ -122,6 +122,8 @@ export interface PowerCapabilities {
|
|
|
122
122
|
* Complete information about what hardware a device has
|
|
123
123
|
*/
|
|
124
124
|
export interface Capabilities {
|
|
125
|
+
/** Firmware-owned notification history and popups; content relay is separate. */
|
|
126
|
+
hasNativeNotifications?: boolean;
|
|
125
127
|
modelName: string;
|
|
126
128
|
|
|
127
129
|
// Camera capabilities
|