Manage new Android 13 notification permission.

This commit is contained in:
Benoit Marty 2022-10-06 18:00:33 +02:00
parent 3952f732dd
commit 652069d520
30 changed files with 241 additions and 92 deletions

View file

@ -635,6 +635,8 @@
<string name="permissions_rationale_msg_record_audio">${app_name} needs permission to access your microphone to perform audio calls.</string>
<!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->
<string name="permissions_rationale_msg_camera_and_audio">${app_name} needs permission to access your camera and your microphone to perform video calls.\n\nPlease allow access on the next pop-ups to be able to make the call.</string>
<!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->
<string name="permissions_rationale_msg_notification">${app_name} needs permission to display notifications. Notifications can display your messages, your invitations, etc.\n\nPlease allow access on the next pop-ups to be able to view notification.</string>
<string name="permissions_denied_qr_code">To scan a QR code, you need to allow camera access.</string>
<string name="permissions_denied_add_contact">Allow permission to access your contacts.</string>
@ -854,7 +856,9 @@
<string name="settings_troubleshoot_test_system_settings_title">System Settings.</string>
<string name="settings_troubleshoot_test_system_settings_success">Notifications are enabled in the system settings.</string>
<string name="settings_troubleshoot_test_system_settings_failed">Notifications are disabled in the system settings.\nPlease check system settings.</string>
<string name="settings_troubleshoot_test_system_settings_permission_failed">${app_name} needs the permission to show notifications.\nPlease grant the permission.</string>
<string name="open_settings">Open Settings</string>
<string name="grant_permission">Grant Permission</string>
<string name="settings_troubleshoot_test_account_settings_title">Account Settings.</string>
<string name="settings_troubleshoot_test_account_settings_success">Notifications are enabled for your account.</string>

25
tools/adb/notification.sh Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
## From https://developer.android.com/develop/ui/views/notifications/notification-permission#test
PACKAGE_NAME=im.vector.app.debug
# App is newly installed on a device that runs Android 13 or higher:
adb shell pm revoke ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed
# The user keeps notifications enabled when the app is installed on a device that runs 12L or lower,
# then the device upgrades to Android 13 or higher:
# adb shell pm grant ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
# adb shell pm set-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
# adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed
# The user manually disables notifications when the app is installed on a device that runs 12L or lower,
# then the device upgrades to Android 13 or higher:
# adb shell pm revoke ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
# adb shell pm set-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
# adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed

View file

