Add a flag to enable or disable incoming share

This commit is contained in:
Benoit Marty
2024-06-06 12:17:50 +02:00
committed by Benoit Marty
parent abaac1c5ca
commit 6781b5cee7
11 changed files with 205 additions and 3 deletions

View File

@@ -120,10 +120,18 @@
<data android:host="user" />
<data android:host="room" />
</intent-filter>
</activity>
<!-- Using an activity-alias for incoming share intent, in order
to be able to disable the feature programmatically -->
<activity-alias
android:name=".ShareActivity"
android:exported="true"
android:targetActivity=".MainActivity">
<!-- Incoming share simple -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="*/*"/>
<data android:mimeType="*/*" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
@@ -136,7 +144,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
</intent-filter>
</activity>
</activity-alias>
<provider
android:name="androidx.startup.InitializationProvider"

View File

@@ -72,6 +72,7 @@ dependencies {
testImplementation(projects.tests.testutils)
testImplementation(projects.features.rageshake.test)
testImplementation(projects.features.rageshake.impl)
testImplementation(projects.features.share.test)
testImplementation(projects.services.appnavstate.test)
testImplementation(projects.services.analytics.test)
testImplementation(libs.test.appyx.junit)

View File

@@ -23,6 +23,7 @@ import androidx.compose.runtime.getValue
import im.vector.app.features.analytics.plan.SuperProperties
import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter
import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter
import io.element.android.features.share.api.ShareService
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.SdkMetadata
import io.element.android.services.analytics.api.AnalyticsService
@@ -34,6 +35,7 @@ class RootPresenter @Inject constructor(
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
private val appErrorStateService: AppErrorStateService,
private val analyticsService: AnalyticsService,
private val shareService: ShareService,
private val sdkMetadata: SdkMetadata,
) : Presenter<RootState> {
@Composable
@@ -52,6 +54,10 @@ class RootPresenter @Inject constructor(
)
}
LaunchedEffect(Unit) {
shareService.observeFeatureFlag(this)
}
return RootState(
rageshakeDetectionState = rageshakeDetectionState,
crashDetectionState = crashDetectionState,

View File

@@ -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<CoroutineScope, Unit> { _ -> }
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")
)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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,
),
}

View File

@@ -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