1use crate::backend::tr;
21use serde_json::{Value, json};
22use sioul_core::i18n::Translator;
23
24const GUARDED: i64 = 33;
26const GUARDED_TEXTS: i64 = 35;
28
29#[derive(Debug, Clone, Default, PartialEq, Eq)]
31pub(crate) struct Said {
32 pub api: i64,
34 pub texts_on: bool,
36 pub texts_could: bool,
37 pub texts_given: bool,
38 pub asked: bool,
41 pub at_once: bool,
42 pub notifications_needed: bool,
44 pub notifications_given: bool,
45 pub contacts_needed: bool,
47 pub contacts_given: bool,
48}
49
50pub(crate) fn lines(s: &Said, tr: &Translator, join: bool) -> Vec<Value> {
54 let mut out = Vec::new();
55 if join && !s.texts_on && s.texts_could {
56 out.push(json!({ "what": "texts-part", "said": tr.text("access-texts-off", None), "info": false, "switch": true }));
57 }
58 if s.texts_on && !s.texts_given {
59 let (words, info) = if s.asked && s.api >= GUARDED_TEXTS {
60 ("access-texts-guarded", true)
61 } else if s.at_once {
62 ("access-texts-at-once", true)
63 } else {
64 ("access-texts", false)
65 };
66 out.push(json!({ "what": "texts", "said": tr.text(words, None), "info": info, "switch": false }));
67 }
68 if s.notifications_needed && !s.notifications_given {
69 let guarded = s.api >= GUARDED;
70 out.push(json!({ "what": "notifications", "said": tr.text(if guarded { "access-notifications-guarded" } else { "access-notifications" }, None), "info": guarded, "switch": false }));
71 }
72 if s.contacts_needed && !s.contacts_given {
73 out.push(json!({ "what": "contacts", "said": tr.text("access-contacts", None), "info": false, "switch": false }));
74 }
75 out
76}
77
78fn said_here() -> Option<Said> {
80 if !cfg!(target_os = "android") {
81 return None;
82 }
83 let state = crate::steps::java("texts-state", "{}");
84 let phone_messages = crate::share::shares(sioul_core::phonemsgs::PART);
85 Some(Said {
86 api: state["api"].as_i64().unwrap_or(0),
87 texts_on: crate::share::shares(sioul_core::texts::PART),
88 texts_could: state["sms_app"].as_str().is_some_and(|app| !app.is_empty()),
89 texts_given: state["read"] == true && state["receive"] == true && state["send"] == true,
90 asked: state["asked"] == true,
91 at_once: state["at_once"] == true,
92 notifications_needed: sioul_core::appnotes::Choices::load(&sioul_core::appnotes::Choices::default_path()).hold || phone_messages,
93 notifications_given: crate::appnotes::access_here() != Some(false),
94 contacts_needed: crate::calls::screens_here() || phone_messages,
95 contacts_given: crate::steps::java("contacts-allowed", "{}") != Value::Bool(false),
96 })
97}
98
99pub(crate) fn withheld(join: bool) -> Vec<Value> {
101 said_here().map(|s| lines(&s, tr(), join)).unwrap_or_default()
102}
103
104pub(crate) fn call(verb: &str, asked: &Value) -> Value {
111 match verb {
112 "access-ask" => match asked["what"].as_str().unwrap_or_default() {
113 "texts" => {
114 crate::steps::java("texts-ask", "{}");
115 }
116 "notifications" => {
117 crate::appnotes::change("open-access", "{}");
118 }
119 "contacts" => crate::steps::ask_contacts(),
120 _ => {}
121 },
122 "access-info" => {
123 crate::steps::java("texts-info", "{}");
124 }
125 _ => {}
126 }
127 json!({ "lines": withheld(asked["join"] == true) })
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
142 fn what_android_withholds_is_said_with_what_it_wants() {
143 let tr = Translator::new("en");
144 let all = Said { api: 36, texts_on: true, texts_could: true, texts_given: true, asked: true, at_once: false, notifications_needed: true, notifications_given: true, contacts_needed: true, contacts_given: true };
145 assert!(lines(&all, &tr, true).is_empty(), "all given: nothing said");
146 let whats = |s: &Said, join: bool| lines(s, &tr, join).iter().map(|l| (l["what"].as_str().unwrap().to_string(), l["info"] == true)).collect::<Vec<_>>();
147 let refused = Said { texts_given: false, notifications_given: false, contacts_given: false, ..all.clone() };
149 assert_eq!(whats(&refused, false), [("texts".to_string(), true), ("notifications".into(), true), ("contacts".into(), false)]);
150 let said = lines(&refused, &tr, false);
151 let texts = said[0]["said"].as_str().unwrap();
152 let (once, info, again) = (texts.find("press Allow in Android… once").unwrap(), texts.find("Allow restricted settings").unwrap(), texts.find("Allow in Android… again").unwrap());
153 assert!(once < info && info < again, "in their order: {texts}");
154 assert!(said[1]["said"].as_str().unwrap().contains("tap Sioul once"), "{said:?}");
155 assert_eq!(whats(&Said { texts_given: false, asked: false, ..all.clone() }, false), [("texts".to_string(), false)]);
157 assert_eq!(whats(&Said { api: 31, texts_given: false, at_once: true, ..all.clone() }, false), [("texts".to_string(), true)]);
159 assert_eq!(whats(&Said { api: 31, texts_given: false, ..all.clone() }, false), [("texts".to_string(), false)]);
160 assert_eq!(whats(&Said { api: 31, notifications_given: false, ..all.clone() }, false), [("notifications".to_string(), false)]);
161 assert_eq!(whats(&Said { api: 34, texts_given: false, notifications_given: false, ..all.clone() }, false), [("texts".to_string(), false), ("notifications".into(), true)]);
163 assert_eq!(whats(&Said { api: 33, texts_given: false, at_once: true, ..all.clone() }, false), [("texts".to_string(), true)]);
164 assert!(!lines(&Said { api: 34, texts_given: false, ..all.clone() }, &tr, false)[0]["said"].as_str().unwrap().contains("restricted settings"), "on 14, never the restricted settings' steps for the texts");
165 let off = Said { texts_on: false, texts_given: false, ..all.clone() };
167 assert!(whats(&off, false).is_empty());
168 assert_eq!(lines(&off, &tr, true)[0]["switch"], true);
169 assert!(whats(&Said { texts_could: false, ..off.clone() }, true).is_empty(), "a phone with no texts to read: nothing");
170 assert!(whats(&Said { notifications_needed: false, notifications_given: false, contacts_needed: false, contacts_given: false, ..all.clone() }, false).is_empty());
172 for language in ["en", "fr"] {
173 let tr = Translator::new(language);
174 for line in lines(&refused, &tr, false).into_iter().chain(lines(&off, &tr, true)).chain(lines(&Said { api: 31, texts_given: false, at_once: true, ..all.clone() }, &tr, false)) {
175 assert!(!line["said"].as_str().unwrap().starts_with("access-"), "{language}: {line}");
176 }
177 }
178 }
179}