@ -22,6 +22,7 @@ import android.os.Build
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.checkPermissions
@ -46,7 +47,15 @@ class DebugPermissionActivity : VectorBaseActivity<ActivityDebugPermissionBindin
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_CONTACTS
)
) + getAndroid13Permissions()
private fun getAndroid13Permissions(): List<String> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
listOf(Manifest.permission.POST_NOTIFICATIONS)
} else {
emptyList()
}
}
private var lastPermissions = emptyList<String>()
@ -77,6 +86,14 @@ class DebugPermissionActivity : VectorBaseActivity<ActivityDebugPermissionBindin
lastPermissions = listOf(Manifest.permission.READ_CONTACTS)
checkPerm()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
views.notification.setOnClickListener {
lastPermissions = listOf(Manifest.permission.POST_NOTIFICATIONS)
checkPerm()
}
} else {
views.notification.isVisible = false
}
}
private fun checkPerm() {

View file

@ -30,43 +30,43 @@
android:id="@+id/camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CAMERA"
android:textAllCaps="false" />
android:text="CAMERA" />
<Button
android:id="@+id/audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RECORD_AUDIO"
android:textAllCaps="false" />
android:text="RECORD_AUDIO" />
<Button
android:id="@+id/camera_audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CAMERA + RECORD_AUDIO"
android:textAllCaps="false" />
android:text="CAMERA + RECORD_AUDIO" />
<Button
android:id="@+id/write"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="WRITE_EXTERNAL_STORAGE"
android:textAllCaps="false" />
android:text="WRITE_EXTERNAL_STORAGE" />
<Button
android:id="@+id/read"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="READ_EXTERNAL_STORAGE"
android:textAllCaps="false" />
android:text="READ_EXTERNAL_STORAGE" />
<Button
android:id="@+id/contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="READ_CONTACTS"
android:textAllCaps="false" />
android:text="READ_CONTACTS" />
<Button
android:id="@+id/notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="POST_NOTIFICATIONS" />
</LinearLayout>

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.VectorPreferences
@ -32,7 +30,7 @@ class TestAutoStartBoot @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
if (vectorPreferences.autoStartOnBoot()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
status = TestStatus.SUCCESS
@ -42,7 +40,7 @@ class TestAutoStartBoot @Inject constructor(
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
override fun doFix() {
vectorPreferences.setAutoStartOnBoot(true)
manager?.retry(activityResultLauncher)
manager?.retry(testParameters)
}
}
status = TestStatus.FAILED

View file

@ -15,9 +15,7 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import android.net.ConnectivityManager
import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.getSystemService
import androidx.core.net.ConnectivityManagerCompat
import androidx.fragment.app.FragmentActivity
@ -32,7 +30,7 @@ class TestBackgroundRestrictions @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
context.getSystemService<ConnectivityManager>()!!.apply {
// Checks if the device is on a metered network
if (isActiveNetworkMetered) {

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
@ -30,7 +28,7 @@ class TestBatteryOptimization @Inject constructor(
private val stringProvider: StringProvider
) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
if (context.isIgnoringBatteryOptimizations()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success)
status = TestStatus.SUCCESS
@ -39,7 +37,7 @@ class TestBatteryOptimization @Inject constructor(
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_failed)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) {
override fun doFix() {
requestDisablingBatteryOptimization(context, activityResultLauncher)
requestDisablingBatteryOptimization(context, testParameters.activityResultLauncher)
}
}
status = TestStatus.FAILED

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import com.google.firebase.messaging.FirebaseMessaging
import im.vector.app.R
@ -36,7 +34,7 @@ class TestFirebaseToken @Inject constructor(
private val fcmHelper: FcmHelper,
) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
status = TestStatus.RUNNING
try {
FirebaseMessaging.getInstance().token
@ -53,7 +51,7 @@ class TestFirebaseToken @Inject constructor(
"ACCOUNT_MISSING" -> {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() {
startAddGoogleAccountIntent(context, activityResultLauncher)
startAddGoogleAccountIntent(context, testParameters.activityResultLauncher)
}
}
stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
@ -35,7 +33,7 @@ class TestPlayServices @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
if (resultCode == ConnectionResult.SUCCESS) {

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
@ -42,7 +40,7 @@ class TestTokenRegistration @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
// Check if we have a registered pusher for this token
val fcmToken = fcmHelper.getFcmToken() ?: run {
status = TestStatus.FAILED
@ -66,9 +64,9 @@ class TestTokenRegistration @Inject constructor(
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
if (workInfo != null) {
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
manager?.retry(activityResultLauncher)
manager?.retry(testParameters)
} else if (workInfo.state == WorkInfo.State.FAILED) {
manager?.retry(activityResultLauncher)
manager?.retry(testParameters)
}
}
})

View file

@ -17,6 +17,9 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- https://developer.android.com/develop/ui/views/notifications/notification-permission#exemptions -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Call feature -->
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<!-- Commented because Google PlayStore does not like we add permission if we are not requiring it. And it was added for future use -->

View file

@ -48,6 +48,7 @@ import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.MainActivity
@ -143,6 +144,7 @@ class HomeActivity :
@Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var nightlyProxy: NightlyProxy
@Inject lateinit var disclaimerDialog: DisclaimerDialog
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager
private var isNewAppLayoutEnabled: Boolean = false // delete once old app layout is removed
@ -172,6 +174,10 @@ class HomeActivity :
}
}
private val postPermissionLauncher = registerForPermissionsResult { _, _ ->
// Nothing to do with the result.
}
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
if (f is MatrixToBottomSheet) {
@ -273,6 +279,7 @@ class HomeActivity :
}
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn()
HomeActivityViewEvents.ShowNotificationDialog -> handleShowNotificationDialog()
HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
@ -288,6 +295,10 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
}
private fun handleShowNotificationDialog() {
notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher)
}
private fun handleShowReleaseNotes() {
startActivity(Intent(this, ReleaseNotesActivity::class.java))
}

View file

@ -31,6 +31,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
object PromptToEnableSessionPush : HomeActivityViewEvents
object ShowAnalyticsOptIn : HomeActivityViewEvents
object ShowNotificationDialog : HomeActivityViewEvents
object ShowReleaseNotes : HomeActivityViewEvents
object NotifyUserForThreadsMigration : HomeActivityViewEvents
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents

View file

@ -143,6 +143,8 @@ class HomeActivityViewModel @AssistedInject constructor(
.onEach { didAskUser ->
if (!didAskUser) {
_viewEvents.post(HomeActivityViewEvents.ShowAnalyticsOptIn)
} else {
_viewEvents.post(HomeActivityViewEvents.ShowNotificationDialog)
}
}
.launchIn(viewModelScope)
@ -162,6 +164,8 @@ class HomeActivityViewModel @AssistedInject constructor(
// do nothing
}
}
} else {
_viewEvents.post(HomeActivityViewEvents.ShowNotificationDialog)
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2022 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.home
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import im.vector.app.R
import im.vector.app.core.utils.checkPermissions
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
import javax.inject.Inject
class NotificationPermissionManager @Inject constructor(
private val sdkIntProvider: BuildVersionSdkIntProvider,
private val vectorPreferences: VectorPreferences,
) {
fun isPermissionGranted(activity: Activity): Boolean {
return if (sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
ContextCompat.checkSelfPermission(
activity,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
} else {
// No notification permission management before API 33.
true
}
}
fun eventuallyRequestPermission(
activity: Activity,
requestPermissionLauncher: ActivityResultLauncher<Array<String>>,
showRationale: Boolean = true,
ignorePreference: Boolean = false,
) {
if (!sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) return
if (!vectorPreferences.areNotificationEnabledForDevice() && !ignorePreference) return
checkPermissions(
listOf(Manifest.permission.POST_NOTIFICATIONS),
activity,
activityResultLauncher = requestPermissionLauncher,
if (showRationale) R.string.permissions_rationale_msg_notification else 0
)
}
@RequiresApi(33)
fun askPermission(requestPermissionLauncher: ActivityResultLauncher<Array<String>>) {
requestPermissionLauncher.launch(
arrayOf(Manifest.permission.POST_NOTIFICATIONS)
)
}
}

View file

@ -43,9 +43,12 @@ import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.core.utils.combineLatest
import im.vector.app.core.utils.isIgnoringBatteryOptimizations
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.requestDisablingBatteryOptimization
import im.vector.app.core.utils.startNotificationSettingsIntent
import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.NotificationPermissionManager
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.BackgroundSyncModeChooserDialog
@ -76,12 +79,24 @@ class VectorSettingsNotificationPreferenceFragment :
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var guardServiceStarter: GuardServiceStarter
@Inject lateinit var vectorFeatures: VectorFeatures
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager
override var titleRes: Int = R.string.settings_notifications
override val preferenceXmlRes = R.xml.vector_settings_notifications
private var interactionListener: VectorSettingsFragmentInteractionListener? = null
private val notificationStartForActivityResult = registerStartForActivityResult { _ ->
// No op
}
private val postPermissionLauncher = registerForPermissionsResult { _, deniedPermanently ->
if (deniedPermanently) {
// Open System setting, to give a chance to the user to enable notification. Sometimes the permission dialog is not displayed
startNotificationSettingsIntent(requireContext(), notificationStartForActivityResult)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = MobileScreen.ScreenName.SettingsNotifications
@ -118,6 +133,12 @@ class VectorSettingsNotificationPreferenceFragment :
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
?.summary = unifiedPushHelper.getCurrentDistributorName()
}
notificationPermissionManager.eventuallyRequestPermission(
requireActivity(),
postPermissionLauncher,
showRationale = false,
ignorePreference = true
)
} else {
unifiedPushHelper.unregister(pushersManager)
session.pushersService().refreshPushers()

View file

@ -34,6 +34,8 @@ import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.startNotificationSettingsIntent
import im.vector.app.databinding.FragmentSettingsNotificationsTroubleshootBinding
import im.vector.app.features.notifications.NotificationActionIds
import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory
@ -76,7 +78,7 @@ class VectorSettingsNotificationsTroubleshootFragment :
}
views.troubleshootRunButton.debouncedClicks {
testManager?.retry(testStartForActivityResult)
testManager?.retry(TroubleshootTest.TestParameters(testStartForActivityResult, testStartForPermissionResult))
}
startUI()
}
@ -125,7 +127,7 @@ class VectorSettingsNotificationsTroubleshootFragment :
}
}
views.troubleshootTestRecyclerView.adapter = testManager?.adapter
testManager?.runDiagnostic(testStartForActivityResult)
testManager?.runDiagnostic(TroubleshootTest.TestParameters(testStartForActivityResult, testStartForPermissionResult))
}
override fun onDestroyView() {
@ -139,8 +141,17 @@ class VectorSettingsNotificationsTroubleshootFragment :
}
}
private val testStartForPermissionResult = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
retry()
} else if (deniedPermanently) {
// Open System setting
startNotificationSettingsIntent(requireContext(), testStartForActivityResult)
}
}
private fun retry() {
testManager?.retry(testStartForActivityResult)
testManager?.retry(TroubleshootTest.TestParameters(testStartForActivityResult, testStartForPermissionResult))
}
override fun onDetach() {

View file

@ -15,10 +15,8 @@
*/
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import android.os.Handler
import android.os.Looper
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.Fragment
import kotlin.properties.Delegates
@ -50,7 +48,7 @@ class NotificationTroubleshootTestManager(val fragment: Fragment) {
test.manager = this
}
fun runDiagnostic(activityResultLauncher: ActivityResultLauncher<Intent>) {
fun runDiagnostic(testParameters: TroubleshootTest.TestParameters) {
if (isCancelled) return
currentTestIndex = 0
val handler = Handler(Looper.getMainLooper())
@ -69,7 +67,7 @@ class NotificationTroubleshootTestManager(val fragment: Fragment) {
// Cosmetic: Start with a small delay for UI/UX reason (better animation effect) for non async tests
handler.postDelayed({
if (fragment.isAdded) {
troubleshootTest.perform(activityResultLauncher)
troubleshootTest.perform(testParameters)
}
}, 600)
} else {
@ -81,18 +79,18 @@ class NotificationTroubleshootTestManager(val fragment: Fragment) {
}
}
if (fragment.isAdded) {
testList.firstOrNull()?.perform(activityResultLauncher)
testList.firstOrNull()?.perform(testParameters)
}
}
fun retry(activityResultLauncher: ActivityResultLauncher<Intent>) {
fun retry(testParameters: TroubleshootTest.TestParameters) {
testList.forEach {
it.cancel()
it.description = null
it.quickFix = null
it.status = TroubleshootTest.TestStatus.NOT_STARTED
}
runDiagnostic(activityResultLauncher)
runDiagnostic(testParameters)
}
fun hasQuickFix(): Boolean {

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.StringProvider
@ -38,7 +36,7 @@ class TestAccountSettings @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
val session = activeSessionHolder.getSafeActiveSession() ?: return
val defaultRule = session.pushRuleService().getPushRules().getAllRules()
.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }
@ -59,7 +57,7 @@ class TestAccountSettings @Inject constructor(
session.pushRuleService().updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled)
}
withContext(Dispatchers.Main) {
manager?.retry(activityResultLauncher)
manager?.retry(testParameters)
}
}
}

