Skip to content

Commit 181a7b7

Browse files
authored
Merge pull request #1477 from vector-im/feature/bma/elementCallUrlParam
Element call url param
2 parents 5b3d340 + 566f096 commit 181a7b7

File tree

2 files changed

+147
-80
lines changed

2 files changed

+147
-80
lines changed

features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,50 @@ class CallIntentDataParser @Inject constructor() {
5454
}
5555

5656
/**
57-
* Ensure the uri has the following parameters and value:
57+
* Ensure the uri has the following parameters and value in the fragment:
5858
* - appPrompt=false
5959
* - confineToRoom=true
6060
* to ensure that the rendering will bo correct on the embedded Webview.
6161
*/
6262
private fun Uri.withCustomParameters(): String {
6363
val builder = buildUpon()
64+
// Remove the existing query parameters
6465
builder.clearQuery()
6566
queryParameterNames.forEach {
6667
if (it == APP_PROMPT_PARAMETER || it == CONFINE_TO_ROOM_PARAMETER) return@forEach
6768
builder.appendQueryParameter(it, getQueryParameter(it))
6869
}
69-
builder.appendQueryParameter(APP_PROMPT_PARAMETER, "false")
70-
builder.appendQueryParameter(CONFINE_TO_ROOM_PARAMETER, "true")
71-
return builder.build().toString()
70+
// Remove the existing fragment parameters, and build the new fragment
71+
val currentFragment = fragment ?: ""
72+
// Reset the current fragment
73+
builder.fragment("")
74+
val queryFragmentPosition = currentFragment.lastIndexOf("?")
75+
val newFragment = if (queryFragmentPosition == -1) {
76+
// No existing query, build it.
77+
"$currentFragment?$APP_PROMPT_PARAMETER=false&$CONFINE_TO_ROOM_PARAMETER=true"
78+
} else {
79+
buildString {
80+
append(currentFragment.substring(0, queryFragmentPosition + 1))
81+
val queryFragment = currentFragment.substring(queryFragmentPosition + 1)
82+
// Replace the existing parameters
83+
val newQueryFragment = queryFragment
84+
.replace("$APP_PROMPT_PARAMETER=true", "$APP_PROMPT_PARAMETER=false")
85+
.replace("$CONFINE_TO_ROOM_PARAMETER=false", "$CONFINE_TO_ROOM_PARAMETER=true")
86+
append(newQueryFragment)
87+
// Ensure the parameters are there
88+
if (!newQueryFragment.contains("$APP_PROMPT_PARAMETER=false")) {
89+
if (newQueryFragment.isNotEmpty()) {
90+
append("&")
91+
}
92+
append("$APP_PROMPT_PARAMETER=false")
93+
}
94+
if (!newQueryFragment.contains("$CONFINE_TO_ROOM_PARAMETER=true")) {
95+
append("&$CONFINE_TO_ROOM_PARAMETER=true")
96+
}
97+
}
98+
}
99+
// We do not want to encode the Fragment part, so append it manually
100+
return builder.build().toString() + "#" + newFragment
72101
}
73102

74103
private const val APP_PROMPT_PARAMETER = "appPrompt"

features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt

