diff --git a/changelog.d/8698.feature b/changelog.d/8698.feature
new file mode 100644
index 0000000000..efe11f03cb
--- /dev/null
+++ b/changelog.d/8698.feature
@@ -0,0 +1,5 @@
+Add support for Mobile Device Management.
+The keys are:
+- default homeserver URL `im.vector.app.serverConfigDefaultHomeserverUrlString`
+- push gateway URL `im.vector.app.serverConfigSygnalAPIUrlString`
+- permalink base URL `im.vector.app.clientPermalinkBaseUrl`
diff --git a/vector-app/src/gplay/java/im/vector/app/push/fcm/VectorFirebaseMessagingService.kt b/vector-app/src/gplay/java/im/vector/app/push/fcm/VectorFirebaseMessagingService.kt
index 6ab9b90a84..7a7f14d690 100644
--- a/vector-app/src/gplay/java/im/vector/app/push/fcm/VectorFirebaseMessagingService.kt
+++ b/vector-app/src/gplay/java/im/vector/app/push/fcm/VectorFirebaseMessagingService.kt
@@ -26,6 +26,8 @@ import im.vector.app.core.pushers.PushParser
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
@@ -46,6 +48,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
@Inject lateinit var pushParser: PushParser
@Inject lateinit var vectorPushHandler: VectorPushHandler
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
+ @Inject lateinit var mdmService: MdmService
private val scope = CoroutineScope(SupervisorJob())
@@ -53,6 +56,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
scope.cancel()
super.onDestroy()
}
+
override fun onNewToken(token: String) {
Timber.tag(loggerTag.value).d("New Firebase token")
fcmHelper.storeFcmToken(token)
@@ -62,7 +66,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
unifiedPushHelper.isEmbeddedDistributor()
) {
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),
+ ),
+ )
}
}
}
diff --git a/vector-app/src/main/AndroidManifest.xml b/vector-app/src/main/AndroidManifest.xml
index 68325ab512..3668296382 100644
--- a/vector-app/src/main/AndroidManifest.xml
+++ b/vector-app/src/main/AndroidManifest.xml
@@ -20,6 +20,10 @@
tools:ignore="UnusedAttribute"
tools:replace="android:allowBackup">
+
+
ignored
+
+ Default homeserver URL
+ Default Push gateway
+ Permalink base url
diff --git a/vector-app/src/main/res/xml/vector_app_restrictions.xml b/vector-app/src/main/res/xml/vector_app_restrictions.xml
new file mode 100644
index 0000000000..1932d19e5c
--- /dev/null
+++ b/vector-app/src/main/res/xml/vector_app_restrictions.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 1e29dfff5e..5bdd960e00 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -76,6 +76,7 @@ import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.configuration.VectorConfiguration
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.pin.PinLocker
import im.vector.app.features.pin.PinMode
@@ -171,6 +172,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var errorFormatter: ErrorFormatter
+ @Inject lateinit var mdmService: MdmService
// For debug only
@Inject lateinit var debugReceiver: DebugReceiver
@@ -412,6 +414,10 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
rageShake.start()
}
debugReceiver.register(this)
+ mdmService.registerListener(this) {
+ // Just log that a change occurred.
+ Timber.w("MDM data has been updated")
+ }
}
private val postResumeScheduledActions = mutableListOf<() -> Unit>()
@@ -442,6 +448,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
rageShake.stop()
debugReceiver.unregister(this)
+ mdmService.unregisterListener(this)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
index 402471ecef..1f2441622d 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
@@ -22,6 +22,8 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.LocaleProvider
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.Pusher
import java.util.UUID
@@ -37,6 +39,7 @@ class PushersManager @Inject constructor(
private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider,
private val getDeviceInfoUseCase: GetDeviceInfoUseCase,
+ private val mdmService: MdmService,
) {
suspend fun testPush() {
val currentSession = activeSessionHolder.getActiveSession()
@@ -50,7 +53,10 @@ class PushersManager @Inject constructor(
}
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(
diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt
index 07052c7146..5b6d91e354 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt
@@ -24,6 +24,8 @@ import com.squareup.moshi.JsonClass
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
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.cache.CacheStrategy
import org.matrix.android.sdk.api.failure.Failure
@@ -40,6 +42,7 @@ class UnifiedPushHelper @Inject constructor(
private val stringProvider: StringProvider,
private val matrix: Matrix,
private val fcmHelper: FcmHelper,
+ private val mdmService: MdmService,
) {
@MainThread
@@ -99,7 +102,12 @@ class UnifiedPushHelper @Inject constructor(
// register app_id type upfcm on sygnal
// the pushkey if FCM key
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()
return
}
@@ -185,7 +193,13 @@ class UnifiedPushHelper @Inject constructor(
}
fun getPushGateway(): String? {
- return if (isEmbeddedDistributor()) stringProvider.getString(R.string.pusher_http_url)
- else unifiedPushStore.getPushGateway()
+ return if (isEmbeddedDistributor()) {
+ mdmService.getData(
+ mdmData = MdmData.DefaultPushGatewayUrl,
+ defaultValue = stringProvider.getString(R.string.pusher_http_url),
+ )
+ } else {
+ unifiedPushStore.getPushGateway()
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt b/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt
index 636c557da9..5b1496028d 100644
--- a/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt
+++ b/vector/src/main/java/im/vector/app/features/homeserver/ServerUrlsRepository.kt
@@ -25,6 +25,7 @@ import javax.inject.Inject
/**
* Object to store and retrieve home and identity server urls.
+ * Note: this class is not used.
*/
class ServerUrlsRepository @Inject constructor(
@DefaultPreferences
@@ -89,5 +90,5 @@ class ServerUrlsRepository @Inject constructor(
/**
* 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)
}
diff --git a/vector/src/main/java/im/vector/app/features/mdm/DefaultMdmService.kt b/vector/src/main/java/im/vector/app/features/mdm/DefaultMdmService.kt
new file mode 100644
index 0000000000..6633f3ce06
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/mdm/DefaultMdmService.kt
@@ -0,0 +1,58 @@
+/*
+ * 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
+import javax.inject.Singleton
+
+@Singleton
+class DefaultMdmService @Inject constructor(
+ @ApplicationContext applicationContext: Context
+) : MdmService {
+ private val restrictionsManager = applicationContext.getSystemService()
+ 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)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/mdm/MdmService.kt b/vector/src/main/java/im/vector/app/features/mdm/MdmService.kt
new file mode 100644
index 0000000000..d601c1658a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/mdm/MdmService.kt
@@ -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
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
index fc6878ffd2..74427736d7 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
@@ -46,6 +46,8 @@ import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.login.ServerType
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.StartAuthenticationFlowUseCase.StartAuthenticationResult
import kotlinx.coroutines.Job
@@ -93,6 +95,7 @@ class OnboardingViewModel @AssistedInject constructor(
private val registrationActionHandler: RegistrationActionHandler,
private val sdkIntProvider: BuildVersionSdkIntProvider,
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
+ mdmService: MdmService,
) : VectorViewModel(initialState) {
@AssistedFactory
@@ -143,7 +146,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
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
get() = authenticationService.getRegistrationWizard()
diff --git a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt
index 4b6063fb93..48850c13c5 100644
--- a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt
+++ b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt
@@ -17,6 +17,7 @@
package im.vector.app.core.pushers
import im.vector.app.R
+import im.vector.app.features.mdm.NoOpMdmService
import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakeAppNameProvider
import im.vector.app.test.fakes.FakeGetDeviceInfoUseCase
@@ -54,6 +55,7 @@ class PushersManagerTest {
stringProvider.instance,
appNameProvider,
getDeviceInfoUseCase,
+ NoOpMdmService(),
)
@Test
diff --git a/vector/src/test/java/im/vector/app/features/mdm/NoOpMdmService.kt b/vector/src/test/java/im/vector/app/features/mdm/NoOpMdmService.kt
new file mode 100644
index 0000000000..a8531d8c7f
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/mdm/NoOpMdmService.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+class NoOpMdmService : MdmService {
+ override fun registerListener(context: Context, onChangedListener: () -> Unit) = Unit
+ override fun unregisterListener(context: Context) = Unit
+ override fun getData(mdmData: MdmData): String? = null
+}
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
index a4afab8488..918452b6af 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
@@ -26,6 +26,7 @@ import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode
+import im.vector.app.features.mdm.NoOpMdmService
import im.vector.app.features.onboarding.RegistrationStateFixture.aRegistrationState
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
import im.vector.app.test.TestBuildVersionSdkIntProvider
@@ -1121,6 +1122,7 @@ class OnboardingViewModelTest {
fakeRegistrationActionHandler.instance,
TestBuildVersionSdkIntProvider().also { it.value = Build.VERSION_CODES.O },
fakeConfigureAndStartSessionUseCase,
+ NoOpMdmService()
).also {
viewModel = it
initialState = state