View file

@ -16,8 +16,6 @@
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.UnifiedPushHelper
@ -30,7 +28,7 @@ class TestAvailableUnifiedPushDistributors @Inject constructor(
private val fcmHelper: FcmHelper,
) : TroubleshootTest(R.string.settings_troubleshoot_test_distributors_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
val distributors = unifiedPushHelper.getExternalDistributors()
description = if (distributors.isEmpty()) {
stringProvider.getString(

View file

@ -16,8 +16,6 @@
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.resources.StringProvider
@ -28,7 +26,7 @@ class TestCurrentUnifiedPushDistributor @Inject constructor(
private val stringProvider: StringProvider,
) : TroubleshootTest(R.string.settings_troubleshoot_test_current_distributor_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
description = stringProvider.getString(
R.string.settings_troubleshoot_test_current_distributor,
unifiedPushHelper.getCurrentDistributorName()

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.VectorPreferences
@ -31,7 +29,7 @@ class TestDeviceSettings @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
if (vectorPreferences.areNotificationEnabledForDevice()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_device_settings_success)
quickFix = null
@ -40,7 +38,7 @@ class TestDeviceSettings @Inject constructor(
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_device_settings_quickfix) {
override fun doFix() {
vectorPreferences.setNotificationEnabledForDevice(true)
manager?.retry(activityResultLauncher)
manager?.retry(testParameters)
}
}
description = stringProvider.getString(R.string.settings_troubleshoot_test_device_settings_failed)

View file

@ -16,8 +16,6 @@
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
@ -38,7 +36,7 @@ class TestEndpointAsTokenRegistration @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper,
) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_registration_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
// Check if we have a registered pusher for this token
val endpoint = unifiedPushHelper.getEndpointOrToken() ?: run {
status = TestStatus.FAILED
@ -66,9 +64,9 @@ class TestEndpointAsTokenRegistration @Inject constructor(
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
if (workInfo != null) {
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
manager?.retry(activityResultLauncher)
manager?.retry(testParameters)
} else if (workInfo.state == WorkInfo.State.FAILED) {
manager?.retry(activityResultLauncher)
manager?.retry(testParameters)
}
}
})

View file

@ -16,8 +16,6 @@
package im.vector.app.features.settings.troubleshoot
import android.content.Context
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.startNotificationSettingsIntent
@ -34,14 +32,14 @@ class TestNotification @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_notification_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
// Display the notification right now
notificationUtils.displayDiagnosticNotification()
description = stringProvider.getString(R.string.settings_troubleshoot_test_notification_notice)
quickFix = object : TroubleshootQuickFix(R.string.open_settings) {
override fun doFix() {
startNotificationSettingsIntent(context, activityResultLauncher)
startNotificationSettingsIntent(context, testParameters.activityResultLauncher)
}
}

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter
@ -43,7 +41,7 @@ class TestPushFromPushGateway @Inject constructor(
private var action: Job? = null
private var pushReceived: Boolean = false
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
pushReceived = false
action = activeSessionHolder.getActiveSession().coroutineScope.launch {
val result = runCatching { pushersManager.testPush() }

View file

@ -15,8 +15,6 @@
*/
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.StringProvider
@ -39,7 +37,7 @@ class TestPushRulesSettings @Inject constructor(
RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS
)
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
val session = activeSessionHolder.getSafeActiveSession() ?: return
val pushRules = session.pushRuleService().getPushRules().getAllRules()
var oneOrMoreRuleIsOff = false

View file

@ -15,34 +15,44 @@
*/
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.FragmentActivity
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.startNotificationSettingsIntent
import im.vector.app.features.home.NotificationPermissionManager
import javax.inject.Inject
/**
* Checks if notifications are enable in the system settings for this app.
* On Android 13, it will check for the notification permission.
*/
class TestSystemSettings @Inject constructor(
private val context: FragmentActivity,
private val stringProvider: StringProvider
) :
TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) {
private val stringProvider: StringProvider,
private val notificationPermissionManager: NotificationPermissionManager,
) : TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
if (NotificationManagerCompat.from(context).areNotificationsEnabled()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_system_settings_success)
quickFix = null
status = TestStatus.SUCCESS
} else {
description = stringProvider.getString(R.string.settings_troubleshoot_test_system_settings_failed)
quickFix = object : TroubleshootQuickFix(R.string.open_settings) {
override fun doFix() {
startNotificationSettingsIntent(context, activityResultLauncher)
if (notificationPermissionManager.isPermissionGranted(context)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_system_settings_failed)
quickFix = object : TroubleshootQuickFix(R.string.open_settings) {
override fun doFix() {
startNotificationSettingsIntent(context, testParameters.activityResultLauncher)
}
}
} else {
// In this case, we can ask for user permission
description = stringProvider.getString(R.string.settings_troubleshoot_test_system_settings_permission_failed)
quickFix = object : TroubleshootQuickFix(R.string.grant_permission) {
override fun doFix() {
notificationPermissionManager.askPermission(testParameters.permissionResultLauncher)
}
}
}
status = TestStatus.FAILED

View file

@ -16,8 +16,6 @@
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.resources.StringProvider
@ -28,7 +26,7 @@ class TestUnifiedPushEndpoint @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper,
) : TroubleshootTest(R.string.settings_troubleshoot_test_current_endpoint_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
val endpoint = unifiedPushHelper.getPrivacyFriendlyUpEndpoint()
if (endpoint != null) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_current_endpoint_success, endpoint)

View file

@ -16,8 +16,6 @@
package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.resources.StringProvider
@ -28,7 +26,7 @@ class TestUnifiedPushGateway @Inject constructor(
private val stringProvider: StringProvider
) : TroubleshootTest(R.string.settings_troubleshoot_test_current_gateway_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
override fun perform(testParameters: TestParameters) {
description = stringProvider.getString(
R.string.settings_troubleshoot_test_current_gateway,
unifiedPushHelper.getPushGateway()

View file

@ -22,6 +22,11 @@ import kotlin.properties.Delegates
abstract class TroubleshootTest(@StringRes val titleResId: Int) {
data class TestParameters(
val activityResultLauncher: ActivityResultLauncher<Intent>,
val permissionResultLauncher: ActivityResultLauncher<Array<String>>
)
enum class TestStatus {
NOT_STARTED,
RUNNING,
@ -40,7 +45,7 @@ abstract class TroubleshootTest(@StringRes val titleResId: Int) {
var manager: NotificationTroubleshootTestManager? = null
abstract fun perform(activityResultLauncher: ActivityResultLauncher<Intent>)
abstract fun perform(testParameters: TestParameters)
fun isFinished(): Boolean = (status == TestStatus.FAILED || status == TestStatus.SUCCESS)