From d2e342b04fb22a2ee8c765fbca482b6048a94d4d Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 19 Dec 2018 18:42:06 +0100 Subject: [PATCH] Notification Troubleshoot v1 Added notification troubleshoot page in settings, that runs diagnostics tests with quickfixes when available. --- .gitignore | 2 + .../java/im/vector/push/fcm/FcmHelper.java | 4 - ...ificationTroubleshootTestManagerFactory.kt | 43 +++++ .../fcm/troubleshoot/TestFirebaseToken.kt | 53 ++++++ .../push/fcm/troubleshoot/TestPlayServices.kt | 55 ++++++ .../fcm/troubleshoot/TestTokenRegistration.kt | 56 ++++++ ...ificationTroubleshootTestManagerFactory.kt | 45 +++++ .../fcm/troubleshoot/TestAutoStartBoot.kt | 44 +++++ .../TestBackgroundRestrictions.kt | 71 +++++++ .../troubleshoot/TestBatteryOptimization.kt | 46 +++++ .../TestNotificationServiceRunning.kt | 37 ++++ .../fcm/troubleshoot/TestServiceRestart.kt | 75 ++++++++ .../im/vector/activity/SplashActivity.java | 25 +-- .../vector/activity/VectorSettingsActivity.kt | 66 ++++++- ...ttingsNotificationsTroubleshootFragment.kt | 174 ++++++++++++++++++ ...ficationTroubleshootRecyclerViewAdapter.kt | 127 +++++++++++++ .../NotificationTroubleshootTestManager.kt | 101 ++++++++++ .../troubleshoot/TestAccountSettings.kt | 61 ++++++ .../troubleshoot/TestDeviceSettings.kt | 45 +++++ .../troubleshoot/TestSystemSettings.kt | 45 +++++ .../troubleshoot/TroubleshootTest.kt | 54 ++++++ .../main/java/im/vector/push/PushManager.java | 26 ++- .../im/vector/util/PreferencesManager.java | 1 + .../main/java/im/vector/util/SystemUtils.kt | 24 +++ .../src/main/res/drawable-hdpi/unit_test.png | Bin 0 -> 684 bytes .../main/res/drawable-hdpi/unit_test_ko.png | Bin 0 -> 577 bytes .../main/res/drawable-hdpi/unit_test_ok.png | Bin 0 -> 838 bytes .../src/main/res/drawable-mdpi/unit_test.png | Bin 0 -> 411 bytes .../main/res/drawable-mdpi/unit_test_ko.png | Bin 0 -> 388 bytes .../main/res/drawable-mdpi/unit_test_ok.png | Bin 0 -> 548 bytes .../src/main/res/drawable-xhdpi/unit_test.png | Bin 0 -> 893 bytes .../main/res/drawable-xhdpi/unit_test_ko.png | Bin 0 -> 742 bytes .../main/res/drawable-xhdpi/unit_test_ok.png | Bin 0 -> 1119 bytes .../main/res/drawable-xxhdpi/unit_test.png | Bin 0 -> 1334 bytes .../main/res/drawable-xxhdpi/unit_test_ko.png | Bin 0 -> 1133 bytes .../main/res/drawable-xxhdpi/unit_test_ok.png | Bin 0 -> 1621 bytes .../main/res/drawable-xxxhdpi/unit_test.png | Bin 0 -> 1792 bytes .../res/drawable-xxxhdpi/unit_test_ko.png | Bin 0 -> 1473 bytes .../res/drawable-xxxhdpi/unit_test_ok.png | Bin 0 -> 2190 bytes ...nt_settings_notifications_troubleshoot.xml | 91 +++++++++ .../layout/item_notification_troubleshoot.xml | 95 ++++++++++ vector/src/main/res/values/strings.xml | 62 ++++++- .../res/xml/vector_settings_preferences.xml | 5 + 43 files changed, 1496 insertions(+), 37 deletions(-) create mode 100644 vector/src/app/java/im/vector/push/fcm/NotificationTroubleshootTestManagerFactory.kt create mode 100644 vector/src/app/java/im/vector/push/fcm/troubleshoot/TestFirebaseToken.kt create mode 100644 vector/src/app/java/im/vector/push/fcm/troubleshoot/TestPlayServices.kt create mode 100644 vector/src/app/java/im/vector/push/fcm/troubleshoot/TestTokenRegistration.kt create mode 100644 vector/src/appfdroid/java/im/vector/push/fcm/NotificationTroubleshootTestManagerFactory.kt create mode 100644 vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestAutoStartBoot.kt create mode 100644 vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestBackgroundRestrictions.kt create mode 100644 vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestBatteryOptimization.kt create mode 100644 vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestNotificationServiceRunning.kt create mode 100644 vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestServiceRestart.kt create mode 100644 vector/src/main/java/im/vector/fragments/VectorSettingsNotificationsTroubleshootFragment.kt create mode 100644 vector/src/main/java/im/vector/fragments/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt create mode 100644 vector/src/main/java/im/vector/fragments/troubleshoot/NotificationTroubleshootTestManager.kt create mode 100644 vector/src/main/java/im/vector/fragments/troubleshoot/TestAccountSettings.kt create mode 100644 vector/src/main/java/im/vector/fragments/troubleshoot/TestDeviceSettings.kt create mode 100644 vector/src/main/java/im/vector/fragments/troubleshoot/TestSystemSettings.kt create mode 100644 vector/src/main/java/im/vector/fragments/troubleshoot/TroubleshootTest.kt create mode 100644 vector/src/main/res/drawable-hdpi/unit_test.png create mode 100644 vector/src/main/res/drawable-hdpi/unit_test_ko.png create mode 100644 vector/src/main/res/drawable-hdpi/unit_test_ok.png create mode 100644 vector/src/main/res/drawable-mdpi/unit_test.png create mode 100644 vector/src/main/res/drawable-mdpi/unit_test_ko.png create mode 100644 vector/src/main/res/drawable-mdpi/unit_test_ok.png create mode 100644 vector/src/main/res/drawable-xhdpi/unit_test.png create mode 100644 vector/src/main/res/drawable-xhdpi/unit_test_ko.png create mode 100644 vector/src/main/res/drawable-xhdpi/unit_test_ok.png create mode 100644 vector/src/main/res/drawable-xxhdpi/unit_test.png create mode 100644 vector/src/main/res/drawable-xxhdpi/unit_test_ko.png create mode 100644 vector/src/main/res/drawable-xxhdpi/unit_test_ok.png create mode 100644 vector/src/main/res/drawable-xxxhdpi/unit_test.png create mode 100644 vector/src/main/res/drawable-xxxhdpi/unit_test_ko.png create mode 100644 vector/src/main/res/drawable-xxxhdpi/unit_test_ok.png create mode 100644 vector/src/main/res/layout/fragment_settings_notifications_troubleshoot.xml create mode 100644 vector/src/main/res/layout/item_notification_troubleshoot.xml diff --git a/.gitignore b/.gitignore index 01b0a56aa1..7e54476492 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ /local.properties /tmp + +captures/ diff --git a/vector/src/app/java/im/vector/push/fcm/FcmHelper.java b/vector/src/app/java/im/vector/push/fcm/FcmHelper.java index e376f4e407..49358c87ef 100755 --- a/vector/src/app/java/im/vector/push/fcm/FcmHelper.java +++ b/vector/src/app/java/im/vector/push/fcm/FcmHelper.java @@ -114,10 +114,6 @@ private static boolean checkPlayServices(Activity activity) { GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); int resultCode = apiAvailability.isGooglePlayServicesAvailable(activity); if (resultCode != ConnectionResult.SUCCESS) { - if (apiAvailability.isUserResolvableError(resultCode)) { - apiAvailability.getErrorDialog(activity, resultCode, 9000 /*hey does the magic number*/) - .show(); - } return false; } return true; diff --git a/vector/src/app/java/im/vector/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/app/java/im/vector/push/fcm/NotificationTroubleshootTestManagerFactory.kt new file mode 100644 index 0000000000..63be2e51b5 --- /dev/null +++ b/vector/src/app/java/im/vector/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2018 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.push.fcm + +import android.support.v4.app.Fragment +import im.vector.fragments.troubleshoot.NotificationTroubleshootTestManager +import im.vector.fragments.troubleshoot.TestAccountSettings +import im.vector.fragments.troubleshoot.TestDeviceSettings +import im.vector.fragments.troubleshoot.TestSystemSettings +import im.vector.push.fcm.troubleshoot.* +import org.matrix.androidsdk.MXSession + +class NotificationTroubleshootTestManagerFactory { + + companion object { + fun createTestManager(fragment: Fragment, session: MXSession?): NotificationTroubleshootTestManager { + val mgr = NotificationTroubleshootTestManager(fragment) + mgr.addTest(TestSystemSettings(fragment)) + if (session != null) { + mgr.addTest(TestAccountSettings(fragment, session)) + } + mgr.addTest(TestDeviceSettings(fragment)) + mgr.addTest(TestPlayServices(fragment)) + mgr.addTest(TestFirebaseToken(fragment)) + mgr.addTest(TestTokenRegistration(fragment)) + return mgr + } + } + +} \ No newline at end of file diff --git a/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestFirebaseToken.kt b/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestFirebaseToken.kt new file mode 100644 index 0000000000..ec7a4ac844 --- /dev/null +++ b/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestFirebaseToken.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2018 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.push.fcm.troubleshoot + +import android.support.v4.app.Fragment +import com.google.firebase.iid.FirebaseInstanceId +import im.vector.R +import im.vector.fragments.troubleshoot.TroubleshootTest +import org.matrix.androidsdk.util.Log + +/* +* Test that app can successfully retrieve a token via firebase + */ +class TestFirebaseToken(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) { + + override fun perform() { + status = TestStatus.RUNNING + fragment.activity?.let { fragmentActivity -> + FirebaseInstanceId.getInstance().instanceId + .addOnCompleteListener(fragmentActivity) { task -> + if (!task.isSuccessful) { + val errorMsg = if (task.exception == null) "Unknown" else task.exception!!.localizedMessage + description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed, errorMsg) + status = TestStatus.FAILED + + } else { + task.result?.token?.let { + val tok = it.substring(0, Math.min(8, it.length)) + "********************" + description = fragment.getString(R.string.settings_troubleshoot_test_fcm_success, tok) + Log.e(this::class.java.simpleName, "Retrieved FCM token success [$it].") + } + status = TestStatus.SUCCESS + } + } + } ?: run { + status = TestStatus.FAILED + } + } + +} \ No newline at end of file diff --git a/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestPlayServices.kt b/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestPlayServices.kt new file mode 100644 index 0000000000..1937e9c2d1 --- /dev/null +++ b/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestPlayServices.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2018 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.push.fcm.troubleshoot + +import android.support.v4.app.Fragment +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import im.vector.R +import im.vector.fragments.troubleshoot.TroubleshootTest +import org.matrix.androidsdk.util.Log + +/* +* Check that the play services APK is available an up-to-date. If needed provide quick fix to install it. + */ +class TestPlayServices(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) { + + override fun perform() { + val apiAvailability = GoogleApiAvailability.getInstance() + val resultCode = apiAvailability.isGooglePlayServicesAvailable(fragment.context) + if (resultCode == ConnectionResult.SUCCESS) { + quickFix = null + description = fragment.getString(R.string.settings_troubleshoot_test_play_services_success) + status = TestStatus.SUCCESS + } else { + if (apiAvailability.isUserResolvableError(resultCode)) { + quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_play_services_quickfix) { + override fun doFix() { + fragment.activity?.let { + apiAvailability.getErrorDialog(it, resultCode, 9000 /*hey does the magic number*/).show() + } + } + } + Log.e(this::javaClass.name, "Play Services apk error $resultCode -> ${apiAvailability.getErrorString(resultCode)}.") + } + + description = fragment.getString(R.string.settings_troubleshoot_test_play_services_failed, apiAvailability.getErrorString(resultCode)) + status = TestStatus.FAILED + } + } + +} + diff --git a/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestTokenRegistration.kt b/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestTokenRegistration.kt new file mode 100644 index 0000000000..47a736a28c --- /dev/null +++ b/vector/src/app/java/im/vector/push/fcm/troubleshoot/TestTokenRegistration.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2018 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.push.fcm.troubleshoot + +import android.support.v4.app.Fragment +import im.vector.Matrix +import im.vector.R +import im.vector.VectorApp +import im.vector.fragments.troubleshoot.TroubleshootTest +import org.matrix.androidsdk.rest.callback.ApiCallback +import org.matrix.androidsdk.rest.model.MatrixError +import java.lang.Exception + +/** + * Force registration of the token to HomeServer + */ +class TestTokenRegistration(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) { + + override fun perform() { + Matrix.getInstance(VectorApp.getInstance().baseContext).pushManager.forceSessionsRegistration(object : ApiCallback { + override fun onSuccess(info: Void?) { + description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_success) + status = TestStatus.SUCCESS + } + + override fun onNetworkError(e: Exception?) { + description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_failed, e?.localizedMessage) + status = TestStatus.FAILED + } + + override fun onMatrixError(e: MatrixError?) { + description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_failed, e?.localizedMessage) + status = TestStatus.FAILED + } + + override fun onUnexpectedError(e: Exception?) { + description = fragment.getString(R.string.settings_troubleshoot_test_token_registration_failed, e?.localizedMessage) + status = TestStatus.FAILED + } + }) + } + +} \ No newline at end of file diff --git a/vector/src/appfdroid/java/im/vector/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/appfdroid/java/im/vector/push/fcm/NotificationTroubleshootTestManagerFactory.kt new file mode 100644 index 0000000000..04f209fa6e --- /dev/null +++ b/vector/src/appfdroid/java/im/vector/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2018 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.push.fcm + +import android.support.v4.app.Fragment +import im.vector.fragments.troubleshoot.NotificationTroubleshootTestManager +import im.vector.fragments.troubleshoot.TestAccountSettings +import im.vector.fragments.troubleshoot.TestDeviceSettings +import im.vector.fragments.troubleshoot.TestSystemSettings +import im.vector.push.fcm.troubleshoot.* +import org.matrix.androidsdk.MXSession + +class NotificationTroubleshootTestManagerFactory { + + companion object { + fun createTestManager(fragment: Fragment, session: MXSession?): NotificationTroubleshootTestManager { + val mgr = NotificationTroubleshootTestManager(fragment) + mgr.addTest(TestSystemSettings(fragment)) + if (session != null) { + mgr.addTest(TestAccountSettings(fragment, session)) + } + mgr.addTest(TestDeviceSettings(fragment)) + mgr.addTest(TestNotificationServiceRunning(fragment)) + mgr.addTest(TestServiceRestart(fragment)) + mgr.addTest(TestAutoStartBoot(fragment)) + mgr.addTest(TestBackgroundRestrictions(fragment)) + mgr.addTest(TestBatteryOptimization(fragment)) + return mgr + } + } + +} \ No newline at end of file diff --git a/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestAutoStartBoot.kt b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestAutoStartBoot.kt new file mode 100644 index 0000000000..dc95dc66a9 --- /dev/null +++ b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestAutoStartBoot.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2018 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.push.fcm.troubleshoot + +import android.support.v4.app.Fragment +import im.vector.R +import im.vector.fragments.troubleshoot.TroubleshootTest +import im.vector.util.PreferencesManager + +/** + * Test that the application is started on boot + */ +class TestAutoStartBoot(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) { + + override fun perform() { + if (PreferencesManager.autoStartOnBoot(fragment.context)) { + description = fragment.getString(R.string.settings_troubleshoot_test_service_boot_success) + status = TestStatus.SUCCESS + quickFix = null + } else { + description = fragment.getString(R.string.settings_troubleshoot_test_service_boot_failed) + quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) { + override fun doFix() { + PreferencesManager.setAutoStartOnBoot(fragment.context, true) + manager?.retry() + } + } + status = TestStatus.FAILED + } + } +} \ No newline at end of file diff --git a/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestBackgroundRestrictions.kt b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestBackgroundRestrictions.kt new file mode 100644 index 0000000000..bac357e92c --- /dev/null +++ b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestBackgroundRestrictions.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2018 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.push.fcm.troubleshoot + +import android.content.Context +import android.net.ConnectivityManager +import android.support.v4.app.Fragment +import android.support.v4.net.ConnectivityManagerCompat +import im.vector.R +import im.vector.fragments.troubleshoot.TroubleshootTest + +class TestBackgroundRestrictions(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) { + + override fun perform() { + (fragment.context!!.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).apply { + // Checks if the device is on a metered network + if (isActiveNetworkMetered) { + // Checks user’s Data Saver settings. + val restrictBackgroundStatus = ConnectivityManagerCompat.getRestrictBackgroundStatus(this) + when (restrictBackgroundStatus) { + ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED -> { + // Background data usage is blocked for this app. Wherever possible, + // the app should also use less data in the foreground. + description = fragment.getString(R.string.settings_troubleshoot_test_bg_restricted_failed, + "RESTRICT_BACKGROUND_STATUS_ENABLED") + status = TestStatus.FAILED + quickFix = null + } + ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED -> { + // The app is whitelisted. Wherever possible, + // the app should use less data in the foreground and background. + description = fragment.getString(R.string.settings_troubleshoot_test_bg_restricted_success, + "RESTRICT_BACKGROUND_STATUS_WHITELISTED") + status = TestStatus.SUCCESS + quickFix = null + } + ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED -> { + // Data Saver is disabled. Since the device is connected to a + // metered network, the app should use less data wherever possible. + description = fragment.getString(R.string.settings_troubleshoot_test_bg_restricted_success, + "RESTRICT_BACKGROUND_STATUS_DISABLED") + status = TestStatus.SUCCESS + quickFix = null + } + + } + + } else { + // The device is not on a metered network. + // Use data as required to perform syncs, downloads, and updates. + description = fragment.getString(R.string.settings_troubleshoot_test_bg_restricted_success, "") + status = TestStatus.SUCCESS + quickFix = null + } + } + } + +} \ No newline at end of file diff --git a/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestBatteryOptimization.kt b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestBatteryOptimization.kt new file mode 100644 index 0000000000..10e31cc135 --- /dev/null +++ b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestBatteryOptimization.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2018 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.push.fcm.troubleshoot + +import android.support.v4.app.Fragment +import im.vector.R +import im.vector.fragments.troubleshoot.NotificationTroubleshootTestManager +import im.vector.fragments.troubleshoot.TroubleshootTest +import im.vector.util.isIgnoringBatteryOptimizations +import im.vector.util.requestDisablingBatteryOptimization + +class TestBatteryOptimization(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) { + + override fun perform() { + + if (fragment.context != null && isIgnoringBatteryOptimizations(fragment.context!!)) { + description = fragment.getString(R.string.settings_troubleshoot_test_battery_success) + status = TestStatus.SUCCESS + quickFix = null + } else { + description = fragment.getString(R.string.settings_troubleshoot_test_battery_failed) + quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) { + override fun doFix() { + fragment.activity?.let { + requestDisablingBatteryOptimization(it, NotificationTroubleshootTestManager.REQ_CODE_FIX) + } + } + } + status = TestStatus.FAILED + } + } + +} \ No newline at end of file diff --git a/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestNotificationServiceRunning.kt b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestNotificationServiceRunning.kt new file mode 100644 index 0000000000..c48701c2c2 --- /dev/null +++ b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestNotificationServiceRunning.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2018 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.push.fcm.troubleshoot + +import android.support.v4.app.Fragment +import im.vector.R +import im.vector.fragments.troubleshoot.TroubleshootTest +import im.vector.services.EventStreamService + +class TestNotificationServiceRunning(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_foreground_service_started_title) { + + override fun perform() { + if (EventStreamService.isStopped()) { + description = fragment.getString(R.string.settings_troubleshoot_test_foreground_service_started_failed) + status = TestStatus.FAILED + quickFix = null + } else { + description = fragment.getString(R.string.settings_troubleshoot_test_foreground_service_startedt_success) + quickFix = null + status = TestStatus.SUCCESS + } + } + +} \ No newline at end of file diff --git a/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestServiceRestart.kt b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestServiceRestart.kt new file mode 100644 index 0000000000..2cca9833ed --- /dev/null +++ b/vector/src/appfdroid/java/im/vector/push/fcm/troubleshoot/TestServiceRestart.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2018 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.push.fcm.troubleshoot + +import android.app.ActivityManager +import android.content.Context +import android.support.v4.app.Fragment +import im.vector.R +import im.vector.VectorApp +import im.vector.fragments.troubleshoot.TroubleshootTest +import im.vector.services.EventStreamService +import java.util.* +import kotlin.concurrent.timerTask + + +/** + * Stop the event stream service and check that it is restarted + */ +class TestServiceRestart(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_service_restart_title) { + + var timer: Timer? = null + + override fun perform() { + status = TestStatus.RUNNING + EventStreamService.getInstance()?.stopSelf() + timer = Timer() + timer?.schedule(timerTask { + if (isMyServiceRunning(EventStreamService::class.java)) { + fragment.activity?.runOnUiThread { + description = fragment.getString(R.string.settings_troubleshoot_test_service_restart_success) + quickFix = null + status = TestStatus.SUCCESS + } + timer?.cancel() + } + }, 0, 1000) + + timer?.schedule(timerTask { + fragment.activity?.runOnUiThread { + status = TestStatus.FAILED + description = fragment.getString(R.string.settings_troubleshoot_test_service_restart_failed) + } + timer?.cancel() + }, 15000) + } + + + private fun isMyServiceRunning(serviceClass: Class<*>): Boolean { + val manager = VectorApp.getInstance().baseContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + for (service in manager.getRunningServices(Integer.MAX_VALUE)) { + if (serviceClass.name == service.service.className) { + return true + } + } + return false + } + + override fun cancel() { + super.cancel() + timer?.cancel() + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/activity/SplashActivity.java b/vector/src/main/java/im/vector/activity/SplashActivity.java index 41b7c8e907..2e84a2587d 100755 --- a/vector/src/main/java/im/vector/activity/SplashActivity.java +++ b/vector/src/main/java/im/vector/activity/SplashActivity.java @@ -45,7 +45,6 @@ import im.vector.VectorApp; import im.vector.analytics.TrackingEvent; import im.vector.push.PushManager; -import im.vector.push.fcm.FcmHelper; import im.vector.receiver.VectorUniversalLinkReceiver; import im.vector.services.EventStreamService; import im.vector.util.PreferencesManager; @@ -321,29 +320,7 @@ public void onInitialSyncComplete(String toToken) { // trigger the push registration if required PushManager pushManager = Matrix.getInstance(getApplicationContext()).getPushManager(); - - if (pushManager.isFcmRegistered()) { - - //Issue #2266 It might be possible that the FCMHelper saved token is different - //than the push manager saved token, and that the pushManager is not aware. - //And as per current code the pushMgr saved token is sent at each startup (resume?) - //So anyway, might be a good thing to check that it is synced? - //Very defensive code but, ya know :/ - String fcmToken = FcmHelper.getFcmToken(this); - String pushMgrSavedToken = pushManager.getCurrentRegistrationToken(); - - boolean savedTokenAreDifferent = pushMgrSavedToken == null ? fcmToken != null : !pushMgrSavedToken.equals(fcmToken); - if (savedTokenAreDifferent) { - Log.e(LOG_TAG, "SAVED NOTIFICATION TOKEN NOT IN SYNC"); - pushManager.resetFCMRegistration(fcmToken); - } else { - pushManager.forceSessionsRegistration(null); - } - - - } else { - pushManager.checkRegistrations(); - } + pushManager.deepCheckRegistration(this); boolean noUpdate; diff --git a/vector/src/main/java/im/vector/activity/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/activity/VectorSettingsActivity.kt index 59b791ea1d..c6b8d9161b 100755 --- a/vector/src/main/java/im/vector/activity/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/activity/VectorSettingsActivity.kt @@ -17,24 +17,29 @@ package im.vector.activity import android.content.Context import android.content.Intent +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v7.preference.Preference +import android.support.v7.preference.PreferenceFragmentCompat import im.vector.Matrix import im.vector.R +import im.vector.fragments.VectorSettingsNotificationsTroubleshootFragment import im.vector.fragments.VectorSettingsPreferencesFragment +import im.vector.util.PreferencesManager /** * Displays the client settings. */ -class VectorSettingsActivity : MXCActionBarActivity() { +class VectorSettingsActivity : MXCActionBarActivity(), + PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, + FragmentManager.OnBackStackChangedListener { + private lateinit var vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment - override fun getLayoutRes(): Int { - return R.layout.activity_vector_settings - } + override fun getLayoutRes() = R.layout.activity_vector_settings - override fun getTitleRes(): Int { - return R.string.title_activity_settings - } + override fun getTitleRes() = R.string.title_activity_settings override fun initUiAndData() { configureToolbar() @@ -59,6 +64,53 @@ class VectorSettingsActivity : MXCActionBarActivity() { } else { vectorSettingsPreferencesFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as VectorSettingsPreferencesFragment } + + + supportFragmentManager.addOnBackStackChangedListener(this) + + } + + override fun onDestroy() { + supportFragmentManager.removeOnBackStackChangedListener(this) + super.onDestroy() + } + + override fun onBackStackChanged() { + if (0 == supportFragmentManager.backStackEntryCount) { + supportActionBar?.title = getString(getTitleRes()) + } + } + + override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?): Boolean { + + var session = getSession(intent) + + if (null == session) { + session = Matrix.getInstance(this).defaultSession + } + + if (session == null) { + return false + } + + var oFragment: Fragment? = null + + if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) { + oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.myUserId) + } + + if (oFragment != null) { + oFragment.setTargetFragment(caller, 0) + // Replace the existing Fragment with the new Fragment + supportFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom, + R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom) + .replace(R.id.vector_settings_page, oFragment, pref?.title.toString()) + .addToBackStack(null) + .commit() + return true + } + return false } companion object { diff --git a/vector/src/main/java/im/vector/fragments/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/fragments/VectorSettingsNotificationsTroubleshootFragment.kt new file mode 100644 index 0000000000..0f734c3655 --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/VectorSettingsNotificationsTroubleshootFragment.kt @@ -0,0 +1,174 @@ +/* + * Copyright 2018 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.fragments + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.support.transition.TransitionManager +import android.support.v7.widget.DividerItemDecoration +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import butterknife.BindView +import im.vector.Matrix +import im.vector.R +import im.vector.activity.MXCActionBarActivity +import im.vector.extensions.withArgs +import im.vector.fragments.troubleshoot.NotificationTroubleshootTestManager +import im.vector.fragments.troubleshoot.TroubleshootTest +import im.vector.push.fcm.NotificationTroubleshootTestManagerFactory +import im.vector.util.BugReporter +import org.matrix.androidsdk.MXSession + +class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() { + + @BindView(R.id.troubleshoot_test_recycler_view) + lateinit var mRecyclerView: RecyclerView + @BindView(R.id.troubleshoot_bottom_view) + lateinit var mBottomView: ViewGroup + @BindView(R.id.toubleshoot_summ_description) + lateinit var mSummaryDescription: TextView + @BindView(R.id.troubleshoot_summ_button) + lateinit var mSummaryButton: Button + @BindView(R.id.troubleshoot_run_button) + lateinit var mRunButton: Button + + private var testManager: NotificationTroubleshootTestManager? = null + // members + private var mSession: MXSession? = null + + override fun getLayoutResId() = R.layout.fragment_settings_notifications_troubleshoot + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val appContext = activity!!.applicationContext + // retrieve the arguments + mSession = Matrix.getInstance(appContext).getSession(arguments!!.getString(MXCActionBarActivity.EXTRA_MATRIX_ID)) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val layoutManager = LinearLayoutManager(context) + mRecyclerView.layoutManager = layoutManager + + val dividerItemDecoration = DividerItemDecoration(mRecyclerView.context, + layoutManager.orientation) + mRecyclerView.addItemDecoration(dividerItemDecoration) + + + mSummaryButton.setOnClickListener { + BugReporter.sendBugReport() + } + + mRunButton.setOnClickListener { + testManager?.retry() + } + startUI() + } + + private fun startUI() { + + mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_running_status, + 0, 0) + + testManager = NotificationTroubleshootTestManagerFactory.createTestManager(this, mSession) + + testManager?.statusListener = { troubleshootTestManager -> + if (isAdded) { + TransitionManager.beginDelayedTransition(mBottomView) + when (troubleshootTestManager.diagStatus) { + TroubleshootTest.TestStatus.NOT_STARTED -> { + mSummaryDescription.text = "" + mSummaryButton.visibility = View.GONE + mRunButton.visibility = View.VISIBLE + } + TroubleshootTest.TestStatus.RUNNING -> { + //Forces int type because it's breaking lint + val size: Int = troubleshootTestManager.testList.size + val currentTestIndex: Int = troubleshootTestManager.currentTestIndex + mSummaryDescription.text = getString( + R.string.settings_troubleshoot_diagnostic_running_status, + currentTestIndex, + size + ) + mSummaryButton.visibility = View.GONE + mRunButton.visibility = View.GONE + } + TroubleshootTest.TestStatus.FAILED -> { + //check if there are quick fixes + var hasQuickFix = false + testManager?.testList?.let { + for (test in it) { + if (test.status == TroubleshootTest.TestStatus.FAILED && test.quickFix != null) { + hasQuickFix = true + break + } + } + } + if (hasQuickFix) { + mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_with_quickfix) + } else { + mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_no_quickfix) + } + mSummaryButton.visibility = View.VISIBLE + mRunButton.visibility = View.VISIBLE + } + TroubleshootTest.TestStatus.SUCCESS -> { + mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_success_status) + mSummaryButton.visibility = View.VISIBLE + mRunButton.visibility = View.VISIBLE + } + } + } + + } + mRecyclerView.adapter = testManager?.adapter + testManager?.runDiagnostic() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_OK && requestCode == NotificationTroubleshootTestManager.REQ_CODE_FIX) { + testManager?.retry() + return + } + super.onActivityResult(requestCode, resultCode, data) + } + + override fun onDetach() { + testManager?.cancel() + super.onDetach() + } + + override fun onResume() { + super.onResume() + (activity as? MXCActionBarActivity)?.supportActionBar?.setTitle(R.string.settings_notification_troubleshoot) + } + + companion object { + // static constructor + fun newInstance(matrixId: String) = VectorSettingsNotificationsTroubleshootFragment() + .withArgs { + putString(MXCActionBarActivity.EXTRA_MATRIX_ID, matrixId) + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt b/vector/src/main/java/im/vector/fragments/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt new file mode 100644 index 0000000000..a6723da5da --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2018 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.fragments.troubleshoot + +import android.os.Build +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView +import butterknife.BindView +import butterknife.ButterKnife +import im.vector.R +import im.vector.ui.themes.ThemeUtils + +class NotificationTroubleshootRecyclerViewAdapter(val tests: ArrayList) + : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val itemView = inflater.inflate(viewType, parent, false) + return ViewHolder(itemView) + } + + override fun getItemViewType(position: Int): Int = R.layout.item_notification_troubleshoot + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val test = tests[position] + holder.bind(test) + } + + override fun getItemCount(): Int = tests.size + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + @BindView(R.id.troubleshootTestTitle) + lateinit var titleText: TextView + @BindView(R.id.troubleshootTestDescription) + lateinit var descriptionText: TextView + @BindView(R.id.troubleshootStatusIcon) + lateinit var statusIconImage: ImageView + @BindView(R.id.troubleshootProgressBar) + lateinit var progressBar: ProgressBar + @BindView(R.id.troubleshootTestButton) + lateinit var fixButton: Button + + init { + ButterKnife.bind(this, itemView) + } + + fun bind(test: TroubleshootTest) { + + val context = itemView.context + titleText.setTextColor(ThemeUtils.getColor(context, R.attr.vctr_riot_primary_text_color)) + descriptionText.setTextColor(ThemeUtils.getColor(context, R.attr.vctr_default_text_hint_color)) + + when (test.status) { + TroubleshootTest.TestStatus.NOT_STARTED -> { + titleText.setTextColor(ThemeUtils.getColor(context, R.attr.vctr_default_text_hint_color)) + descriptionText.setTextColor(ThemeUtils.getColor(context, R.attr.vctr_default_text_hint_color)) + + progressBar.visibility = View.INVISIBLE + statusIconImage.visibility = View.VISIBLE + statusIconImage.setImageResource(R.drawable.unit_test) + } + TroubleshootTest.TestStatus.RUNNING -> { + progressBar.visibility = View.VISIBLE + statusIconImage.visibility = View.INVISIBLE + + } + TroubleshootTest.TestStatus.FAILED -> { + progressBar.visibility = View.INVISIBLE + statusIconImage.visibility = View.VISIBLE + statusIconImage.setImageResource(R.drawable.unit_test_ko) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + statusIconImage.imageTintList = null + } + + descriptionText.setTextColor(ThemeUtils.getColor(context, R.attr.vctr_highlighted_message_text_color)) + } + TroubleshootTest.TestStatus.SUCCESS -> { + progressBar.visibility = View.INVISIBLE + statusIconImage.visibility = View.VISIBLE + statusIconImage.setImageResource(R.drawable.unit_test_ok) + } + } + + val quickFix = test.quickFix + if (quickFix != null) { + fixButton.setText(test.quickFix!!.title) + fixButton.setOnClickListener { _ -> + test.quickFix!!.doFix() + } + fixButton.visibility = View.VISIBLE + } else { + fixButton.visibility = View.GONE + } + + titleText.setText(test.titleResId) + val description = test.description + if (description == null) { + descriptionText.visibility = View.GONE + } else { + descriptionText.visibility = View.VISIBLE + descriptionText.text = description + } + } + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/troubleshoot/NotificationTroubleshootTestManager.kt b/vector/src/main/java/im/vector/fragments/troubleshoot/NotificationTroubleshootTestManager.kt new file mode 100644 index 0000000000..e47612d576 --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/troubleshoot/NotificationTroubleshootTestManager.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2018 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.fragments.troubleshoot + +import android.os.Handler +import android.os.Looper +import android.support.v4.app.Fragment +import kotlin.properties.Delegates + +class NotificationTroubleshootTestManager(val fragment: Fragment) { + + val testList = ArrayList() + var isCancelled = false + + var currentTestIndex by Delegates.observable(0) { _, _, _ -> + statusListener?.invoke(this) + } + val adapter = NotificationTroubleshootRecyclerViewAdapter(testList) + + + var statusListener: ((NotificationTroubleshootTestManager) -> Unit)? = null + + var diagStatus: TroubleshootTest.TestStatus by Delegates.observable(TroubleshootTest.TestStatus.NOT_STARTED) { _, _, _ -> + statusListener?.invoke(this) + } + + + fun addTest(test: TroubleshootTest) { + testList.add(test) + test.manager = this + } + + fun runDiagnostic() { + if (isCancelled) return + currentTestIndex = 0 + val handler = Handler(Looper.getMainLooper()) + diagStatus = if (testList.size > 0) TroubleshootTest.TestStatus.RUNNING else TroubleshootTest.TestStatus.SUCCESS + var isAllGood = true + for ((index, test) in testList.withIndex()) { + test.statusListener = { + if (!isCancelled) { + adapter.notifyItemChanged(index) + if (it.isFinished()) { + isAllGood = isAllGood && (it.status == TroubleshootTest.TestStatus.SUCCESS) + currentTestIndex++ + if (currentTestIndex < testList.size) { + val troubleshootTest = testList[currentTestIndex] + troubleshootTest.status = TroubleshootTest.TestStatus.RUNNING + //Cosmetic: Start with a small delay for UI/UX reason (better animation effect) for non async tests + handler.postDelayed({ + if (fragment.isAdded) { + troubleshootTest.perform() + } + }, 600) + } else { + //we are done, test global status? + diagStatus = if (isAllGood) TroubleshootTest.TestStatus.SUCCESS else TroubleshootTest.TestStatus.FAILED + } + } + } + } + } + if (fragment.isAdded) { + testList.firstOrNull()?.perform() + } + } + + fun retry() { + for (test in testList) { + test.cancel() + test.description = null + test.quickFix = null + test.status = TroubleshootTest.TestStatus.NOT_STARTED + } + runDiagnostic() + } + + fun cancel() { + isCancelled = true + for (test in testList) { + test.cancel() + } + } + + companion object { + val REQ_CODE_FIX = 9099 + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/troubleshoot/TestAccountSettings.kt b/vector/src/main/java/im/vector/fragments/troubleshoot/TestAccountSettings.kt new file mode 100644 index 0000000000..e27f755fe3 --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/troubleshoot/TestAccountSettings.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2018 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.fragments.troubleshoot + +import android.support.v4.app.Fragment +import im.vector.R +import org.matrix.androidsdk.MXSession +import org.matrix.androidsdk.rest.model.bingrules.BingRule +import org.matrix.androidsdk.util.BingRulesManager + +/** + * Check that the main pushRule (RULE_ID_DISABLE_ALL) is correctly setup + */ +class TestAccountSettings(val fragment: Fragment, val session: MXSession?) : TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) { + + override fun perform() { + val defaultRule = session?.dataHandler?.bingRulesManager?.pushRules()?.findDefaultRule(BingRule.RULE_ID_DISABLE_ALL) + if (defaultRule != null) { + if (!defaultRule.isEnabled) { + description = fragment.getString(R.string.settings_troubleshoot_test_account_settings_success) + quickFix = null + status = TestStatus.SUCCESS + } else { + description = fragment.getString(R.string.settings_troubleshoot_test_account_settings_failed) + quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_account_settings_quickfix) { + override fun doFix() { + if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished + session?.dataHandler?.bingRulesManager?.updateEnableRuleStatus(defaultRule, !defaultRule.isEnabled, + object : BingRulesManager.onBingRuleUpdateListener { + + override fun onBingRuleUpdateSuccess() { + manager?.retry() + } + + override fun onBingRuleUpdateFailure(errorMessage: String) { + manager?.retry() + } + }) + } + } + status = TestStatus.FAILED + } + } else { + //should not happen? + status = TestStatus.FAILED + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/troubleshoot/TestDeviceSettings.kt b/vector/src/main/java/im/vector/fragments/troubleshoot/TestDeviceSettings.kt new file mode 100644 index 0000000000..e229328c6b --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/troubleshoot/TestDeviceSettings.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2018 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.fragments.troubleshoot + +import android.support.v4.app.Fragment +import im.vector.Matrix +import im.vector.R + +/** + * Checks if notifications are enable in the system settings for this app. + */ +class TestDeviceSettings(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) { + + override fun perform() { + val pushManager = Matrix.getInstance(fragment.activity).pushManager + if (pushManager.areDeviceNotificationsAllowed()) { + description = fragment.getString(R.string.settings_troubleshoot_test_device_settings_success) + quickFix = null + status = TestStatus.SUCCESS + } else { + quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_device_settings_quickfix) { + override fun doFix() { + pushManager.setDeviceNotificationsAllowed(true) + manager?.retry() + } + + } + description = fragment.getString(R.string.settings_troubleshoot_test_device_settings_failed) + status = TestStatus.FAILED + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/troubleshoot/TestSystemSettings.kt b/vector/src/main/java/im/vector/fragments/troubleshoot/TestSystemSettings.kt new file mode 100644 index 0000000000..6221c748ae --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/troubleshoot/TestSystemSettings.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2018 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.fragments.troubleshoot + +import android.support.v4.app.Fragment +import android.support.v4.app.NotificationManagerCompat +import im.vector.R +import im.vector.util.startNotificationSettingsIntent + +/** + * Checks if notifications are enable in the system settings for this app. + */ +class TestSystemSettings(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) { + + override fun perform() { + if (NotificationManagerCompat.from(fragment.context!!).areNotificationsEnabled()) { + description = fragment.getString(R.string.settings_troubleshoot_test_system_settings_success) + quickFix = null + status = TestStatus.SUCCESS + } else { + description = fragment.getString(R.string.settings_troubleshoot_test_system_settings_failed) + quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_system_settings_quickfix) { + override fun doFix() { + if (manager?.diagStatus == TestStatus.RUNNING) return //wait before all is finished + startNotificationSettingsIntent(fragment, NotificationTroubleshootTestManager.REQ_CODE_FIX) + } + + } + status = TestStatus.FAILED + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/fragments/troubleshoot/TroubleshootTest.kt b/vector/src/main/java/im/vector/fragments/troubleshoot/TroubleshootTest.kt new file mode 100644 index 0000000000..49901ea2eb --- /dev/null +++ b/vector/src/main/java/im/vector/fragments/troubleshoot/TroubleshootTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2018 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.fragments.troubleshoot + +import android.support.annotation.StringRes +import kotlin.properties.Delegates + +abstract class TroubleshootTest(@StringRes val titleResId: Int) { + + enum class TestStatus { + NOT_STARTED, + RUNNING, + FAILED, + SUCCESS + } + + var description: String? = null + + var status: TestStatus by Delegates.observable(TestStatus.NOT_STARTED) { _, _, _ -> + statusListener?.invoke(this) + } + + var statusListener: ((TroubleshootTest) -> Unit)? = null + + var manager : NotificationTroubleshootTestManager? = null + + abstract fun perform() + + fun isFinished(): Boolean = (status == TestStatus.FAILED || status == TestStatus.SUCCESS) + + var quickFix: TroubleshootQuickFix? = null + + + abstract class TroubleshootQuickFix(@StringRes val title: Int) { + abstract fun doFix() + } + + open fun cancel() { + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/push/PushManager.java b/vector/src/main/java/im/vector/push/PushManager.java index eac3b970a1..3c82e6a7f7 100755 --- a/vector/src/main/java/im/vector/push/PushManager.java +++ b/vector/src/main/java/im/vector/push/PushManager.java @@ -165,6 +165,28 @@ public void onNetworkConnectionUpdate(boolean isConnected) { mRegistrationToken = getStoredRegistrationToken(); } + public void deepCheckRegistration(Context context) { + if (isFcmRegistered()) { + //Issue #2266 It might be possible that the FCMHelper saved token is different + //than the push manager saved token, and that the pushManager is not aware. + //And as per current code the pushMgr saved token is sent at each startup (resume?) + //So anyway, might be a good thing to check that it is synced? + //Very defensive code but, ya know :/ + String fcmToken = FcmHelper.getFcmToken(context); + String pushMgrSavedToken = getCurrentRegistrationToken(); + + boolean savedTokenAreDifferent = pushMgrSavedToken == null ? fcmToken != null : !pushMgrSavedToken.equals(fcmToken); + if (savedTokenAreDifferent) { + Log.e(LOG_TAG, "SAVED NOTIFICATION TOKEN NOT IN SYNC"); + resetFCMRegistration(fcmToken); + } else { + forceSessionsRegistration(null); + } + } else { + checkRegistrations(); + } + } + /** * Check if the FCM registration has been broken with a new token ID. * The FCM could have cleared it (onTokenRefresh). @@ -1140,7 +1162,9 @@ public void setScreenTurnedOn(boolean flag) { /** * Tell if the application can run in background. - * It depends on the app settings and the `IgnoringBatteryOptimizations` permission. + * It depends on the app settings and the `IgnoringBatteryOptimizations` permission in FCM mode. + * In FCM mode return true if token is registred and IgnoringBatteryOptimizations is on + * In fdroid mode returns true if user pref for backgroudn sync is on (will use foreground notificaiton to keep alive, no need for battery optimisation). * * @return true if the background sync is allowed */ diff --git a/vector/src/main/java/im/vector/util/PreferencesManager.java b/vector/src/main/java/im/vector/util/PreferencesManager.java index 11204f7d9c..b4f5813cc8 100755 --- a/vector/src/main/java/im/vector/util/PreferencesManager.java +++ b/vector/src/main/java/im/vector/util/PreferencesManager.java @@ -55,6 +55,7 @@ public class PreferencesManager { public static final String SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY = "SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY"; public static final String SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY = "SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY"; public static final String SETTINGS_NOTIFICATION_PRIVACY_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_PRIVACY_PREFERENCE_KEY"; + public static final String SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY"; public static final String SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY = "SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY"; public static final String SETTINGS_COPYRIGHT_PREFERENCE_KEY = "SETTINGS_COPYRIGHT_PREFERENCE_KEY"; public static final String SETTINGS_CLEAR_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_CACHE_PREFERENCE_KEY"; diff --git a/vector/src/main/java/im/vector/util/SystemUtils.kt b/vector/src/main/java/im/vector/util/SystemUtils.kt index 44c6f40fae..80652e040c 100644 --- a/vector/src/main/java/im/vector/util/SystemUtils.kt +++ b/vector/src/main/java/im/vector/util/SystemUtils.kt @@ -26,6 +26,7 @@ import android.net.Uri import android.os.Build import android.os.PowerManager import android.provider.Settings +import android.support.v4.app.Fragment import androidx.core.widget.toast import im.vector.R import im.vector.settings.VectorLocale @@ -101,3 +102,26 @@ fun getDeviceLocale(context: Context): Locale { return locale } + +/** + * Shows notification settings for the current app. + * In android O will directly opens the notification settings, in lower version it will show the App settings + */ +fun startNotificationSettingsIntent(fragment: Fragment, requestCode: Int) { + val intent = Intent() + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { + intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + intent.putExtra(Settings.EXTRA_APP_PACKAGE, fragment.context?.packageName) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + intent.putExtra("app_package", fragment.context?.packageName) + intent.putExtra("app_uid", fragment.context?.applicationInfo?.uid) + } else { + intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + intent.addCategory(Intent.CATEGORY_DEFAULT); + val uri = Uri.fromParts("package", fragment.activity?.packageName, null) + intent.data = uri + } + + fragment.startActivityForResult(intent, requestCode) +} diff --git a/vector/src/main/res/drawable-hdpi/unit_test.png b/vector/src/main/res/drawable-hdpi/unit_test.png new file mode 100644 index 0000000000000000000000000000000000000000..56b43424286994fe6ac7c65b4cec9903efafcce3 GIT binary patch literal 684 zcmV;d0#p5oP)b z&*zK$Q5M|*vNr0^g`v7kd;-}y;npbtS2*@5j{PH^ogg5bZxPO~2}`SPgXh8`khPBCLL|P6A;S0CF>=rKDh|@Z36-__;eE0hqPlp@ z;D{ZS)AlQjy1|FpRT$)v%4vI-LAHTM`}_$+%%86svy+aQ-!PTRW-vKgYT!r<{(<+S|@qidMY0gD89$j6cvE^7~E zeMGilvA|K<3T5JAZqf0kJ=bup&L@*>dy7~sU|>FHwc2CidWjP!*=a@%h53-0nf1VK z;>T1=Cjp%N+$3C_!?|3;wP<8Ch54r*&?3d+45Ir!Sa3Q3jPw!lIzv~6j+a7XP6Xi} zQ479s!1m}-P$7lq|CCHRU?pO)zz$m&>g}TGsm7w;;u@`D?!>hdxW?D`+wvPHeI5E1 SVN|jJ00001r;P)^I-+&d$!rlrY8*E@RC_hMJN+R%rwjtitsIQ=Aux48_rg&6mc->mJe9y zPDr*E!AWx_{uHKWp+0!=ae+S>cX!TMgIq94(=Eo zOCuIXG!f<_cM?N}n6rehNjujNwDXNY6As3mGD3LbZ@`yrxH1*oi4BonXc9S$HNFE! z;>%9we+`#%LKh$vku5%89WpqpWx@$Mk#MG>(Q7T4U7CU1f{p89ZnOACPT()D<8R9^5(3q-8# literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-hdpi/unit_test_ok.png b/vector/src/main/res/drawable-hdpi/unit_test_ok.png new file mode 100644 index 0000000000000000000000000000000000000000..ba83c1f3ed3ec87c3adba17e05fffc5df9634a46 GIT binary patch literal 838 zcmV-M1G)T(P)A~A{7xo zj!knZJ?ZwO^k7MDVnmSM3ig8}9u!Pk^;8f+MbL^$%|(rewwR>58Qy7PM73!shY!)t-o0R*|3+s1&m-`+4(fu_KD_z}cm+_V{7wyGriXbtIn{Y9aK=F=H~}YmrhX$MvjL|!p%9zXCaMfL75q?bVL#R8{2=V!u!v;TZvn|5o?(PqJ}gJ3tiT zI1L?S*LyGlIL;4NP~tl1g*&~m*NU4T#RKIPZJvl0`#>Q5?SKobW(Lc#s~))Mhcw5X zw&WC*Y~yiI5oY}uGbQq(~(;(5KIK#tuD{%i)Xh| zj7B3h!DQeDe)*uZ8@0sdbe`ihnbkssoX)J8PevC4UKzuRxgbQLT%OWjy9JZjI0~$W zr2ifc`D!g8R?{0iS5D>D#U8Jq)(m8wp(a4F!HLHr~vOv&^Lxc_-$-$hl{sQ4M zXf_2W7BPuuC9rNHyeu$Ko@)xq2TQRN-?vM5;;b&xR6ca+LzMd1zg%h@Bai`V|Ha>yKZP_p$@bVP QRR91007*qoM6N<$f?p4Z0ssI2 literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-mdpi/unit_test.png b/vector/src/main/res/drawable-mdpi/unit_test.png new file mode 100644 index 0000000000000000000000000000000000000000..adff5fe154205976e78f83e5da7d38bd571e7d2e GIT binary patch literal 411 zcmV;M0c8G(P)vorty!!YxVcVJ~%MoMLU-!F|ZA!Wsl;x~mwnx-{5 zXT}9}UHfg@J_&rMoOSsFzmrhDSS%i+DC(#UK*&wgyo!7;0QPLHy$pijoI^~G0vNDx z5IEz9C(FgEf9l*MNwOh7v9025RaGzYYl*}o=fTjk5+A6`u6`g-bUaTYvGDIYz3>;b zkm=xyV`P*@OfhmYE1@L?sjcv*n8U)g`ZJ8m_x-)`%n3Ox+~3AE$KR{bLPMtOIu__8 zb6B{vz=pAHwU z6|#*z%JM7swdr|mkxRz?*@R(u&3*XyW%?G7>R^Ap&R>jV$MiSFaOeO4002ovPDHLk FV1nPEyjuVO literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-mdpi/unit_test_ko.png b/vector/src/main/res/drawable-mdpi/unit_test_ko.png new file mode 100644 index 0000000000000000000000000000000000000000..5a9ddef599628600a8c1d84e5811e83d25dfab95 GIT binary patch literal 388 zcmV-~0ek+5P)tLM`zsy#c&HPyftaG1oC8#w1)QeapEYhX`x zSXhnvoDU$;XB5_`x5b_j*Ka5c*aB*Jd+VyRt{ZbD?5$kR!0Ic&)9P*J$6PrOBgUc$ i|KN8GUDN*ioqqs_K7}n$B^`YL0000 zMlnr*$hmAjhD3)<_{m10gsI8kBoXZ-I{B|;5+y8g$OdoH-3h=ie!4b+XyDUQIG-bK zuCxbL(d)VuP}GS(kWWqAG7zvw=TYdE1c*b)R?68X-}eLHrd}})`TBQWXOG?Eh;J1@ z;!+MosWd+(=tTg~epCS5D}CYY?6K3Ka&=3@%4=Iyxq!j8iT5<%HJ7nI@0gomzb$gL zko0tL4ttEVnlA#31TqyT@D^rgYQP>_0)0)F-xfJgSuJ=c0CmINZ&aE900000^~6s_+`6`FQkM5LgB5}~_77B1>SlmrZ}n@yp=pxZ)& zg>G$Ww}oz+b*K>hK(GrJfi8=sph5{^yG|2`6>HCVnHlCypV9ac2cC1^oqNx@bLZn- zeK)3T&1UoF@$qpI=2@T5w=o)xLg+(uhunkQL;nr0#YiMlkH_PGI5w&ICK=T0^}GFk z{{_Gw!}iW3dog~3fc67{z{_MZ`Rg*Du>rJNtw5*Kc@F1iAis4P$ha{CafY!%G#Y)i zzP{e~vhxgpvpzpRKY+ugm(vWoqhK(Y^UB750n}=>2ZO=j5F}v_qAq6j3D2Qz+Z{YT z+6;%o5S}P9wT)+IoUh;S-%6!YUv*qp0Al7}OaKs)XfK^ke}FvH0ZbVI*p*7<3G{i$ z&L-7ty2!*sJrkB3dBNxcd{Y&h4N zTbA_}v(61g0DV?E z0`m+mPToTbcjqN1Au$A&vtg;@M9E3n;tdA{FB!e<&60K_tr zazj%v0A4~%X(l$gEu_YES@4Ei3OhheY;rRLW&mopE;kY$;w z30Wq!+wIR7bX7%&mL=KlRX@*6yO_C2eiVzv9?3FYrI-qq&Iq83M>R$dpzoN;Q^*rf z2W@CeACH-{D9uT7lBf(w#+8=kGKiJ_F_A&6^sku=g1xAium8`f9t7oO{XPE&zN^>h TK)M+l00000NkvXXu0mjft&f*M literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xhdpi/unit_test_ko.png b/vector/src/main/res/drawable-xhdpi/unit_test_ko.png new file mode 100644 index 0000000000000000000000000000000000000000..1a806a6fb57d84d143de67cccba5b094d93691a7 GIT binary patch literal 742 zcmVIZl?U_b;x<4uo(H$^e9o(!8@vyhlsoRsx?)5~mjGc!FqG7YocJ6%<;x~jYD4O77; zTT9JY;+rQ4-6N)}M6y7{`k+Zy$6XToVIq2K*?#2J@BP7C_Ah5otLd$!i^oa)_(bTT z{+r8_AIx*lFulAG*(d7AewNO;4#@kqJvxvL7G4*U*(@XH%`x$zusolc=V!dqi6_ZT zbO5XVlf-XCfCvR_yA}Z)v#^ z2i(g4#$T!i_keJs)v(vokuZJ|Id@?%x^kv%wtNYXo{DX6C#%Q89EM_fxuFBta3MM8 zCO?{a(AmXv1|X6>X%#MF8hK1UwHQ zZys2{gq+dUHwTwYo?5l@zy!9z^;-OftI`RX1GaH@dzE$rYysO03QK@im7-eIbQ7Yg z)zxG{9q=W!4ks86)Y$YqA_Yn&YHBQdNI@wFj$XHi)iON}TMBm@pi!!{9Hyvo>&puT zb~OOwP<uG&>k(Nr7cMlsgQmNwD_SFl_unZpDtQIq}5L?Nrjg9p-Bu)jS!7`m)uQf z-rXMi-o1NyIj*>Hx4ZLaf3q{Qv-=Qyw%Ax(<=s0E!jPeT0MtZ)Pjk;dX*i8Q)7+1P z!hxEnRwrUFoKIzW-v8*$26#UaS)W4skN^$?!IR6$TYQ9hUie%k;m2Qw<1=15MGffv z^WCb6sfp7>nUf6ugx4a)c)a)mqUvhc(D3Q;jc1Z>a;^r*u9HZYc*o6dN-L%zj8+jI zaPvli0UcNOZK2HK5QEpR7|2TjVKRUi>E0Rt-4wGKklXq7vV&_xHiA8|-SmXgJc)VE z?d9FM29Tb;Dj0&L$$0}L;mQ_sy}Db^d1Ybccvc9kqXzU>so7qDgRqEze-qGiyeV6l zMV1K*m4J;B3d;j21s|0Q91;J(V6d?#6n|bp;1vOL8Yoy&TP$Ow!p+UzpJhw^6&dGh z#vBp;slZ_KS33u9X>!L%N;hW*D`>!Umpc1*lL0c7>8k99@HE# zreLdVZ8te3jQJwqGyYarcyQFzKAwmkVhdg~X|^%nE1Uz=WFwX^QGlnp!2D>cc8=_M z2{6-d(hA4aq_VeKA0DGh76>C;@z(@>CUlWiwAO7s*|0aa~6+r zeEF(%X^4rY>iTw*)0x=6Hk~FZ<#i_AeN1A2M?*Z?6CU1NOFNzc#4ej8;?30oJlMYN z+eJ<9yYY4fVd?@9ZPGNao@)iM0qD9rW={2n;#Le};n)(7-(ydlk_FlA{{{jW{k&!9 zt_d)Hf3lB{+%A*mHRgJ7TV-D|E@B9^vC+e}+Rz&s{z55rj5EHqyJcv?)ao3Ge8AvG zOxki|!A33=->~94Zo*tx;;ORxjqaDvKXl`3TE|GVjUe*_$DLEge`8OcRq}=_ra}af zbZX)9sT*x;ipiWz>}_W7?^X(Kt;g6(9*}JJd@%q{sFsKK7Ou#J<&@!!e>ROv%N{qU zo;7Do!B+BmWxt(s70MIo#U#)*Ip3NjUddo`h`r=qYPDdIUNCdmxtFh>>lkqcRq*wi z^w#k?{B@YUv%ARpo@55MU&nGU%j?xsOPECKA+vO44u82=D!42oSo&F(FGTWm=3KB{ zCL-)?d}7ve!tHg^ea{)g!pbSwj?H6zl!`%CC{#t1Ju&HfM}7r!&vNa0t(FggCiJ{I z@S8lH{e(m7EZb*4Otw3}2}G~J03nK%jT;(63e_i)u|g}QiLR_6YdLqW*R$n-CYrtV lVtyPSr=}4y{=YuY{{ya3oIRVe&guXF002ovPDHLkV1g6x9xea? literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxhdpi/unit_test.png b/vector/src/main/res/drawable-xxhdpi/unit_test.png new file mode 100644 index 0000000000000000000000000000000000000000..cfc7555596540ecfccd575e6992eb6915f5d4a7d GIT binary patch literal 1334 zcmV-61LyE|{&LVwapHnaQQoA+5?Si^X>QY?)HE1L&zpHk*BbdJ@-JS>@3=h{zhQ$y6%!8g;1} zO;tHCKRi3VJ-h|sVJnU(PeJCIB*#eLx?vhlJ_q>1BHZ5q zUYco2P&P3#GV($)coh2V?CfLMe1^6WQ+wE4!*IOpeX%XKFcd^M+eqM{JuxY4We%1(zxz*VIHKE$cAS%AHE0&JgF zm_xP$2p1hNl{CV6ta;v~v|5_7oi@p9%oaBz7*|}+`wE0!`iM;?NEnEj*e&j)fXR8Q zUHCS*Ja9d?Iny!7>@E zK>H~02j~Dd;J$(}_RB|X5dm0!3Z^g80pq%y>v^G3)5!r?q87X}eY`^4j5HmoU{JzB z?2Lf1a3^JCw160dg|iS-8ot79ppV!hf`oT+a z5nDtEz_P*!EDN}93iPIv@v?HYU@FU;h4Lm2=(m|YF!7p6Qy7?r@fBjXm?Z|sl#h%u z_2FyPWP-wA`c~}_yTueY##q*b3m!6W)6ybgAZB8>zXK4O5m1gZZzJ#;PFA+ZO@=gP z+X3e1A!Pg>sHBrgN5g3Wwz;LWJKc6b9WF3CW-8a1bTTm-NmDjW`(c`$(gz~81Elb9 z0pn_o_uhhVJwrI9smv>W>}T4dO_L&GBv#FH9N@_an3I~}sUuK0oy+AedFXY2qz&4F z;<5~!Hbji>Q=GB|8yZFsx-SqGvvW{zw^2DF)$`N9aYh67)CHsxS37*QdXL?;b)Ybtj~!1j63F2i@8U s)@}Q%f!hh*x9!g!UPt)c%HM7N18l2>ge^b?jsO4v07*qoM6N<$f{Q?MPyhe` literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxhdpi/unit_test_ko.png b/vector/src/main/res/drawable-xxhdpi/unit_test_ko.png new file mode 100644 index 0000000000000000000000000000000000000000..77fd68a6b833a8f56310f74a739cbc5dab620910 GIT binary patch literal 1133 zcmV-z1d{uSP)ub-C* z_!Yr~{D&=JQLmhFS|NV7xOvMxe)5A6Moz~Dh}km*qq%s2HQ6L7=nJDH1Z|aRY*N<- z@AHwzW+!|d8DPxr*~Lur8M(=UPK3s#A@^Efg~BO)nUy*-I%hTBajWowWDtC)aF8XVBzy|z*9(ErQbDuWA{${(8pX!wI3C_7dzr?8vAU^Rdli6)4RzVVz;W^v$bsP*!Bt_ zJs{Ds!N-OdETC@_r3YmuyW}uM<$gDD<#4HOFkwgNsZ2(y$2U#dj zN{5A6!^;Tr`J@DACI{~zguM%IQY_@lNM;~?6j4akfnB4`VaSIr*SCOOBTEKyjnW4k zuxqsAlznJ&eGAyJ0y3WF8mR|3V8=6%$cwp$j#Q_C-A*M^)s&)exKJ%c`KgKqcDPWa zf>Nr;Ius7<8s)c0p%ZQ5$=OxLjD8~ac=I!c1eWw3mU?5LMaJUe&rL~b!=v)9s6`uVJSd+52``3I=+(7bKpmb!)kXC4(16A5Yyj9=o&gmsPDF4rLQB+8 z=de|db*Ih3V8ByGC~eFPV>-JLv)d=+0?Z+NYL!>eP@pGGhhkU?rpEaz>00000NkvXXu0mjfAwLc$ literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxhdpi/unit_test_ok.png b/vector/src/main/res/drawable-xxhdpi/unit_test_ok.png new file mode 100644 index 0000000000000000000000000000000000000000..cf5e55ea666d679c502e8940f485e192b04283bd GIT binary patch literal 1621 zcmV-b2CDgqP)xETVu_k>;kqUh;|g>8A4fClrDf-YWxzQ7+PNi zL+>3Jo+_&T9pR!4NKCZWO-<*z1&Gtsvvv=c(y+S@jnuS2(G%Ke^(OXQn052~z5^TZ z4R%;pGP5rMssy>m7C*(M@X5{oMv#g9Pu6{Y1kUAx=m=y$*Lce|HK)F(9^Vs0>AlL( zh)l?8d8lt+@`j%-Ujt%e(I-R>zpGTQn*j1?2n(L;Kal##B*S!Bh#5;azlb>;S!wW$ ziQDA18ABnA#u2c=3w5?o<(1-rkjMKY!(Zsy(;C2T!`4$|Sg)(J;xrIhupq2+r)35# zPT1Vl)o`0a9m8`%R$F_$6FYAXaHw32HkApJg#h~TxI-BmV|2^r8NhLVr9!0}M9^=> z#u(l5OD<%uurmvj_nYgu!SXk|QfNa}gR^2UjR2Fw;Bp$|1X$0z!L!W!k`aP08WYXMJijO{X%@8 zDVfr>f|62*0FQ%N@@B-&=L1wKhX@V-K!4=oM@C&Jo-*=W493jZrM;6Xb~EBqRLm!V zPllQryG(VuGSPN&j}8+*k4~5wJGqdGNQQ5=z%Z_h{R0mBTM}JTpy2 zB=2F(0_EX=fE$$Gw9boz>+F?LElU@}N0CGa7W+?nw3hf=^Xf`7dRYnyxapVyA+ z=__qp$x4nm8%(;2p)+@k*ukB>I*3{odP z8hwrCk1-+z(V>%J%#7V$2l$9vCuF3d*shcs>&mn=8fAzPA31nXDPdd-3f1-FIhbt& z_Vd&wB-Z%ZuSK z*){HddG zX#|+SHzVX<6ArWJH*#K7Iq-~)F}iW(8Nm4q-;9*vN^Yf<_-0Iu&6{K88NdzOJ14ps zq2w%E2_PluWGsY9x2s!b05`zr@PP8=k{YW(CB~w=8{nq21~7wfMEGW8RY!s^HVWL% zFwVRv#02kizhCnC8o&mv>1$SJMAr00w_^mq*X*wbZX@`6&Hn7+HH6En`Mb^kAeEbR TYRl&l00000NkvXXu0mjf6J7^z literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxxhdpi/unit_test.png b/vector/src/main/res/drawable-xxxhdpi/unit_test.png new file mode 100644 index 0000000000000000000000000000000000000000..6feb0d73ddeaccfa14ef860a3eb753c71e8fc4e4 GIT binary patch literal 1792 zcmV+b2mknqP)b$h$pp8+E=0cGQDv$(Psg(Z+|7;%!x51EYL*H$+@ldf0O zlgvyHQ_$1Z^{VQ9-|JV^-BoWw=SrNGmzUep>2xpDT*1+i&1Nr!LZN4ok0BqUdR(V) zO5yq`@_!=#2*-Lf8eQ(~?cGAYWGPgNo);Dtu7S)bjyMQi#knXwXp5%V2RJ8i%nS?+ z{C?JWivs2aVr69|wzISI9srL)e%Y(e8I(PQA?`LcHGS37(?hR0bvm^HmX?;D+27y) z5J*12(Q;};g>{fbwaJEthA;a1`u-`biFd7{4M4YstG|u&r|7!PyZe%tZowmNVQ74d zbG8_5MHwKGNW5@#bTk8n-Nh&^L7UaOy1LPLJpQ-ezI_{DadENx`1tsH-1+DHDl27u zc)(^j93JWK?_c$!C+w#(Tz)K*$@~;F^5j5F#72yMI2Ggp=sYq%KmR#8zUilPSKU3_ zp#-j%aQuPeFU3a&gxmscZ}WL-A^$0d;}~}(Q>)MJ3~W5Y@v$?`BY=<{Ypto@xkwSXK%@~I#+|||98i2DsClxe2uWRfoe-epA z-oy;N<|->$7T7w0?FT4$z5peM!1kK%>ELMqKIfs}KCEqg)Zvc*1`xxgy6ZZE^ajXg zf$_Soj$ICnn=r!5UhfGf9)svG#Fv~x$V0(&eSQ6_a zW#2&q?U->Xn#Kh4mg~A=SxnU5LHP|60C>U}4EK$@UXBOw!v^H}ZZ9UfJw8O<6)Tld zNGxh7idxBy9Nm?244}U1OdmE+(7`ai!uH)ARWh%z3pfr+L{o+t?t>AhEy*ovaYVKe~DSbV{;C3 z-5!;%N|jBWNo^}7GhrrYau0LU0@iRIhaQ0U_&x7Y9_S*4c~1wDoXMR-fIBwY1WP6S zE@o&uE2DsziQS;`3JuU90H8VkrMansx^t&$(=)h0ZCHolY{}6 znXo^i0oElFaFaMUDy2F>lo2zr8?dd18Hxs1$O;uNaFC<XcQau*^a!u}Nu5adhhB7=VGtmQcVWq z>^ioYzt)J$UQ8^+WKv9S`N7rBQ^0PFjzVy!UvS-RmkH--1}9W1R)BY2-3ViJ2so_b z3HAz%kn`4?;{hbf3Jg5Pc~c;6i*6ho9Q<5~Ie|K<8$gWY$whI>Ax{z~48TH0Q(=d7 z1ldDgONp+B<9<1x4%9`R)Gc9CvO{w5iV&mx#1L?ISkr-$wjpXdRF+17o7Hx(v~2`T zIt)6v$?GCg^@hv4$*MWwOO%~=mj@`%3GaO=I65DzI0@!g<~3&E+WpH64Zx^t1|B23 z%Ikn63lp*V#pBxpYz3GcYxi$+wh?${?5zqvt*t*!x{bg**WqgNue14v;5CB~F+^(X i&(mIpU|G=rKK~y#=uuA literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxxhdpi/unit_test_ko.png b/vector/src/main/res/drawable-xxxhdpi/unit_test_ko.png new file mode 100644 index 0000000000000000000000000000000000000000..74db63ff2c9ee7999a14f5b16ac8ead39e0ad958 GIT binary patch literal 1473 zcmV;y1wQ(TP)AV1zh3n=rHG^HfhydV2bEx_iF5 zV0XISs<-NWo~o|Tx5dafsQU7;mXeo5sWqjHRl`s#sB%#$Sr9Q6SUu!-MARKT-ZBhn zK)$U+w#uTum+HlUyb@z{!R>p$S0Wl!L1)Zyzf4A;2H=Ey}cQy9y3nn*5}J-jKj{i(3v#wgt0{1w2d)9Md|^n{$`=o`ROCP_aDS4 zdu&-T9$YLJokXoQ`Soo$^~dSNv@B?E!kUH;l`Eib>>H+WsYmS(~3b@+klKOj(jL z{dBQ7xJ)d5>7G4+^Za4^;0GwBKH3v#xj6T%RJZ*(Y6~FU?7nJ6w z6ID)3#OBW5$4GZ$)5vmdU#Z>ftJ7^zbL03>YkGG%o^k9Quz^}}ZlL;lKzCO+o{f#C z#VHH1Y10@T#r6o`ejY7<){!=wkHDz0cG2|+;9&-2quS4pliZ_StlEYi0oF;*IPDr; z-A=5U0G{DxcZ@YUV9{$*CoIf{p$XstW_0D*$Z2EN1n~5BHef|ZVPiIVZqK9Z=&G}k zQy--W-GDt z6@XhxZo@4QAdOMNS6@E)h+lm{X?{A%sc~&!7a}+PVA!H*gEQOjEE!K5n9(^vTol4SS=4?vbh!VugLYZXU^Fb{)Rh>XY@h{EpmN!ae5=OF>3D9O{bu_QC}es&MDSzq4{-8ud%jY9 z!gjU2-vj7-3-?oJ&TO8?;vaAy3NYH>DeMuz2z_rs!XBTq3ijOZRen&KSN_;C>w5f> zQ?HKz-kW1>eBPO1mY#{t=^H?7eFRWoIUXCa+)b$$H3Y5qgVLgw@0SlNzfEFurw;>RQT&j6_l=o%|?z{ER}y#PSpGt9y( zeooZB1Q2Ad9VJNsJuJcCMLthspzT3kqQo>X2DpK0uWL9SyXBJ}2Y5P5p9`}ylmL2L zYp?4m>Iu5#BY29FoWL=_%^t@D`rc+SFuLm+CJx~D0O-B2ZtpBR76DYu^(QkVfYS*b zkGZg5d=dg}p-p~MU675jdw^XSovL&D*_=%Re{=icq>}_qL|#9gbrZpOE b|9t*`gxyc_C^%%B00000NkvXXu0mjfYK6=p literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxxhdpi/unit_test_ok.png b/vector/src/main/res/drawable-xxxhdpi/unit_test_ok.png new file mode 100644 index 0000000000000000000000000000000000000000..7747f98590ab1e0e30899d1813c1bac4c0fd59a7 GIT binary patch literal 2190 zcmV;92yyp`P)4;{A2fO+EM{Q4F-Z} zjQp@&B8o^f#E=j`=+c--j3CB@7>YdFE-D!Npfx`KDG>-pO^g9iFg)At-tjwkyUS&J z_s-tCk8aaR+I#QJnKS1*bLPzJEQuVaHC>A)To#uC!k}qFs4_G+70>ZXiBcje<>r-A z7l_0KA?PeVPvCi6NcHoLqNHm>p!sj6$yK6UFs?sZ^MHt|#bExJ66!vogtvA`C=&FO z65@#P(e9>M;UBXtuQ$PLqS4>{G8ZWg4n*gq5=}yk3Wcf;oKK@hN)pcsz__J`&YEQy!5h)z z6H>O2%K{Sc543t)MR*td2}X+hmP2p zyH-H3YfiZ`#6FDmiEbOqB|Ut@8Ioc_DDYX2n|7od;$UaZa%Iq0g$AB6uuLo)%jgDE zS{ATJE%NnrUfPUtztRo!p`wL9Xe_TNdG#r3j_PF4Dj){_BAa}jj#(jsf#;6k4rAGMo#JQ#Je~~0*r8)m$mLSX=R+0YovCC@seqWbXNRXorK&s} z^5GBqNUBFt%?`E-Fz5M55_wAF^8~4DtcI!ZQ~+1vbxn0Uobob|f#{ z7+X>rqyS#t7Rm}QN!E-$8B=WOem$G(G{PGjgL+R4+1mbk*|PtIy@OPvr&>2qNfyu- zEM)L-XlHS<&}y#zh3IvyA#>9PjFyzJ0N&4EjYNLW+m_ZMg#MMHBx!9Z(6%YQ>w~pU zR-$)1wt$c8>su(Wl`i)CZ;ziRBrJdrGYS(tgqQR@w!9nT;293g__uMT{sqYU$pTOR ziZcrn>i}g!0q~T|^|kX)fu3LAR1yBjuH|(PwqAw>Y_%(M%CllM6=0s>8Jf$&>SWIhr%`hT+Jb`D;0yATm zu^StmuJcnaNxrnTGTg3fZB@_wiAE&S2Fo9-qjfCDZ55oLd0pj5mzP4K4Nm*^xD4ys zmlgN-^|oLtn;g&c{#0c=3*fG9XcNYLGZs2)byDzd?q53nF*Y_2>uA#~pNd&PjZc#} zH)JHf8K)*L)lPS4J=jsb0gr{*6GV;o!<5V$+Uzk+pA!~3zZDe~Ke4V{Kiv%+&#g|M zyPlG<`=R+#n3kT}jsjB&X3}olO8tnpTR}1#tZud=P0qud`=4Kqs(Gh;|C!{nuMvU-Rw!TP|O@`Yx34Ogzu-iJp&0B^Pc9 z96V-MZp}l_-=hp;FL)MN^Rx9X(fgjRnMuhNR`4v)hKKk66RJ3bxB$7uNkCGxqOmIc zr4tR8@gfcKwc!sp(AiInU7|Oxm09z6 zy1Q&#JP0B0u;yp;ov~vYG{aKd@g+K`t6=WmrF;SW6W3EX$@#RgB5bP|D?fmybK=Y{ znEiBXe)iunjKLH!IqniEH6@)IP{IyDauH~I1<`kMbjcF75MAA1XYG2-^-t^QvQU14 zE#SCYMnF&&?5ItvcbY2O_Tx}vHWmBp8fP?L(X|z9uU@8<@rI5rJLPno8oH{VR8eI+ zo}C)uIh^DTGeUuVe>u^)DGRpORvHvNj43rI9zR(Y^3g)cp=j8JY0WaZGREg*!?@bB zvc2xEeXdI$WF+`*rW_!P<`U90-X`ee@_4kMK zc__=689O^c?*1cQ0l-;J!e2*2XLY~}kEW9^kB*4w0o1+IOMS0(;+RbZa1f$bMqCff z#$xLF0#{G2gD6h@d1@yVnEzm_0nDZX%m_>bA!eckXrWjcY9D*?bom53YTkz8@_!%; ztnsMRm<2#$c=eP)1nB~TE%*#lJA>^tt8{g+wOwK;<4ql1F3PP~6AIw)Q)uB_#tq`E z%@D?B0Nj|07yAng8|`v9RsD?efziY|z$BpnzAKA`pJv29QQ1)Hwfc@ZJ2=QHk%^c3 z&f2@-?f1j$W2}wm^POV~LY3iJ$(ymHA;7Z%c54VcMz3j)s$2mWO7M)u#58y( zI!FNwileDG@EFSIa|N;hi5UZ9v5OF6acvdA1sqq0$}9MNI48!CDn^iTrBnbH@=XmG zH1hj!Of0+Zw#iWe44R{^;SXAdn*vy7mR%>eRskGs%~99G$r4!BbRxxBIfJG_Cynv# z45rEVhH4e(B%29_9UhN)hv4XB9^8HM@8T + + + + + + + + + + + + + + +