Lines changed: 114 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -35,150 +35,188 @@ class CallIntentDataParserTests {
3535

3636
@Test
3737
fun `empty data returns null`() {
38-
val url = ""
39-
assertThat(callIntentDataParser.parse(url)).isNull()
38+
doTest("", null)
4039
}
4140

4241
@Test
4342
fun `invalid data returns null`() {
44-
val url = "!"
45-
assertThat(callIntentDataParser.parse(url)).isNull()
43+
doTest("!", null)
4644
}
4745

4846
@Test
4947
fun `data with no scheme returns null`() {
50-
val url = "test"
51-
assertThat(callIntentDataParser.parse(url)).isNull()
48+
doTest("test", null)
5249
}
5350

5451
@Test
5552
fun `Element Call http urls returns null`() {
56-
val httpBaseUrl = "http://call.element.io"
57-
val httpCallUrl = "http://call.element.io/some-actual-call?with=parameters"
58-
assertThat(callIntentDataParser.parse(httpBaseUrl)).isNull()
59-
assertThat(callIntentDataParser.parse(httpCallUrl)).isNull()
53+
doTest("http://call.element.io", null)
54+
doTest("http://call.element.io/some-actual-call?with=parameters", null)
6055
}
6156

6257
@Test
6358
fun `Element Call urls will be returned as is`() {
64-
val httpsBaseUrl = "https://call.element.io"
65-
val httpsCallUrl = VALID_CALL_URL_WITH_PARAM
66-
assertThat(callIntentDataParser.parse(httpsBaseUrl)).isEqualTo("$httpsBaseUrl?$EXTRA_PARAMS")
67-
assertThat(callIntentDataParser.parse(httpsCallUrl)).isEqualTo("$httpsCallUrl&$EXTRA_PARAMS")
59+
doTest(
60+
url = "https://call.element.io",
61+
expectedResult = "https://call.element.io#?$EXTRA_PARAMS"
62+
)
6863
}
6964

7065
@Test
71-
fun `HTTP and HTTPS urls that don't come from EC return null`() {
72-
val httpBaseUrl = "http://app.element.io"
73-
val httpsBaseUrl = "https://app.element.io"
74-
val httpInvalidUrl = "http://"
75-
val httpsInvalidUrl = "http://"
76-
assertThat(callIntentDataParser.parse(httpBaseUrl)).isNull()
77-
assertThat(callIntentDataParser.parse(httpsBaseUrl)).isNull()
78-
assertThat(callIntentDataParser.parse(httpInvalidUrl)).isNull()
79-
assertThat(callIntentDataParser.parse(httpsInvalidUrl)).isNull()
66+
fun `Element Call url with url param gets url extracted`() {
67+
doTest(
68+
url = VALID_CALL_URL_WITH_PARAM,
69+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
70+
)
8071
}
8172

8273
@Test
83-
fun `element scheme with call host and url with http will returns null`() {
84-
val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters"
85-
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
86-
val url = "element://call?url=$encodedUrl"
87-
assertThat(callIntentDataParser.parse(url)).isNull()
74+
fun `HTTP and HTTPS urls that don't come from EC return null`() {
75+
doTest("http://app.element.io", null)
76+
doTest("https://app.element.io", null, testEmbedded = false)
77+
doTest("http://", null)
78+
doTest("https://", null)
8879
}
8980

9081
@Test
91-
fun `element scheme with call host and url param gets url extracted`() {
82+
fun `Element Call url with no url returns null`() {
9283
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
9384
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
94-
val url = "element://call?url=$encodedUrl"
95-
assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS")
85+
val url = "io.element.call:/?no_url=$encodedUrl"
86+
assertThat(callIntentDataParser.parse(url)).isNull()
9687
}
9788

9889
@Test
99-
fun `element scheme 2 with url param with http returns null`() {
100-
val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters"
90+
fun `element scheme with no call host returns null`() {
91+
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
10192
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
102-
val url = "io.element.call:/?url=$encodedUrl"
93+
val url = "element://no-call?url=$encodedUrl"
10394
assertThat(callIntentDataParser.parse(url)).isNull()
10495
}
10596

10697
@Test
107-
fun `element scheme 2 with url param gets url extracted`() {
108-
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
109-
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
110-
val url = "io.element.call:/?url=$encodedUrl"
111-
assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS")
98+
fun `element scheme with no data returns null`() {
99+
val url = "element://call?url="
100+
assertThat(callIntentDataParser.parse(url)).isNull()
112101
}
113102

114103
@Test
115-
fun `element scheme with call host and no url param returns null`() {
116-
val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters"
117-
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
118-
val url = "element://call?no-url=$encodedUrl"
104+
fun `Element Call url with no data returns null`() {
105+
val url = "io.element.call:/?url="
119106
assertThat(callIntentDataParser.parse(url)).isNull()
120107
}
121108

122109
@Test
123-
fun `element scheme 2 with no url returns null`() {
110+
fun `element invalid scheme returns null`() {
124111
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
125112
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
126-
val url = "io.element.call:/?no_url=$encodedUrl"
113+
val url = "bad.scheme:/?url=$encodedUrl"
127114
assertThat(callIntentDataParser.parse(url)).isNull()
128115
}
129116

130117
@Test
131-
fun `element scheme with no call host returns null`() {
132-
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
133-
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
134-
val url = "element://no-call?url=$encodedUrl"
135-
assertThat(callIntentDataParser.parse(url)).isNull()
118+
fun `Element Call url with url extra param appPrompt gets url extracted`() {
119+
doTest(
120+
url = "${VALID_CALL_URL_WITH_PARAM}&appPrompt=true",
121+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
122+
)
136123
}
137124

138125
@Test
139-
fun `element scheme with no data returns null`() {
140-
val url = "element://call?url="
141-
assertThat(callIntentDataParser.parse(url)).isNull()
126+
fun `Element Call url with url extra param in fragment appPrompt gets url extracted`() {
127+
doTest(
128+
url = "${VALID_CALL_URL_WITH_PARAM}#?appPrompt=true",
129+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&confineToRoom=true"
130+
)
142131
}
143132

144133
@Test
145-
fun `element scheme 2 with no data returns null`() {
146-
val url = "io.element.call:/?url="
147-
assertThat(callIntentDataParser.parse(url)).isNull()
134+
fun `Element Call url with url extra param in fragment appPrompt and other gets url extracted`() {
135+
doTest(
136+
url = "${VALID_CALL_URL_WITH_PARAM}#?appPrompt=true&otherParam=maybe",
137+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&otherParam=maybe&confineToRoom=true"
138+
)
148139
}
149140

150141
@Test
151-
fun `element invalid scheme returns null`() {
152-
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
153-
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
154-
val url = "bad.scheme:/?url=$encodedUrl"
155-
assertThat(callIntentDataParser.parse(url)).isNull()
142+
fun `Element Call url with url extra param confineToRoom gets url extracted`() {
143+
doTest(
144+
url = "${VALID_CALL_URL_WITH_PARAM}&confineToRoom=false",
145+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
146+
)
156147
}
157148

158149
@Test
159-
fun `element scheme 2 with url extra param appPrompt gets url extracted`() {
160-
val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}&appPrompt=true"
161-
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
162-
val url = "io.element.call:/?url=$encodedUrl"
163-
assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS")
150+
fun `Element Call url with url extra param in fragment confineToRoom gets url extracted`() {
151+
doTest(
152+
url = "${VALID_CALL_URL_WITH_PARAM}#?confineToRoom=false",
153+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&appPrompt=false"
154+
)
164155
}
165156

166157
@Test
167-
fun `element scheme 2 with url extra param confineToRoom gets url extracted`() {
168-
val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}&confineToRoom=false"
169-
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
170-
val url = "io.element.call:/?url=$encodedUrl"
171-
assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS")
158+
fun `Element Call url with url extra param in fragment confineToRoom and more gets url extracted`() {
159+
doTest(
160+
url = "${VALID_CALL_URL_WITH_PARAM}#?confineToRoom=false&otherParam=maybe",
161+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&otherParam=maybe&appPrompt=false"
162+
)
172163
}
173164

174165
@Test
175-
fun `element scheme 2 with url fragment gets url extracted`() {
176-
val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}#fragment"
177-
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
178-
val url = "io.element.call:/?url=$encodedUrl"
179-
assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS#fragment")
166+
fun `Element Call url with url fragment gets url extracted`() {
167+
doTest(
168+
url = "${VALID_CALL_URL_WITH_PARAM}#fragment",
169+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?$EXTRA_PARAMS"
170+
)
180171
}
181172

173+
@Test
174+
fun `Element Call url with url fragment with params gets url extracted`() {
175+
doTest(
176+
url = "${VALID_CALL_URL_WITH_PARAM}#fragment?otherParam=maybe",
177+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe&$EXTRA_PARAMS"
178+
)
179+
}
180+
181+
@Test
182+
fun `Element Call url with url fragment with other params gets url extracted`() {
183+
doTest(
184+
url = "${VALID_CALL_URL_WITH_PARAM}#?otherParam=maybe",
185+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe&$EXTRA_PARAMS"
186+
)
187+
}
188+
189+
@Test
190+
fun `Element Call url with empty fragment`() {
191+
doTest(
192+
url = "${VALID_CALL_URL_WITH_PARAM}#",
193+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
194+
)
195+
}
196+
197+
@Test
198+
fun `Element Call url with empty fragment query`() {
199+
doTest(
200+
url = "${VALID_CALL_URL_WITH_PARAM}#?",
201+
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
202+
)
203+
}
204+
205+
private fun doTest(url: String, expectedResult: String?, testEmbedded: Boolean = true) {
206+
// Test direct parsing
207+
assertThat(callIntentDataParser.parse(url)).isEqualTo(expectedResult)
208+
209+
if (testEmbedded) {
210+
// Test embedded url, scheme 1
211+
val encodedUrl = URLEncoder.encode(url, "utf-8")
212+
val urlScheme1 = "element://call?url=$encodedUrl"
213+
assertThat(callIntentDataParser.parse(urlScheme1)).isEqualTo(expectedResult)
214+
215+
// Test embedded url, scheme 2
216+
val urlScheme2 = "io.element.call:/?url=$encodedUrl"
217+
assertThat(callIntentDataParser.parse(urlScheme2)).isEqualTo(expectedResult)
218+
}
219+
}
182220

183221
companion object {
184222
const val VALID_CALL_URL_WITH_PARAM = "https://call.element.io/some-actual-call?with=parameters"

0 commit comments

Comments
 (0)