From e619fefb7f16b455b9ed4f57d43f2e1c37dff3ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 6 Jun 2024 12:17:50 +0200 Subject: [PATCH] Add a flag to enable or disable incoming share --- app/src/main/AndroidManifest.xml | 12 ++- appnav/build.gradle.kts | 1 + .../android/appnav/root/RootPresenter.kt | 6 ++ .../android/appnav/RootPresenterTest.kt | 24 +++++- .../features/share/api/ShareService.kt | 23 ++++++ features/share/impl/build.gradle.kts | 1 + .../share/impl/DefaultShareService.kt | 76 +++++++++++++++++++ features/share/test/build.gradle.kts | 28 +++++++ .../features/share/test/FakeShareService.kt | 29 +++++++ .../libraries/featureflag/api/FeatureFlags.kt | 7 ++ .../impl/StaticFeatureFlagProvider.kt | 1 + 11 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareService.kt create mode 100644 features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareService.kt create mode 100644 features/share/test/build.gradle.kts create mode 100644 features/share/test/src/main/kotlin/io/element/android/features/share/test/FakeShareService.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8c9a247172..b3872a18fd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -120,10 +120,18 @@ + + + + - + @@ -136,7 +144,7 @@ - + { @Composable @@ -52,6 +54,10 @@ class RootPresenter @Inject constructor( ) } + LaunchedEffect(Unit) { + shareService.observeFeatureFlag(this) + } + return RootState( rageshakeDetectionState = rageshakeDetectionState, crashDetectionState = crashDetectionState, diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt index 118ecd1e46..aeaeb10859 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt @@ -28,6 +28,8 @@ import io.element.android.features.rageshake.test.crash.FakeCrashDataStore import io.element.android.features.rageshake.test.rageshake.FakeRageShake import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder +import io.element.android.features.share.api.ShareService +import io.element.android.features.share.test.FakeShareService import io.element.android.libraries.matrix.test.FakeSdkMetadata import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.services.analytics.test.FakeAnalyticsService @@ -35,6 +37,8 @@ import io.element.android.services.apperror.api.AppErrorState import io.element.android.services.apperror.api.AppErrorStateService import io.element.android.services.apperror.impl.DefaultAppErrorStateService import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -55,6 +59,22 @@ class RootPresenterTest { } } + @Test + fun `present - check that share service is invoked`() = runTest { + val lambda = lambdaRecorder { _ -> } + val presenter = createRootPresenter( + shareService = FakeShareService { + lambda(it) + } + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(2) + lambda.assertions().isCalledOnce() + } + } + @Test fun `present - passes app error state`() = runTest { val presenter = createRootPresenter( @@ -79,7 +99,8 @@ class RootPresenterTest { } private fun createRootPresenter( - appErrorService: AppErrorStateService = DefaultAppErrorStateService() + appErrorService: AppErrorStateService = DefaultAppErrorStateService(), + shareService: ShareService = FakeShareService {}, ): RootPresenter { val crashDataStore = FakeCrashDataStore() val rageshakeDataStore = FakeRageshakeDataStore() @@ -102,6 +123,7 @@ class RootPresenterTest { rageshakeDetectionPresenter = rageshakeDetectionPresenter, appErrorStateService = appErrorService, analyticsService = FakeAnalyticsService(), + shareService = shareService, sdkMetadata = FakeSdkMetadata("sha") ) } diff --git a/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareService.kt b/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareService.kt new file mode 100644 index 0000000000..c46f5b3215 --- /dev/null +++ b/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareService.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.share.api + +import kotlinx.coroutines.CoroutineScope + +interface ShareService { + fun observeFeatureFlag(coroutineScope: CoroutineScope) +} diff --git a/features/share/impl/build.gradle.kts b/features/share/impl/build.gradle.kts index bc9fdee42d..c180c5d29a 100644 --- a/features/share/impl/build.gradle.kts +++ b/features/share/impl/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.architecture) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareService.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareService.kt new file mode 100644 index 0000000000..89a94c2a95 --- /dev/null +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareService.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.share.impl + +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.share.api.ShareService +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultShareService @Inject constructor( + private val featureFlagService: FeatureFlagService, + @ApplicationContext private val context: Context, +) : ShareService { + override fun observeFeatureFlag(coroutineScope: CoroutineScope) { + val shareActivityComponent = context + .packageManager + .getPackageInfo( + context.packageName, + PackageManager.GET_ACTIVITIES or PackageManager.MATCH_DISABLED_COMPONENTS + ) + .activities + .firstOrNull { it.name.endsWith(".ShareActivity") } + ?.let { shareActivityInfo -> + ComponentName( + shareActivityInfo.packageName, + shareActivityInfo.name, + ) + } + ?: return Unit.also { + Timber.w("ShareActivity not found") + } + featureFlagService.isFeatureEnabledFlow(FeatureFlags.IncomingShare) + .onEach { enabled -> + val state = if (enabled) { + PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + } else { + PackageManager.COMPONENT_ENABLED_STATE_DISABLED + } + try { + context.packageManager.setComponentEnabledSetting( + shareActivityComponent, + state, + PackageManager.DONT_KILL_APP, + ) + } catch (e: Exception) { + Timber.e(e, "Failed to enable or disable the component") + } + } + .launchIn(coroutineScope) + } +} diff --git a/features/share/test/build.gradle.kts b/features/share/test/build.gradle.kts new file mode 100644 index 0000000000..0eaa0bedd2 --- /dev/null +++ b/features/share/test/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.share.test" +} + +dependencies { + implementation(projects.features.share.api) + implementation(libs.coroutines.core) + implementation(projects.tests.testutils) +} diff --git a/features/share/test/src/main/kotlin/io/element/android/features/share/test/FakeShareService.kt b/features/share/test/src/main/kotlin/io/element/android/features/share/test/FakeShareService.kt new file mode 100644 index 0000000000..302d8e2a54 --- /dev/null +++ b/features/share/test/src/main/kotlin/io/element/android/features/share/test/FakeShareService.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.share.test + +import io.element.android.features.share.api.ShareService +import io.element.android.tests.testutils.lambda.lambdaError +import kotlinx.coroutines.CoroutineScope + +class FakeShareService( + private val observeFeatureFlagLambda: (CoroutineScope) -> Unit = { lambdaError() } +) : ShareService { + override fun observeFeatureFlag(coroutineScope: CoroutineScope) { + observeFeatureFlagLambda(coroutineScope) + } +} diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 5c3bd8efd4..4d0c50a533 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -96,4 +96,11 @@ enum class FeatureFlags( defaultValue = true, isFinished = false, ), + IncomingShare( + key = "feature.incomingShare", + title = "Incoming Share support", + description = "Allow the application to receive data from other applications", + defaultValue = true, + isFinished = false, + ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 139877500b..7574144066 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -44,6 +44,7 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.RoomDirectorySearch -> false FeatureFlags.ShowBlockedUsersDetails -> false FeatureFlags.QrCodeLogin -> OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE + FeatureFlags.IncomingShare -> true } } else { false