Implement Mobile Device Manager feature with 3 keys.

This commit is contained in:
Benoit Marty 2023-11-30 18:02:02 +01:00
parent bb9d1fc8d8
commit fb077d5065
12 changed files with 179 additions and 7 deletions

View File

@ -26,6 +26,8 @@ import im.vector.app.core.pushers.PushParser
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.pushers.VectorPushHandler import im.vector.app.core.pushers.VectorPushHandler
import im.vector.app.features.mdm.MdmData
import im.vector.app.features.mdm.MdmService
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -46,6 +48,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
@Inject lateinit var pushParser: PushParser @Inject lateinit var pushParser: PushParser
@Inject lateinit var vectorPushHandler: VectorPushHandler @Inject lateinit var vectorPushHandler: VectorPushHandler
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper @Inject lateinit var unifiedPushHelper: UnifiedPushHelper
@Inject lateinit var mdmService: MdmService
private val scope = CoroutineScope(SupervisorJob()) private val scope = CoroutineScope(SupervisorJob())
@ -53,6 +56,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
scope.cancel() scope.cancel()
super.onDestroy() super.onDestroy()
} }
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
Timber.tag(loggerTag.value).d("New Firebase token") Timber.tag(loggerTag.value).d("New Firebase token")
fcmHelper.storeFcmToken(token) fcmHelper.storeFcmToken(token)
@ -62,7 +66,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
unifiedPushHelper.isEmbeddedDistributor() unifiedPushHelper.isEmbeddedDistributor()
) { ) {
scope.launch { scope.launch {
pushersManager.enqueueRegisterPusher(token, getString(R.string.pusher_http_url)) pushersManager.enqueueRegisterPusher(
pushKey = token,
gateway = mdmService.getData(
mdmData = MdmData.DefaultPushGatewayUrl,
defaultValue = getString(R.string.pusher_http_url),
),
)
} }
} }
} }

View File

@ -20,6 +20,10 @@
tools:ignore="UnusedAttribute" tools:ignore="UnusedAttribute"
tools:replace="android:allowBackup"> tools:replace="android:allowBackup">
<meta-data
android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/vector_app_restrictions" />
<!-- Activity alias for the launcher Activity (must be declared after the Activity it targets) --> <!-- Activity alias for the launcher Activity (must be declared after the Activity it targets) -->
<!-- exported="true" is required to launch application --> <!-- exported="true" is required to launch application -->
<activity-alias <activity-alias

View File

