Add a flag to enable or disable incoming share
This commit is contained in:
parent
abaac1c5ca
commit
6781b5cee7
11 changed files with 205 additions and 3 deletions
|
|
@ -120,10 +120,18 @@
|
||||||
<data android:host="user" />
|
<data android:host="user" />
|
||||||
<data android:host="room" />
|
<data android:host="room" />
|
||||||
</intent-filter>
|
</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 -->
|
<!-- Incoming share simple -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<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.DEFAULT" />
|
||||||
<category android:name="android.intent.category.OPENABLE" />
|
<category android:name="android.intent.category.OPENABLE" />
|
||||||
|
|
@ -136,7 +144,7 @@
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.OPENABLE" />
|
<category android:name="android.intent.category.OPENABLE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity-alias>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.startup.InitializationProvider"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ dependencies {
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testImplementation(projects.features.rageshake.test)
|
testImplementation(projects.features.rageshake.test)
|
||||||
testImplementation(projects.features.rageshake.impl)
|
testImplementation(projects.features.rageshake.impl)
|
||||||
|
testImplementation(projects.features.share.test)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
testImplementation(libs.test.appyx.junit)
|
testImplementation(libs.test.appyx.junit)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import androidx.compose.runtime.getValue
|
||||||
import im.vector.app.features.analytics.plan.SuperProperties
|
import im.vector.app.features.analytics.plan.SuperProperties
|
||||||
import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter
|
import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter
|
||||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter
|
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.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.SdkMetadata
|
import io.element.android.libraries.matrix.api.SdkMetadata
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|
@ -34,6 +35,7 @@ class RootPresenter @Inject constructor(
|
||||||
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
||||||
private val appErrorStateService: AppErrorStateService,
|
private val appErrorStateService: AppErrorStateService,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
|
private val shareService: ShareService,
|
||||||
private val sdkMetadata: SdkMetadata,
|
private val sdkMetadata: SdkMetadata,
|
||||||
) : Presenter<RootState> {
|
) : Presenter<RootState> {
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -52,6 +54,10 @@ class RootPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
shareService.observeFeatureFlag(this)
|
||||||
|
}
|
||||||
|
|
||||||
return RootState(
|
return RootState(
|
||||||
rageshakeDetectionState = rageshakeDetectionState,
|
rageshakeDetectionState = rageshakeDetectionState,
|
||||||
crashDetectionState = crashDetectionState,
|
crashDetectionState = crashDetectionState,
|
||||||
|
|
|
||||||
|
|
@ -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.FakeRageShake
|
||||||
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
|
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
|
||||||
import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder
|
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.FakeSdkMetadata
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
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.api.AppErrorStateService
|
||||||
import io.element.android.services.apperror.impl.DefaultAppErrorStateService
|
import io.element.android.services.apperror.impl.DefaultAppErrorStateService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
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 kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
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
|
@Test
|
||||||
fun `present - passes app error state`() = runTest {
|
fun `present - passes app error state`() = runTest {
|
||||||
val presenter = createRootPresenter(
|
val presenter = createRootPresenter(
|
||||||
|
|
@ -79,7 +99,8 @@ class RootPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRootPresenter(
|
private fun createRootPresenter(
|
||||||
appErrorService: AppErrorStateService = DefaultAppErrorStateService()
|
appErrorService: AppErrorStateService = DefaultAppErrorStateService(),
|
||||||
|
shareService: ShareService = FakeShareService {},
|
||||||
): RootPresenter {
|
): RootPresenter {
|
||||||
val crashDataStore = FakeCrashDataStore()
|
val crashDataStore = FakeCrashDataStore()
|
||||||
val rageshakeDataStore = FakeRageshakeDataStore()
|
val rageshakeDataStore = FakeRageshakeDataStore()
|
||||||
|
|
@ -102,6 +123,7 @@ class RootPresenterTest {
|
||||||
rageshakeDetectionPresenter = rageshakeDetectionPresenter,
|
rageshakeDetectionPresenter = rageshakeDetectionPresenter,
|
||||||
appErrorStateService = appErrorService,
|
appErrorStateService = appErrorService,
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = FakeAnalyticsService(),
|
||||||
|
shareService = shareService,
|
||||||
sdkMetadata = FakeSdkMetadata("sha")
|
sdkMetadata = FakeSdkMetadata("sha")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,7 @@ dependencies {
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.featureflag.api)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
28
features/share/test/build.gradle.kts
Normal file
28
features/share/test/build.gradle.kts
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -96,4 +96,11 @@ enum class FeatureFlags(
|
||||||
defaultValue = true,
|
defaultValue = true,
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
|
IncomingShare(
|
||||||
|
key = "feature.incomingShare",
|
||||||
|
title = "Incoming Share support",
|
||||||
|
description = "Allow the application to receive data from other applications",
|
||||||
|
defaultValue = true,
|
||||||
|
isFinished = false,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class StaticFeatureFlagProvider @Inject constructor() :
|
||||||
FeatureFlags.RoomDirectorySearch -> false
|
FeatureFlags.RoomDirectorySearch -> false
|
||||||
FeatureFlags.ShowBlockedUsersDetails -> false
|
FeatureFlags.ShowBlockedUsersDetails -> false
|
||||||
FeatureFlags.QrCodeLogin -> OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE
|
FeatureFlags.QrCodeLogin -> OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE
|
||||||
|
FeatureFlags.IncomingShare -> true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue