Skip to content

Commit a65fc32

Browse files
PM-36508: Add support for requesting LocalNetworkAccess permission
1 parent 099cb08 commit a65fc32

29 files changed

Lines changed: 1193 additions & 8 deletions

File tree

app/src/main/kotlin/com/x8bit/bitwarden/MainActivity.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import com.x8bit.bitwarden.ui.platform.feature.cookieacquisition.navigateToCooki
4141
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.debugMenuDestination
4242
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
4343
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
44+
import com.x8bit.bitwarden.ui.platform.feature.localnetworkaccess.localNetworkAccessDestination
45+
import com.x8bit.bitwarden.ui.platform.feature.localnetworkaccess.navigateToLocalNetworkAccess
4446
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavigationRoute
4547
import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination
4648
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
@@ -151,6 +153,10 @@ class MainActivity : AppCompatActivity() {
151153
onDismiss = { navController.popBackStack() },
152154
onSplashScreenRemoved = { shouldShowSplashScreen = false },
153155
)
156+
localNetworkAccessDestination(
157+
onDismiss = { navController.popBackStack() },
158+
onSplashScreenRemoved = { shouldShowSplashScreen = false },
159+
)
154160
}
155161
}
156162
}
@@ -235,6 +241,9 @@ class MainActivity : AppCompatActivity() {
235241
MainEvent.Recreate -> handleRecreate()
236242
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
237243
MainEvent.NavigateToCookieAcquisition -> navController.navigateToCookieAcquisition()
244+
MainEvent.NavigateToLocalNetworkAccess -> {
245+
navController.navigateToLocalNetworkAccess()
246+
}
238247

239248
is MainEvent.UpdateAppLocale -> {
240249
AppCompatDelegate.setApplicationLocales(

app/src/main/kotlin/com/x8bit/bitwarden/MainViewModel.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import com.x8bit.bitwarden.data.platform.manager.garbage.GarbageCollectionManage
3737
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
3838
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
3939
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
40+
import com.x8bit.bitwarden.data.platform.manager.network.NetworkPermissionManager
4041
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
4142
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
4243
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
@@ -80,6 +81,7 @@ class MainViewModel @Inject constructor(
8081
accessibilitySelectionManager: AccessibilitySelectionManager,
8182
autofillSelectionManager: AutofillSelectionManager,
8283
cookieAcquisitionRequestManager: CookieAcquisitionRequestManager,
84+
networkPermissionManager: NetworkPermissionManager,
8385
private val addTotpItemFromAuthenticatorManager: AddTotpItemFromAuthenticatorManager,
8486
private val specialCircumstanceManager: SpecialCircumstanceManager,
8587
private val garbageCollectionManager: GarbageCollectionManager,
@@ -168,6 +170,13 @@ class MainViewModel @Inject constructor(
168170
.onEach(::sendAction)
169171
.launchIn(viewModelScope)
170172

173+
networkPermissionManager
174+
.isLocalNetworkAccessRequiredStateFlow
175+
.filter { it }
176+
.map { MainAction.Internal.LocalNetworkAccessRequired }
177+
.onEach(::sendAction)
178+
.launchIn(viewModelScope)
179+
171180
cookieAcquisitionRequestManager
172181
.cookieAcquisitionRequestFlow
173182
.filterNotNull()
@@ -223,6 +232,7 @@ class MainViewModel @Inject constructor(
223232
is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action)
224233
is MainAction.Internal.DynamicColorsUpdate -> handleDynamicColorsUpdate(action)
225234
is MainAction.Internal.CookieAcquisitionReady -> handleCookieAcquisitionReady()
235+
is MainAction.Internal.LocalNetworkAccessRequired -> handleLocalNetworkAccessRequired()
226236
is MainAction.Internal.ResizeHasBeenRequested -> handleResizeHasBeenRequested()
227237
}
228238
}
@@ -304,6 +314,10 @@ class MainViewModel @Inject constructor(
304314
sendEvent(MainEvent.NavigateToCookieAcquisition)
305315
}
306316

317+
private fun handleLocalNetworkAccessRequired() {
318+
sendEvent(MainEvent.NavigateToLocalNetworkAccess)
319+
}
320+
307321
private fun handleResizeHasBeenRequested() {
308322
mutableStateFlow.update { it.copy(hasResizeBeenRequested = true) }
309323
}
@@ -656,6 +670,11 @@ sealed class MainAction {
656670
*/
657671
data object CookieAcquisitionReady : Internal()
658672

673+
/**
674+
* Indicates that the local network access is required.
675+
*/
676+
data object LocalNetworkAccessRequired : Internal()
677+
659678
/**
660679
* Indicates that resize has been requested on the Activity
661680
*/
@@ -694,6 +713,11 @@ sealed class MainEvent {
694713
*/
695714
data object NavigateToCookieAcquisition : MainEvent()
696715

716+
/**
717+
* Navigate to the local network access screen.
718+
*/
719+
data object NavigateToLocalNetworkAccess : MainEvent()
720+
697721
/**
698722
* Indicates that the app language has been updated.
699723
*/

app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/network/di/PlatformNetworkModule.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CL
1515
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_USER_AGENT
1616
import com.x8bit.bitwarden.data.platform.manager.CertificateManager
1717
import com.x8bit.bitwarden.data.platform.manager.network.NetworkCookieManager
18+
import com.x8bit.bitwarden.data.platform.manager.network.NetworkPermissionManager
1819
import dagger.Module
1920
import dagger.Provides
2021
import dagger.hilt.InstallIn
@@ -58,6 +59,7 @@ object PlatformNetworkModule {
5859
certificateManager: CertificateManager,
5960
buildInfoManager: BuildInfoManager,
6061
networkCookieManager: NetworkCookieManager,
62+
networkPermissionManager: NetworkPermissionManager,
6163
clock: Clock,
6264
): BitwardenServiceClientConfig = BitwardenServiceClientConfig(
6365
clock = clock,
@@ -72,6 +74,7 @@ object PlatformNetworkModule {
7274
certificateProvider = certificateManager,
7375
enableHttpBodyLogging = buildInfoManager.isDevBuild,
7476
cookieProvider = networkCookieManager,
77+
permissionProvider = networkPermissionManager,
7578
)
7679

7780
@Provides

app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManage
7474
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManagerImpl
7575
import com.x8bit.bitwarden.data.platform.manager.network.NetworkCookieManager
7676
import com.x8bit.bitwarden.data.platform.manager.network.NetworkCookieManagerImpl
77+
import com.x8bit.bitwarden.data.platform.manager.network.NetworkPermissionManager
78+
import com.x8bit.bitwarden.data.platform.manager.network.NetworkPermissionManagerImpl
7779
import com.x8bit.bitwarden.data.platform.manager.restriction.RestrictionManager
7880
import com.x8bit.bitwarden.data.platform.manager.restriction.RestrictionManagerImpl
7981
import com.x8bit.bitwarden.data.platform.manager.sdk.SdkPlatformApiFactory
@@ -447,4 +449,12 @@ object PlatformManagerModule {
447449
cookieDiskSource = cookieDiskSource,
448450
cookieAcquisitionRequestManager = cookieAcquisitionRequestManager,
449451
)
452+
453+
@Provides
454+
@Singleton
455+
fun provideNetworkPermissionManager(
456+
@ApplicationContext context: Context,
457+
): NetworkPermissionManager = NetworkPermissionManagerImpl(
458+
context = context,
459+
)
450460
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.x8bit.bitwarden.data.platform.manager.network
2+
3+
import com.bitwarden.network.provider.PermissionProvider
4+
import kotlinx.coroutines.flow.StateFlow
5+
6+
/**
7+
* A manager class for handling network permissions.
8+
*/
9+
interface NetworkPermissionManager : PermissionProvider {
10+
/**
11+
* StateFlow indicating if local network access is being requested at this moment.
12+
*
13+
* Emits `true` when local network access is, `false` otherwise.
14+
*/
15+
val isLocalNetworkAccessRequiredStateFlow: StateFlow<Boolean>
16+
17+
/**
18+
* Sets the local network access required state to `false`.
19+
*/
20+
fun clearIsLocalNetworkAccessRequired()
21+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.x8bit.bitwarden.data.platform.manager.network
2+
3+
import android.Manifest
4+
import android.content.Context
5+
import android.content.pm.PackageManager
6+
import android.os.Build
7+
import androidx.core.content.ContextCompat
8+
import com.bitwarden.core.util.isBuildVersionAtLeast
9+
import com.bitwarden.ui.platform.resource.BitwardenString
10+
import kotlinx.coroutines.flow.MutableStateFlow
11+
import kotlinx.coroutines.flow.StateFlow
12+
13+
/**
14+
* The default implementation of [NetworkPermissionManager].
15+
*/
16+
internal class NetworkPermissionManagerImpl(
17+
private val context: Context,
18+
) : NetworkPermissionManager {
19+
private val mutableIsLocalNetworkAccessRequiredStateFlow = MutableStateFlow(value = false)
20+
21+
override val errorMessageString: String
22+
get() = ContextCompat.getString(
23+
context,
24+
BitwardenString
25+
.your_request_was_interrupted_because_the_app_needs_local_network_permission,
26+
)
27+
28+
override val hasLocalNetworkAccessPermission: Boolean
29+
get() = if (isBuildVersionAtLeast(version = Build.VERSION_CODES.CINNAMON_BUN)) {
30+
ContextCompat.checkSelfPermission(
31+
context,
32+
Manifest.permission.ACCESS_LOCAL_NETWORK,
33+
) == PackageManager.PERMISSION_GRANTED
34+
} else {
35+
true
36+
}
37+
38+
override val isLocalNetworkAccessRequiredStateFlow: StateFlow<Boolean> =
39+
mutableIsLocalNetworkAccessRequiredStateFlow
40+
41+
override fun acquireLocalNetworkAccessPermission() {
42+
mutableIsLocalNetworkAccessRequiredStateFlow.value = true
43+
}
44+
45+
override fun clearIsLocalNetworkAccessRequired() {
46+
mutableIsLocalNetworkAccessRequiredStateFlow.value = false
47+
}
48+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.x8bit.bitwarden.data.platform.util
22

33
import com.bitwarden.network.exception.CookieRedirectException
4+
import com.bitwarden.network.exception.LocalNetworkAccessException
45

56
/**
67
* Returns a user-friendly error message if this [Throwable] is an allow-listed
78
* exception type that carries one, or `null` otherwise.
89
*/
910
val Throwable.userFriendlyMessage: String?
1011
get() = when (this) {
12+
is LocalNetworkAccessException -> message
1113
is CookieRedirectException -> message
1214
else -> null
1315
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@file:OmitFromCoverage
2+
3+
package com.x8bit.bitwarden.ui.platform.feature.localnetworkaccess
4+
5+
import androidx.navigation.NavController
6+
import androidx.navigation.NavGraphBuilder
7+
import com.bitwarden.annotation.OmitFromCoverage
8+
import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions
9+
import kotlinx.serialization.Serializable
10+
11+
/**
12+
* The type-safe route for the local network access screen.
13+
*/
14+
@OmitFromCoverage
15+
@Serializable
16+
data object LocalNetworkAccessRoute
17+
18+
/**
19+
* Add the local network access screen to the nav graph.
20+
*/
21+
fun NavGraphBuilder.localNetworkAccessDestination(
22+
onDismiss: () -> Unit,
23+
onSplashScreenRemoved: () -> Unit,
24+
) {
25+
composableWithSlideTransitions<LocalNetworkAccessRoute> {
26+
LocalNetworkAccessScreen(onDismiss = onDismiss)
27+
// If we are displaying the local network access screen, then we can just hide
28+
// the splash screen.
29+
onSplashScreenRemoved()
30+
}
31+
}
32+
33+
/**
34+
* Navigate to the local network access screen.
35+
*/
36+
fun NavController.navigateToLocalNetworkAccess() {
37+
this.navigate(route = LocalNetworkAccessRoute) {
38+
launchSingleTop = true
39+
}
40+
}

0 commit comments

Comments
 (0)