@ -50,6 +50,9 @@ import im.vector.app.features.analytics.metrics.VectorPlugins
import im.vector.app.features.configuration.VectorCustomEventTypesProvider import im.vector.app.features.configuration.VectorCustomEventTypesProvider
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
import im.vector.app.features.mdm.DefaultMdmService
import im.vector.app.features.mdm.MdmData
import im.vector.app.features.mdm.MdmService
import im.vector.app.features.navigation.DefaultNavigator import im.vector.app.features.navigation.DefaultNavigator
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.PinCodeStore
@ -145,6 +148,7 @@ import javax.inject.Singleton
flipperProxy: FlipperProxy, flipperProxy: FlipperProxy,
vectorPlugins: VectorPlugins, vectorPlugins: VectorPlugins,
vectorCustomEventTypesProvider: VectorCustomEventTypesProvider, vectorCustomEventTypesProvider: VectorCustomEventTypesProvider,
mdmService: MdmService,
): MatrixConfiguration { ): MatrixConfiguration {
return MatrixConfiguration( return MatrixConfiguration(
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION, applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
@ -156,6 +160,7 @@ import javax.inject.Singleton
metricPlugins = vectorPlugins.plugins(), metricPlugins = vectorPlugins.plugins(),
cryptoAnalyticsPlugin = vectorPlugins.cryptoMetricPlugin, cryptoAnalyticsPlugin = vectorPlugins.cryptoMetricPlugin,
customEventTypesProvider = vectorCustomEventTypesProvider, customEventTypesProvider = vectorCustomEventTypesProvider,
clientPermalinkBaseUrl = mdmService.getData(MdmData.PermalinkBaseUrl),
syncConfig = SyncConfig( syncConfig = SyncConfig(
syncFilterParams = SyncFilterParams(lazyLoadMembersForStateEvents = true, useThreadNotifications = true) syncFilterParams = SyncFilterParams(lazyLoadMembersForStateEvents = true, useThreadNotifications = true)
) )
@ -168,6 +173,11 @@ import javax.inject.Singleton
return Matrix(context, configuration) return Matrix(context, configuration)
} }
@Provides
fun providesMdmService(context: Context): MdmService {
return DefaultMdmService(context)
}
@Provides @Provides
fun providesCurrentSession(activeSessionHolder: ActiveSessionHolder): Session { fun providesCurrentSession(activeSessionHolder: ActiveSessionHolder): Session {
// TODO handle session injection better // TODO handle session injection better

View File

@ -2,4 +2,8 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- The https://github.com/LikeTheSalad/android-stem requires a non empty strings.xml --> <!-- The https://github.com/LikeTheSalad/android-stem requires a non empty strings.xml -->
<string name="ignored_placeholder" tools:ignore="UnusedResources">ignored</string> <string name="ignored_placeholder" tools:ignore="UnusedResources">ignored</string>
<string name="restriction_default_homeserver_url_title">Default homeserver URL</string>
<string name="restriction_default_push_gateway_title">Default Push gateway</string>
<string name="restriction_permalink_base_url_title">Permalink base url</string>
</resources> </resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
<restriction
android:key="im.vector.app.serverConfigDefaultHomeserverUrlString"
android:restrictionType="string"
android:title="@string/restriction_default_homeserver_url_title" />
<restriction
android:key="im.vector.app.serverConfigSygnalAPIUrlString"
android:restrictionType="string"
android:title="@string/restriction_default_push_gateway_title" />
<restriction
android:key="im.vector.app.clientPermalinkBaseUrl"
android:restrictionType="string"
android:title="@string/restriction_permalink_base_url_title" />
</restrictions>

View File

@ -76,6 +76,7 @@ import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.consent.ConsentNotGivenHelper import im.vector.app.features.consent.ConsentNotGivenHelper
import im.vector.app.features.mdm.MdmService
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.pin.PinLocker import im.vector.app.features.pin.PinLocker
import im.vector.app.features.pin.PinMode import im.vector.app.features.pin.PinMode
@ -171,6 +172,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var errorFormatter: ErrorFormatter
@Inject lateinit var mdmService: MdmService
// For debug only // For debug only
@Inject lateinit var debugReceiver: DebugReceiver @Inject lateinit var debugReceiver: DebugReceiver
@ -412,6 +414,10 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
rageShake.start() rageShake.start()
} }
debugReceiver.register(this) debugReceiver.register(this)
mdmService.registerListener(this) {
// Just log that a change occurred.
Timber.w("MDM data has been updated")
}
} }
private val postResumeScheduledActions = mutableListOf<() -> Unit>() private val postResumeScheduledActions = mutableListOf<() -> Unit>()
@ -442,6 +448,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
rageShake.stop() rageShake.stop()
debugReceiver.unregister(this) debugReceiver.unregister(this)
mdmService.unregisterListener(this)
} }
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {

View File

@ -22,6 +22,8 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.AppNameProvider import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.mdm.MdmData
import im.vector.app.features.mdm.MdmService
import org.matrix.android.sdk.api.session.pushers.HttpPusher import org.matrix.android.sdk.api.session.pushers.HttpPusher
import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.Pusher
import java.util.UUID import java.util.UUID
@ -37,6 +39,7 @@ class PushersManager @Inject constructor(
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider, private val appNameProvider: AppNameProvider,
private val getDeviceInfoUseCase: GetDeviceInfoUseCase, private val getDeviceInfoUseCase: GetDeviceInfoUseCase,
private val mdmService: MdmService,
) { ) {
suspend fun testPush() { suspend fun testPush() {
val currentSession = activeSessionHolder.getActiveSession() val currentSession = activeSessionHolder.getActiveSession()
@ -50,7 +53,10 @@ class PushersManager @Inject constructor(
} }
suspend fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID { suspend fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID {
return enqueueRegisterPusher(pushKey, stringProvider.getString(R.string.pusher_http_url)) return enqueueRegisterPusher(
pushKey = pushKey,
gateway = mdmService.getData(MdmData.DefaultPushGatewayUrl, stringProvider.getString(R.string.pusher_http_url))
)
} }
suspend fun enqueueRegisterPusher( suspend fun enqueueRegisterPusher(

View File

@ -24,6 +24,8 @@ import com.squareup.moshi.JsonClass
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.getApplicationLabel import im.vector.app.core.utils.getApplicationLabel
import im.vector.app.features.mdm.MdmData
import im.vector.app.features.mdm.MdmService
import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.cache.CacheStrategy
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
@ -40,6 +42,7 @@ class UnifiedPushHelper @Inject constructor(
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val matrix: Matrix, private val matrix: Matrix,
private val fcmHelper: FcmHelper, private val fcmHelper: FcmHelper,
private val mdmService: MdmService,
) { ) {
@MainThread @MainThread
@ -99,7 +102,12 @@ class UnifiedPushHelper @Inject constructor(
// register app_id type upfcm on sygnal // register app_id type upfcm on sygnal
// the pushkey if FCM key // the pushkey if FCM key
if (UnifiedPush.getDistributor(context) == context.packageName) { if (UnifiedPush.getDistributor(context) == context.packageName) {
unifiedPushStore.storePushGateway(stringProvider.getString(R.string.pusher_http_url)) unifiedPushStore.storePushGateway(
gateway = mdmService.getData(
mdmData = MdmData.DefaultPushGatewayUrl,
defaultValue = stringProvider.getString(R.string.pusher_http_url),
)
)
onDoneRunnable?.run() onDoneRunnable?.run()
return return
} }
@ -185,7 +193,13 @@ class UnifiedPushHelper @Inject constructor(
} }
fun getPushGateway(): String? { fun getPushGateway(): String? {
return if (isEmbeddedDistributor()) stringProvider.getString(R.string.pusher_http_url) return if (isEmbeddedDistributor()) {
else unifiedPushStore.getPushGateway() mdmService.getData(
mdmData = MdmData.DefaultPushGatewayUrl,
defaultValue = stringProvider.getString(R.string.pusher_http_url),
)
} else {
unifiedPushStore.getPushGateway()
}
} }
} }

View File

@ -25,6 +25,7 @@ import javax.inject.Inject
/** /**
* Object to store and retrieve home and identity server urls. * Object to store and retrieve home and identity server urls.
* Note: this class is not used.
*/ */
class ServerUrlsRepository @Inject constructor( class ServerUrlsRepository @Inject constructor(
@DefaultPreferences @DefaultPreferences
@ -89,5 +90,5 @@ class ServerUrlsRepository @Inject constructor(
/** /**
* Return default homeserver url from resources. * Return default homeserver url from resources.
*/ */
fun getDefaultHomeServerUrl() = stringProvider.getString(R.string.matrix_org_server_url) private fun getDefaultHomeServerUrl() = stringProvider.getString(R.string.matrix_org_server_url)
} }

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2023 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 im.vector.app.features.mdm
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.RestrictionsManager
import androidx.core.content.getSystemService
import dagger.hilt.android.qualifiers.ApplicationContext
import timber.log.Timber
import javax.inject.Inject
class DefaultMdmService @Inject constructor(
@ApplicationContext applicationContext: Context
) : MdmService {
private val restrictionsManager = applicationContext.getSystemService<RestrictionsManager>()
private var onChangedListener: (() -> Unit)? = null
private val restrictionsReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Timber.w("Restrictions changed")
onChangedListener?.invoke()
}
}
override fun registerListener(context: Context, onChangedListener: () -> Unit) {
val restrictionsFilter = IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED)
this.onChangedListener = onChangedListener
context.registerReceiver(restrictionsReceiver, restrictionsFilter)
}
override fun unregisterListener(context: Context) {
context.unregisterReceiver(restrictionsReceiver)
this.onChangedListener = null
}
override fun getData(mdmData: MdmData): String? {
return restrictionsManager?.applicationRestrictions?.getString(mdmData.key)
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2023 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 im.vector.app.features.mdm
import android.content.Context
import timber.log.Timber
enum class MdmData(val key: String) {
DefaultHomeserverUrl(key = "im.vector.app.serverConfigDefaultHomeserverUrlString"),
DefaultPushGatewayUrl(key = "im.vector.app.serverConfigSygnalAPIUrlString"),
PermalinkBaseUrl(key = "im.vector.app.clientPermalinkBaseUrl"),
}
interface MdmService {
fun registerListener(context: Context, onChangedListener: () -> Unit)
fun unregisterListener(context: Context)
fun getData(mdmData: MdmData): String?
fun getData(mdmData: MdmData, defaultValue: String): String {
return getData(mdmData)
?.also {
Timber.w("Using MDM data for ${mdmData.name}: $it")
}
?: defaultValue
}
}

View File

@ -46,6 +46,8 @@ import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.login.ServerType import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode import im.vector.app.features.login.SignMode
import im.vector.app.features.mdm.MdmData
import im.vector.app.features.mdm.MdmService
import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -93,6 +95,7 @@ class OnboardingViewModel @AssistedInject constructor(
private val registrationActionHandler: RegistrationActionHandler, private val registrationActionHandler: RegistrationActionHandler,
private val sdkIntProvider: BuildVersionSdkIntProvider, private val sdkIntProvider: BuildVersionSdkIntProvider,
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase, private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
mdmService: MdmService,
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) { ) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -143,7 +146,7 @@ class OnboardingViewModel @AssistedInject constructor(
} }
private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
private val defaultHomeserverUrl = matrixOrgUrl private val defaultHomeserverUrl = mdmService.getData(MdmData.DefaultHomeserverUrl, matrixOrgUrl)
private val registrationWizard: RegistrationWizard private val registrationWizard: RegistrationWizard
get() = authenticationService.getRegistrationWizard() get() = authenticationService.getRegistrationWizard()