summaryrefslogtreecommitdiffstats
path: root/mobile/android/android-components/components/support
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/android-components/components/support')
-rw-r--r--mobile/android/android-components/components/support/base/build.gradle1
-rw-r--r--mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/BuildVersionProvider.kt31
-rw-r--r--mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/PowerManagerInfoProvider.kt39
-rw-r--r--mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/ProcessInfoProvider.kt32
-rw-r--r--mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/StartForegroundService.kt41
-rw-r--r--mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/UserInteractionHandler.kt7
-rw-r--r--mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapper.kt18
-rw-r--r--mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/android/StartForegroundServiceTest.kt98
-rw-r--r--mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapperTest.kt50
-rw-r--r--mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/view/View.kt5
-rw-r--r--mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt20
-rw-r--r--mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt88
-rw-r--r--mobile/android/android-components/components/support/webextensions/src/main/java/mozilla/components/support/webextensions/WebExtensionSupport.kt4
-rw-r--r--mobile/android/android-components/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt19
14 files changed, 433 insertions, 20 deletions
diff --git a/mobile/android/android-components/components/support/base/build.gradle b/mobile/android/android-components/components/support/base/build.gradle
index 76918b4c21..bc7bffa7e7 100644
--- a/mobile/android/android-components/components/support/base/build.gradle
+++ b/mobile/android/android-components/components/support/base/build.gradle
@@ -74,6 +74,7 @@ android {
dependencies {
implementation ComponentsDependencies.kotlin_coroutines
+ implementation ComponentsDependencies.androidx_core_ktx
implementation ComponentsDependencies.androidx_lifecycle_viewmodel
api project(":concept-base")
diff --git a/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/BuildVersionProvider.kt b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/BuildVersionProvider.kt
new file mode 100644
index 0000000000..406e851049
--- /dev/null
+++ b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/BuildVersionProvider.kt
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.support.base.android
+
+import android.os.Build
+
+/**
+ * This class provides information about the build version without exposing the android framework
+ * APIs directly, making it easier to test the code that depends on it.
+ */
+interface BuildVersionProvider {
+
+ /**
+ * Returns the SDK_INT of the current build version.
+ */
+ fun sdkInt(): Int
+
+ companion object {
+ const val FOREGROUND_SERVICE_RESTRICTIONS_STARTING_VERSION = Build.VERSION_CODES.S
+ }
+}
+
+/**
+ * @see BuildVersionProvider
+ */
+class DefaultBuildVersionProvider : BuildVersionProvider {
+
+ override fun sdkInt(): Int = Build.VERSION.SDK_INT
+}
diff --git a/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/PowerManagerInfoProvider.kt b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/PowerManagerInfoProvider.kt
new file mode 100644
index 0000000000..0d8a78efb3
--- /dev/null
+++ b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/PowerManagerInfoProvider.kt
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.support.base.android
+
+import android.content.Context
+import android.os.Build
+import android.os.PowerManager
+import androidx.core.content.ContextCompat
+
+/**
+ * This class provides information about battery optimisations without exposing the android
+ * framework APIs directly, making it easier to test the code that depends on it.
+ */
+interface PowerManagerInfoProvider {
+
+ /**
+ * Returns true if the user has disabled battery optimisations for the app.
+ */
+ fun isIgnoringBatteryOptimizations(): Boolean
+}
+
+/**
+ * @see PowerManagerInfoProvider
+ */
+class DefaultPowerManagerInfoProvider(private val context: Context) : PowerManagerInfoProvider {
+
+ private val powerManager by lazy {
+ ContextCompat.getSystemService(context, PowerManager::class.java)
+ }
+
+ override fun isIgnoringBatteryOptimizations(): Boolean =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ powerManager?.isIgnoringBatteryOptimizations(context.packageName) ?: false
+ } else {
+ true
+ }
+}
diff --git a/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/ProcessInfoProvider.kt b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/ProcessInfoProvider.kt
new file mode 100644
index 0000000000..2890b8c4c4
--- /dev/null
+++ b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/ProcessInfoProvider.kt
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.support.base.android
+
+import android.app.ActivityManager
+
+/**
+ * This class provides information the running app process without exposing the android framework
+ * APIs directly, making easier to test the code that depends on it.
+ */
+interface ProcessInfoProvider {
+
+ /**
+ * Returns true if the current app process is in the foreground.
+ */
+ fun isForegroundImportance(): Boolean
+}
+
+/**
+ * @see ProcessInfoProvider
+ */
+class DefaultProcessInfoProvider : ProcessInfoProvider {
+
+ override fun isForegroundImportance(): Boolean {
+ val appProcessInfo = ActivityManager.RunningAppProcessInfo()
+ ActivityManager.getMyMemoryState(appProcessInfo)
+
+ return appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+ }
+}
diff --git a/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/StartForegroundService.kt b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/StartForegroundService.kt
new file mode 100644
index 0000000000..9bfa946205
--- /dev/null
+++ b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/StartForegroundService.kt
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.support.base.android
+
+/**
+ * This class is used to start a foreground service safely. For api levels >= 31. It will only
+ * start the service if the app is in the foreground to prevent throwing the
+ * ForegroundServiceStartNotAllowedException.
+ *
+ * @param processInfoProvider The provider to check if the app is in the foreground.
+ * @param buildVersionProvider The provider to get the sdk version.
+ */
+class StartForegroundService(
+ private val processInfoProvider: ProcessInfoProvider = DefaultProcessInfoProvider(),
+ private val buildVersionProvider: BuildVersionProvider = DefaultBuildVersionProvider(),
+ private val powerManagerInfoProvider: PowerManagerInfoProvider,
+) {
+
+ /**
+ * @see StartForegroundService
+ *
+ * @param func The function to run if the app is in the foreground to follow the foreground
+ * service restrictions for sdk version >= 31. For lower versions, the function will always run.
+ */
+ operator fun invoke(func: () -> Unit): Boolean =
+ if (buildVersionProvider.sdkInt() >= BuildVersionProvider.FOREGROUND_SERVICE_RESTRICTIONS_STARTING_VERSION) {
+ if (powerManagerInfoProvider.isIgnoringBatteryOptimizations() ||
+ processInfoProvider.isForegroundImportance()
+ ) {
+ func()
+ true
+ } else {
+ false
+ }
+ } else {
+ func()
+ true
+ }
+}
diff --git a/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/UserInteractionHandler.kt b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/UserInteractionHandler.kt
index 25e16ffcce..f52b19b93c 100644
--- a/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/UserInteractionHandler.kt
+++ b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/UserInteractionHandler.kt
@@ -19,6 +19,13 @@ interface UserInteractionHandler {
fun onBackPressed(): Boolean
/**
+ * Called when this [UserInteractionHandler] gets the option to handle the user pressing the forward key.
+ *
+ * Returns true if this [UserInteractionHandler] consumed the event and no other components need to be notified.
+ */
+ fun onForwardPressed(): Boolean = false
+
+ /**
* In most cases, when the home button is pressed, we invoke this callback to inform the app that the user
* is going to leave the app.
*
diff --git a/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapper.kt b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapper.kt
index a6d1e8709c..02a48abdab 100644
--- a/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapper.kt
+++ b/mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapper.kt
@@ -157,6 +157,24 @@ class ViewBoundFeatureWrapper<T : LifecycleAwareFeature>() {
}
/**
+ * Convenience method for invoking [UserInteractionHandler.onForwardPressed] on a wrapped
+ * [LifecycleAwareFeature] that implements [UserInteractionHandler]. Returns false if
+ * the [LifecycleAwareFeature] was cleared already.
+ */
+ @Synchronized
+ fun onForwardPressed(): Boolean {
+ val feature = feature ?: return false
+
+ if (feature !is UserInteractionHandler) {
+ throw IllegalAccessError(
+ "Feature does not implement ${UserInteractionHandler::class.java.simpleName} interface",
+ )
+ }
+
+ return feature.onForwardPressed()
+ }
+
+ /**
* Convenience method for invoking [ActivityResultHandler.onActivityResult] on a wrapped
* [LifecycleAwareFeature] that implements [ActivityResultHandler]. Returns false if
* the [LifecycleAwareFeature] was cleared already.
diff --git a/mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/android/StartForegroundServiceTest.kt b/mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/android/StartForegroundServiceTest.kt
new file mode 100644
index 0000000000..b583ed055c
--- /dev/null
+++ b/mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/android/StartForegroundServiceTest.kt
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.support.base.android
+
+import android.os.Build
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class StartForegroundServiceTest {
+
+ @Test
+ fun `WHEN build version below S THEN start foreground service should return true regardless of foreground importance`() {
+ val tested = StartForegroundService(
+ FakeProcessInfoProvider(false),
+ FakeBuildVersionProvider(Build.VERSION_CODES.P),
+ FakePowerManagerInfoProvider(false),
+ )
+
+ var isInvoked = false
+ val actual = tested.invoke {
+ isInvoked = true
+ }
+ val expected = true
+
+ assertEquals(expected, actual)
+ assertTrue(isInvoked)
+ }
+
+ @Test
+ fun `WHEN build version is S and above and foreground importance is false THEN start foreground service should return false`() {
+ val tested = StartForegroundService(
+ FakeProcessInfoProvider(false),
+ FakeBuildVersionProvider(Build.VERSION_CODES.S),
+ FakePowerManagerInfoProvider(false),
+ )
+
+ var isInvoked = false
+ val actual = tested.invoke {
+ isInvoked = true
+ }
+
+ assertFalse(actual)
+ assertFalse(isInvoked)
+ }
+
+ @Test
+ fun `WHEN build version is S and above and foreground importance is true THEN start foreground service should return true`() {
+ val tested = StartForegroundService(
+ FakeProcessInfoProvider(true),
+ FakeBuildVersionProvider(Build.VERSION_CODES.S),
+ FakePowerManagerInfoProvider(false),
+ )
+
+ var isInvoked = false
+ val actual = tested.invoke {
+ isInvoked = true
+ }
+
+ assertTrue(actual)
+ assertTrue(isInvoked)
+ }
+
+ @Test
+ fun `WHEN build version is S, foreground importance is false and battery optimisations are disabled THEN start foreground service should return true`() {
+ val tested = StartForegroundService(
+ FakeProcessInfoProvider(false),
+ FakeBuildVersionProvider(Build.VERSION_CODES.S),
+ FakePowerManagerInfoProvider(true),
+ )
+
+ var isInvoked = true
+ val actual = tested.invoke {
+ isInvoked = true
+ }
+
+ assertTrue(actual)
+ assertTrue(isInvoked)
+ }
+
+ class FakeProcessInfoProvider(private val isForegroundImportance: Boolean) :
+ ProcessInfoProvider {
+ override fun isForegroundImportance(): Boolean = isForegroundImportance
+ }
+
+ class FakeBuildVersionProvider(private val sdkInt: Int) : BuildVersionProvider {
+ override fun sdkInt(): Int = sdkInt
+ }
+
+ class FakePowerManagerInfoProvider(
+ private val isIgnoringBatteryOptimizations: Boolean,
+ ) : PowerManagerInfoProvider {
+ override fun isIgnoringBatteryOptimizations(): Boolean = isIgnoringBatteryOptimizations
+ }
+}
diff --git a/mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapperTest.kt b/mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapperTest.kt
index a07364acc2..73c71caec8 100644
--- a/mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapperTest.kt
+++ b/mobile/android/android-components/components/support/base/src/test/java/mozilla/components/support/base/feature/ViewBoundFeatureWrapperTest.kt
@@ -61,6 +61,34 @@ class ViewBoundFeatureWrapperTest {
}
@Test
+ fun `Calling onForwardPressed on an empty wrapper returns false`() {
+ val wrapper = ViewBoundFeatureWrapper<MockFeature>()
+ assertFalse(wrapper.onForwardPressed())
+ }
+
+ @Test
+ fun `onForwardPressed is forwarded to feature`() {
+ val feature = MockFeatureWithUserInteractionHandler(onForwardPressed = true)
+
+ val wrapper = ViewBoundFeatureWrapper(
+ feature = feature,
+ owner = MockedLifecycleOwner(MockedLifecycle(Lifecycle.State.CREATED)),
+ view = mock(),
+ )
+
+ assertTrue(wrapper.onForwardPressed())
+ assertTrue(feature.onForwardPressedInvoked)
+
+ assertFalse(
+ ViewBoundFeatureWrapper(
+ feature = MockFeatureWithUserInteractionHandler(onForwardPressed = false),
+ owner = MockedLifecycleOwner(MockedLifecycle(Lifecycle.State.CREATED)),
+ view = mock(),
+ ).onForwardPressed(),
+ )
+ }
+
+ @Test
fun `Calling onActivityResult on an empty wrapper returns false`() {
val wrapper = ViewBoundFeatureWrapper<MockFeature>()
assertFalse(wrapper.onActivityResult(0, mock(), RESULT_OK))
@@ -350,6 +378,19 @@ class ViewBoundFeatureWrapperTest {
wrapper.onBackPressed()
}
+ @Test(expected = IllegalAccessError::class)
+ fun `onForwardPressed throws if feature does not implement ForwardHandler`() {
+ val feature = MockFeature()
+
+ val wrapper = ViewBoundFeatureWrapper(
+ feature = feature,
+ owner = MockedLifecycleOwner(MockedLifecycle(Lifecycle.State.CREATED)),
+ view = mock(),
+ )
+
+ wrapper.onForwardPressed()
+ }
+
@Test
fun `Setting a feature clears a previously existing feature`() {
val feature = MockFeature()
@@ -434,6 +475,7 @@ private open class MockFeature : LifecycleAwareFeature {
private class MockFeatureWithUserInteractionHandler(
private val onBackPressed: Boolean = false,
+ private val onForwardPressed: Boolean = false,
) : MockFeature(), UserInteractionHandler {
var onBackPressedInvoked = false
private set
@@ -442,6 +484,14 @@ private class MockFeatureWithUserInteractionHandler(
onBackPressedInvoked = true
return onBackPressed
}
+
+ var onForwardPressedInvoked = false
+ private set
+
+ override fun onForwardPressed(): Boolean {
+ onForwardPressedInvoked = true
+ return onForwardPressed
+ }
}
private class MockFeatureWithActivityResultHandler(
diff --git a/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/view/View.kt b/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/view/View.kt
index 625eae2c96..1efbb4db83 100644
--- a/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/view/View.kt
+++ b/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/android/view/View.kt
@@ -13,7 +13,6 @@ import android.view.ViewTreeObserver
import android.view.inputmethod.InputMethodManager
import androidx.annotation.MainThread
import androidx.core.content.getSystemService
-import androidx.core.view.ViewCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
@@ -25,13 +24,13 @@ import java.lang.ref.WeakReference
* Is the horizontal layout direction of this view from Right to Left?
*/
val View.isRTL: Boolean
- get() = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL
+ get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
/**
* Is the horizontal layout direction of this view from Left to Right?
*/
val View.isLTR: Boolean
- get() = layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR
+ get() = layoutDirection == View.LAYOUT_DIRECTION_LTR
/**
* Tries to focus this view and show the soft input window for it.
diff --git a/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt b/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt
index abcd1a741c..8b20bfd1e5 100644
--- a/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt
+++ b/mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt
@@ -307,7 +307,16 @@ fun String.sanitizeFileName(): String {
file.name.replace("\\.\\.+".toRegex(), ".")
} else {
file.name.replace(".", "")
- }
+ }.replaceEscapedCharacters()
+}
+
+/**
+ * Replaces control characters from ASCII 0 to ASCII 19 with '_' so the file name is valid
+ * and is correctly displayed.
+ */
+private fun String.replaceEscapedCharacters(): String {
+ val controlCharactersRegex = "[\\x00-\\x13]".toRegex()
+ return replace(controlCharactersRegex, "_")
}
/**
@@ -330,6 +339,15 @@ fun String.urlEncode(): String {
}
/**
+ * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
+ * Replaces invalid octets with the unicode replacement character
+ * ("\\uFFFD").
+ *
+ * @see [Uri.decode]
+ */
+fun String.decode(): String = Uri.decode(this)
+
+/**
* Returns the string if it's length is not higher than @param[maximumLength] or
* a @param[replacement] string if String length is higher than @param[maximumLength]
*/
diff --git a/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt b/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
index 89ade2aace..eaa9e78c9a 100644
--- a/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
+++ b/mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
@@ -181,24 +181,80 @@ class StringTest {
@Test
fun sanitizeFileName() {
- var file = "/../../../../../../../../../../directory/file.......txt"
- val fileName = "file.txt"
-
- assertEquals(fileName, file.sanitizeFileName())
-
- file = "/root/directory/file.txt"
-
- assertEquals(fileName, file.sanitizeFileName())
-
- assertEquals("file", "file".sanitizeFileName())
-
- assertEquals("file", "file..".sanitizeFileName())
-
- assertEquals("file", "file.".sanitizeFileName())
+ val testCases = listOf(
+ "/../../../../../../../../../../directory/file.......txt" to "file.txt",
+ "/root/directory/file.txt" to "file.txt",
+ "file" to "file",
+ "file.." to "file",
+ "file." to "file",
+ ".file" to "file",
+ "test.2020.12.01.txt" to "test.2020.12.01.txt",
+ "\u0000filename" to "_filename",
+ "file\u0001name" to "file_name",
+ "data\u0002stream" to "data_stream",
+ "end\u0003text" to "end_text",
+ "trans\u0004mission" to "trans_mission",
+ "query\u0005result" to "query_result",
+ "acknowledge\u0006signal" to "acknowledge_signal",
+ "bell\u0007sound" to "bell_sound",
+ "back\u0008space" to "back_space",
+ "horizontal\u0009tab" to "horizontal_tab",
+ "new\u000Aline" to "new_line",
+ "vertical\u000Btab" to "vertical_tab",
+ "form\u000Cfeed" to "form_feed",
+ "return\u000Dcarriage" to "return_carriage",
+ "shift\u000Eout" to "shift_out",
+ "shift\u000Fin" to "shift_in",
+ "escape\u0010data" to "escape_data",
+ "device\u0011control1" to "device_control1",
+ "device\u0012control2" to "device_control2",
+ "device\u0013control3" to "device_control3",
+ )
- assertEquals("file", ".file".sanitizeFileName())
+ testCases.forEach { (raw, escaped) ->
+ assertEquals(escaped, raw.sanitizeFileName())
+ }
+ }
+
+ @Test
+ fun `WHEN a string contains utf 8 encoded characters decode decodes it`() {
+ // List of pairs of encoded strings and their expected decoded results
+ val testCases = listOf(
+ "hello%20world" to "hello world",
+ "wow%21amazing" to "wow!amazing",
+ "quote%22here%22" to "quote\"here\"",
+ "hash%23tag" to "hash#tag",
+ "save%24money" to "save\$money",
+ "100%25complete" to "100%complete",
+ "you%26me" to "you&me",
+ "it%27s%20easy" to "it's easy",
+ "open%28now%29" to "open(now)",
+ "star%2Ashine" to "star*shine",
+ "add%2Bmore" to "add+more",
+ "comma%2Cseparated" to "comma,separated",
+ "dash%2Dbetween" to "dash-between",
+ "end%2Eperiod" to "end.period",
+ "path%2Fto%2Ffile" to "path/to/file",
+ "time%3A12%3A00" to "time:12:00",
+ "wait%3Bplease" to "wait;please",
+ "less%3Cthan" to "less<than",
+ "equals%3Dsign" to "equals=sign",
+ "greater%3Ethan" to "greater>than",
+ "what%3Fwhere" to "what?where",
+ "email%40domain.com" to "email@domain.com",
+ "bracket%5Bopen%5D" to "bracket[open]",
+ "escape%5Cbackslash" to "escape\\backslash",
+ "bracket%5Dclose%5D" to "bracket]close]",
+ "high%5Efive" to "high^five",
+ "accent%60grave" to "accent`grave",
+ "brace%7Bopenclose%7D" to "brace{openclose}",
+ "pipe%7Csymbol" to "pipe|symbol",
+ "tilde%7Ewave" to "tilde~wave",
+ )
- assertEquals("test.2020.12.01.txt", "test.2020.12.01.txt".sanitizeFileName())
+ testCases.forEach { (encoded, decoded) ->
+ assertEquals(decoded, encoded.decode())
+ }
}
@Test
diff --git a/mobile/android/android-components/components/support/webextensions/src/main/java/mozilla/components/support/webextensions/WebExtensionSupport.kt b/mobile/android/android-components/components/support/webextensions/src/main/java/mozilla/components/support/webextensions/WebExtensionSupport.kt
index b4b78d6272..3945a2fd06 100644
--- a/mobile/android/android-components/components/support/webextensions/src/main/java/mozilla/components/support/webextensions/WebExtensionSupport.kt
+++ b/mobile/android/android-components/components/support/webextensions/src/main/java/mozilla/components/support/webextensions/WebExtensionSupport.kt
@@ -269,6 +269,10 @@ object WebExtensionSupport {
store.dispatch(WebExtensionAction.UpdateWebExtensionEnabledAction(extension.id, true))
}
+ override fun onOptionalPermissionsChanged(extension: WebExtension) {
+ installedExtensions[extension.id] = extension
+ }
+
override fun onDisabled(extension: WebExtension) {
installedExtensions[extension.id] = extension
store.dispatch(WebExtensionAction.UpdateWebExtensionEnabledAction(extension.id, false))
diff --git a/mobile/android/android-components/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt b/mobile/android/android-components/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt
index b4e45b3f55..ec61832cef 100644
--- a/mobile/android/android-components/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt
+++ b/mobile/android/android-components/components/support/webextensions/src/test/java/mozilla/components/support/webextensions/WebExtensionSupportTest.kt
@@ -598,6 +598,25 @@ class WebExtensionSupportTest {
}
@Test
+ fun `reacts to optional permissions for an extension being changed`() {
+ val store = spy(BrowserStore())
+ val engine: Engine = mock()
+ val ext: WebExtension = mock()
+ whenever(ext.id).thenReturn("extensionId")
+ whenever(ext.url).thenReturn("url")
+
+ val delegateCaptor = argumentCaptor<WebExtensionDelegate>()
+ WebExtensionSupport.initialize(engine, store)
+ verify(engine).registerWebExtensionDelegate(delegateCaptor.capture())
+
+ assertNull(WebExtensionSupport.installedExtensions[ext.id])
+
+ delegateCaptor.value.onOptionalPermissionsChanged(ext)
+
+ assertEquals(ext, WebExtensionSupport.installedExtensions[ext.id])
+ }
+
+ @Test
fun `observes store and registers handlers on new engine sessions`() {
val tab = createTab(id = "1", url = "https://www.mozilla.org")
val customTab = createCustomTab(id = "2", url = "https://www.mozilla.org", source = SessionState.Source.Internal.CustomTab)