diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt index 21b2464d..1450dbb5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/pushers/PushersService.kt @@ -61,4 +61,6 @@ interface PushersService { } fun livePushers(): LiveData> + + fun pushers() : List } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index afbe85e6..e77cfd3c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -191,5 +191,4 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se } } - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt index dcba72c3..e07af970 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/BingRuleWatcher.kt @@ -37,12 +37,15 @@ internal class BingRuleWatcher @Inject constructor(monarchy: Monarchy, override val query = Monarchy.Query { EventEntity.types(it, listOf( - EventType.REDACTION, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED) + EventType.MESSAGE, + EventType.REDACTION, + EventType.ENCRYPTED) ) } override fun processChanges(inserted: List, updated: List, deleted: List) { + // TODO Use const for "global" val rules = defaultPushRuleService.getPushRules("global") inserted.map { it.asDomain() } .filter { it.senderId != sessionParams.credentials.userId } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt index 1377ade5..aafe781f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt @@ -112,4 +112,8 @@ internal class DefaultPusherService @Inject constructor( { it.asDomain() } ) } + + override fun pushers(): List { + return monarchy.fetchAllCopiedSync { PusherEntity.where(it, sessionParam.credentials.userId) }.map { it.asDomain() } + } } \ No newline at end of file diff --git a/vector/build.gradle b/vector/build.gradle index 642d7a7f..ba12a14a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -238,8 +238,11 @@ dependencies { kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0' // gplay flavor only - gplayImplementation 'com.google.firebase:firebase-core:16.0.8' - gplayImplementation 'com.google.firebase:firebase-messaging:17.5.0' + gplayImplementation('com.google.firebase:firebase-messaging:19.0.1') { + exclude group: 'com.google.firebase', module: 'firebase-core' + exclude group: 'com.google.firebase', module: 'firebase-analytics' + exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' + } // TESTS testImplementation 'junit:junit:4.12' diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml index 042d9ef8..11d116fe 100644 --- a/vector/src/fdroid/AndroidManifest.xml +++ b/vector/src/fdroid/AndroidManifest.xml @@ -8,7 +8,7 @@ - + @@ -16,10 +16,14 @@ + + \ No newline at end of file diff --git a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestAutoStartBoot.kt b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt similarity index 96% rename from vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestAutoStartBoot.kt rename to vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt index bd266448..211f7337 100644 --- a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestAutoStartBoot.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotredesign.push.fcm.troubleshoot +package im.vector.riotredesign.fdroid.features.settings.troubleshoot import androidx.fragment.app.Fragment import im.vector.riotredesign.R diff --git a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestBackgroundRestrictions.kt b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt similarity index 98% rename from vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestBackgroundRestrictions.kt rename to vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt index d5903b92..72c0b433 100644 --- a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestBackgroundRestrictions.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotredesign.push.fcm.troubleshoot +package im.vector.riotredesign.fdroid.features.settings.troubleshoot import android.content.Context import android.net.ConnectivityManager diff --git a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestBatteryOptimization.kt b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt similarity index 96% rename from vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestBatteryOptimization.kt rename to vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt index ad1529e8..4cfd762f 100644 --- a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/troubleshoot/TestBatteryOptimization.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotredesign.push.fcm.troubleshoot +package im.vector.riotredesign.fdroid.features.settings.troubleshoot import androidx.fragment.app.Fragment import im.vector.riotredesign.R diff --git a/vector/src/fdroid/java/im/vector/riotredesign/fdroid/package-info.kt b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/package-info.kt new file mode 100644 index 00000000..9873a280 --- /dev/null +++ b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/package-info.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2019 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. + */ + +/** + * Code exclusively used by the FDroid build and not referenced on the main source code + */ +package im.vector.riotredesign.fdroid \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/receiver/AlarmSyncBroadcastReceiver.kt similarity index 72% rename from vector/src/main/java/im/vector/riotredesign/core/services/AlarmSyncBroadcastReceiver.kt rename to vector/src/fdroid/java/im/vector/riotredesign/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index 6c6a3f67..92b00c5e 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/services/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -1,4 +1,20 @@ -package im.vector.riotredesign.core.services +/* + * Copyright 2019 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.riotredesign.fdroid.receiver import android.app.AlarmManager import android.app.PendingIntent @@ -9,6 +25,7 @@ import android.os.Build import android.os.PowerManager import androidx.core.content.ContextCompat import im.vector.matrix.android.internal.session.sync.job.SyncService +import im.vector.riotredesign.fdroid.service.VectorSyncService import timber.log.Timber class AlarmSyncBroadcastReceiver : BroadcastReceiver() { @@ -48,15 +65,14 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { } companion object { - const val REQUEST_CODE = 0 + private const val REQUEST_CODE = 0 fun scheduleAlarm(context: Context, userId: String, delay: Long) { //Reschedule val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java).apply { putExtra(SyncService.EXTRA_USER_ID, userId) } - val pIntent = PendingIntent.getBroadcast(context, AlarmSyncBroadcastReceiver.REQUEST_CODE, - intent, PendingIntent.FLAG_UPDATE_CURRENT) + val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) val firstMillis = System.currentTimeMillis() + delay val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -68,8 +84,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { fun cancelAlarm(context: Context) { val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) - val pIntent = PendingIntent.getBroadcast(context, AlarmSyncBroadcastReceiver.REQUEST_CODE, - intent, PendingIntent.FLAG_UPDATE_CURRENT) + val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmMgr.cancel(pIntent) } diff --git a/vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeOrRebootReceiver.kt b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt similarity index 82% rename from vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeOrRebootReceiver.kt rename to vector/src/fdroid/java/im/vector/riotredesign/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt index c8a64e39..ca19d0d2 100644 --- a/vector/src/fdroid/java/im/vector/riotredesign/receiver/OnApplicationUpgradeOrRebootReceiver.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt @@ -15,21 +15,21 @@ * limitations under the License. */ -package im.vector.riotredesign.receiver +package im.vector.riotredesign.fdroid.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import im.vector.riotredesign.core.di.HasVectorInjector -import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver import timber.log.Timber class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Timber.v("## onReceive() ${intent.action}") - if (context is HasVectorInjector) { - val activeSession = context.injector().activeSessionHolder().getSafeActiveSession() + val appContext = context.applicationContext + if (appContext is HasVectorInjector) { + val activeSession = appContext.injector().activeSessionHolder().getSafeActiveSession() if (activeSession != null) { AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.myUserId, 10) } diff --git a/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/service/VectorSyncService.kt similarity index 92% rename from vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt rename to vector/src/fdroid/java/im/vector/riotredesign/fdroid/service/VectorSyncService.kt index 6f105c94..6b3cec1a 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/services/VectorSyncService.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/fdroid/service/VectorSyncService.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotredesign.core.services +package im.vector.riotredesign.fdroid.service import android.app.NotificationManager import android.content.Context @@ -28,12 +28,12 @@ import timber.log.Timber class VectorSyncService : SyncService() { override fun onCreate() { - Timber.v("VectorSyncService - onCreate ") + Timber.v("VectorSyncService - onCreate") super.onCreate() } override fun onDestroy() { - Timber.v("VectorSyncService - onDestroy ") + Timber.v("VectorSyncService - onDestroy") removeForegroundNotif() super.onDestroy() } @@ -57,7 +57,7 @@ class VectorSyncService : SyncService() { } /** - * If the service is bounded and the service was previously started we can remove foreground notif + * If the service is bounded and the service was previously started we can remove foreground notification */ override fun onBind(intent: Intent?): IBinder { Timber.v("VectorSyncService - onBind ") diff --git a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.kt index 11826a7b..fb3c00de 100755 --- a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/FcmHelper.kt @@ -18,9 +18,15 @@ package im.vector.riotredesign.push.fcm import android.app.Activity import android.content.Context - +import im.vector.riotredesign.core.di.ActiveSessionHolder import im.vector.riotredesign.core.pushers.PushersManager +import im.vector.riotredesign.fdroid.receiver.AlarmSyncBroadcastReceiver +import im.vector.riotredesign.features.settings.PreferencesManager +import timber.log.Timber +/** + * This class has an alter ego in the gplay variant. + */ object FcmHelper { fun isPushSupported(): Boolean = false @@ -52,4 +58,17 @@ object FcmHelper { fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) { // No op } + + fun onEnterForeground(context: Context) { + AlarmSyncBroadcastReceiver.cancelAlarm(context) + } + + fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) { + //We need to use alarm in this mode + if (PreferencesManager.areNotificationEnabledForDevice(context) && activeSessionHolder.hasActiveSession()) { + val currentSession = activeSessionHolder.getActiveSession() + AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.myUserId, 4_000L) + Timber.i("Alarm scheduled to restart service") + } + } } diff --git a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt index 0a3fdd22..9a5ada35 100644 --- a/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/fdroid/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -17,9 +17,9 @@ package im.vector.riotredesign.push.fcm import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.Session +import im.vector.riotredesign.fdroid.features.settings.troubleshoot.TestAutoStartBoot +import im.vector.riotredesign.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions import im.vector.riotredesign.features.settings.troubleshoot.* -import im.vector.riotredesign.push.fcm.troubleshoot.TestAutoStartBoot -import im.vector.riotredesign.push.fcm.troubleshoot.TestBackgroundRestrictions class NotificationTroubleshootTestManagerFactory { diff --git a/vector/src/gplay/AndroidManifest.xml b/vector/src/gplay/AndroidManifest.xml index 145da025..8f6a1cef 100755 --- a/vector/src/gplay/AndroidManifest.xml +++ b/vector/src/gplay/AndroidManifest.xml @@ -9,7 +9,7 @@ android:name="firebase_analytics_collection_deactivated" android:value="true" /> - + diff --git a/vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestFirebaseToken.kt b/vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestFirebaseToken.kt new file mode 100644 index 00000000..ec76fd2b --- /dev/null +++ b/vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestFirebaseToken.kt @@ -0,0 +1,74 @@ +/* + * 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.riotredesign.gplay.features.settings.troubleshoot + +import androidx.appcompat.app.AppCompatActivity +import com.google.firebase.iid.FirebaseInstanceId +import im.vector.riotredesign.R +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.core.utils.startAddGoogleAccountIntent +import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager +import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest +import im.vector.riotredesign.push.fcm.FcmHelper +import timber.log.Timber +import javax.inject.Inject + +/* +* Test that app can successfully retrieve a token via firebase + */ +class TestFirebaseToken @Inject constructor(private val context: AppCompatActivity, + private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) { + + override fun perform() { + status = TestStatus.RUNNING + try { + FirebaseInstanceId.getInstance().instanceId + .addOnCompleteListener(context) { task -> + if (!task.isSuccessful) { + val errorMsg = if (task.exception == null) "Unknown" else task.exception!!.localizedMessage + //Can't find where this constant is (not documented -or deprecated in docs- and all obfuscated) + if ("SERVICE_NOT_AVAILABLE".equals(errorMsg)) { + description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg) + } else if ("TOO_MANY_REGISTRATIONS".equals(errorMsg)) { + description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_too_many_registration, errorMsg) + } else if ("ACCOUNT_MISSING".equals(errorMsg)) { + description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg) + quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) { + override fun doFix() { + startAddGoogleAccountIntent(context, NotificationTroubleshootTestManager.REQ_CODE_FIX) + } + } + } else { + description = stringProvider.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 = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, tok) + Timber.e("Retrieved FCM token success [$it].") + FcmHelper.storeFcmToken(context, tok) + } + status = TestStatus.SUCCESS + } + } + } catch (e: Throwable) { + description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, e.localizedMessage) + status = TestStatus.FAILED + } + } + +} \ No newline at end of file diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestPlayServices.kt b/vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestPlayServices.kt similarity index 66% rename from vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestPlayServices.kt rename to vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestPlayServices.kt index 5d56712b..f99787c6 100644 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestPlayServices.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestPlayServices.kt @@ -13,40 +13,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotredesign.push.fcm.troubleshoot +package im.vector.riotredesign.gplay.features.settings.troubleshoot -import androidx.fragment.app.Fragment +import androidx.appcompat.app.AppCompatActivity import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import im.vector.riotredesign.R +import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest import timber.log.Timber +import javax.inject.Inject /* * 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) { +class TestPlayServices @Inject constructor(private val context: AppCompatActivity, + private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) { override fun perform() { val apiAvailability = GoogleApiAvailability.getInstance() - val resultCode = apiAvailability.isGooglePlayServicesAvailable(fragment.context) + val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) if (resultCode == ConnectionResult.SUCCESS) { quickFix = null - description = fragment.getString(R.string.settings_troubleshoot_test_play_services_success) + description = stringProvider.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() - } + apiAvailability.getErrorDialog(context, resultCode, 9000 /*hey does the magic number*/).show() } } Timber.e("Play Services apk error $resultCode -> ${apiAvailability.getErrorString(resultCode)}.") } - description = fragment.getString(R.string.settings_troubleshoot_test_play_services_failed, apiAvailability.getErrorString(resultCode)) + description = stringProvider.getString(R.string.settings_troubleshoot_test_play_services_failed, apiAvailability.getErrorString(resultCode)) status = TestStatus.FAILED } } diff --git a/vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestTokenRegistration.kt b/vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestTokenRegistration.kt new file mode 100644 index 00000000..4485ce90 --- /dev/null +++ b/vector/src/gplay/java/im/vector/riotredesign/gplay/features/settings/troubleshoot/TestTokenRegistration.kt @@ -0,0 +1,79 @@ +/* + * 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.riotredesign.gplay.features.settings.troubleshoot + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.work.WorkInfo +import androidx.work.WorkManager +import im.vector.matrix.android.api.session.pushers.PusherState +import im.vector.riotredesign.R +import im.vector.riotredesign.core.di.ActiveSessionHolder +import im.vector.riotredesign.core.pushers.PushersManager +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest +import im.vector.riotredesign.push.fcm.FcmHelper +import javax.inject.Inject + +/** + * Force registration of the token to HomeServer + */ +class TestTokenRegistration @Inject constructor(private val context: AppCompatActivity, + private val stringProvider: StringProvider, + private val pushersManager: PushersManager, + private val activeSessionHolder: ActiveSessionHolder) : TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) { + + override fun perform() { + //Check if we have a registered pusher for this token + val fcmToken = FcmHelper.getFcmToken(context) ?: run { + status = TestStatus.FAILED + return + } + val session = activeSessionHolder.getSafeActiveSession() ?: run { + status = TestStatus.FAILED + return + } + val pusher = session.pushers().filter { + it.pushKey == fcmToken && it.state == PusherState.REGISTERED + } + if (pusher.isEmpty()) { + description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_failed, null) + quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) { + override fun doFix() { + val workId = pushersManager.registerPusherWithFcmKey(fcmToken) + WorkManager.getInstance().getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> + if (workInfo != null) { + if (workInfo.state == WorkInfo.State.SUCCEEDED) { + manager?.retry() + } else if (workInfo.state == WorkInfo.State.FAILED) { + manager?.retry() + } + } + }) + } + + } + + status = TestStatus.FAILED + + } else { + description = stringProvider.getString(R.string.settings_troubleshoot_test_token_registration_success) + status = TestStatus.SUCCESS + } + + } + +} \ No newline at end of file diff --git a/vector/src/gplay/java/im/vector/riotredesign/gplay/package-info.kt b/vector/src/gplay/java/im/vector/riotredesign/gplay/package-info.kt new file mode 100644 index 00000000..60938525 --- /dev/null +++ b/vector/src/gplay/java/im/vector/riotredesign/gplay/package-info.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019 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. + */ + +/** + * Code exclusively used by the GPlay build and not referenced on the main source code + */ +package im.vector.riotredesign.gplay + diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/riotredesign/gplay/push/fcm/VectorFirebaseMessagingService.kt similarity index 89% rename from vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt rename to vector/src/gplay/java/im/vector/riotredesign/gplay/push/fcm/VectorFirebaseMessagingService.kt index 1355ef63..bd30e5c9 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -17,7 +17,7 @@ * limitations under the License. */ -package im.vector.riotredesign.push.fcm +package im.vector.riotredesign.gplay.push.fcm import android.os.Handler import android.os.Looper @@ -26,11 +26,12 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ProcessLifecycleOwner import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.R +import im.vector.riotredesign.core.di.ActiveSessionHolder +import im.vector.riotredesign.core.extensions.vectorComponent import im.vector.riotredesign.core.preference.BingRule import im.vector.riotredesign.core.pushers.PushersManager import im.vector.riotredesign.features.badge.BadgeProxy @@ -38,35 +39,31 @@ import im.vector.riotredesign.features.notifications.NotifiableEventResolver import im.vector.riotredesign.features.notifications.NotifiableMessageEvent import im.vector.riotredesign.features.notifications.NotificationDrawerManager import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent -<<<<<<< HEAD -======= import im.vector.riotredesign.features.settings.PreferencesManager -import org.koin.android.ext.android.inject ->>>>>>> develop +import im.vector.riotredesign.push.fcm.FcmHelper import timber.log.Timber +import javax.inject.Inject /** * Class extending FirebaseMessagingService. */ class VectorFirebaseMessagingService : FirebaseMessagingService() { -<<<<<<< HEAD @Inject lateinit var notificationDrawerManager: NotificationDrawerManager + @Inject lateinit var notifiableEventResolver: NotifiableEventResolver + @Inject lateinit var pusherManager: PushersManager + @Inject lateinit var activeSessionHolder: ActiveSessionHolder - private val notifiableEventResolver by lazy { - NotifiableEventResolver(this) - } -======= - private val notificationDrawerManager by inject() - private val pusherManager by inject() ->>>>>>> develop - - private val notifiableEventResolver by inject() // UI handler private val mUIHandler by lazy { Handler(Looper.getMainLooper()) } + override fun onCreate() { + super.onCreate() + vectorComponent().inject(this) + } + /** * Called when message is received. * @@ -103,7 +100,6 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { * you retrieve the token. */ override fun onNewToken(refreshedToken: String?) { - if (Matrix.getInstance().currentSession == null) return Timber.i("onNewToken: FCM Token has been updated") FcmHelper.storeFcmToken(this, refreshedToken) if (refreshedToken == null) { @@ -149,7 +145,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { val unreadCount = data.get("unread")?.let { Integer.parseInt(it) } ?: 0 BadgeProxy.updateBadgeCount(applicationContext, unreadCount) - val session = safeGetCurrentSession() + val session = activeSessionHolder.getSafeActiveSession() if (session == null) { Timber.w("## Can't sync from push, no current session") @@ -167,21 +163,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { } } - fun safeGetCurrentSession(): Session? { - try { - return Matrix.getInstance().currentSession - } catch (e: Throwable) { - Timber.e(e, "## Failed to get current session") - return null - } - } - // check if the event was not yet received // a previous catchup might have already retrieved the notified event private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean { if (null != eventId && null != roomId) { try { - val session = safeGetCurrentSession() ?: return false + val session = activeSessionHolder.getSafeActiveSession() ?: return false val room = session.getRoom(roomId) ?: return false return room.getTimeLineEvent(eventId) != null } catch (e: Exception) { @@ -241,7 +228,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { if (notifiableEvent is NotifiableMessageEvent) { if (TextUtils.isEmpty(notifiableEvent.senderName)) { notifiableEvent.senderName = data["sender_display_name"] - ?: data["sender"] ?: "" + ?: data["sender"] ?: "" } if (TextUtils.isEmpty(notifiableEvent.roomName)) { notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: "" @@ -287,11 +274,11 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { try { return Event(eventId = data["event_id"], - senderId = data["sender"], - roomId = data["room_id"], - type = data.getValue("type"), + senderId = data["sender"], + roomId = data["room_id"], + type = data.getValue("type"), // TODO content = data.getValue("content"), - originServerTs = System.currentTimeMillis()) + originServerTs = System.currentTimeMillis()) } catch (e: Exception) { Timber.e(e, "buildEvent fails ") } diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt index 25f380f4..ca807f36 100755 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/FcmHelper.kt @@ -25,6 +25,7 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.firebase.iid.FirebaseInstanceId import im.vector.riotredesign.R +import im.vector.riotredesign.core.di.ActiveSessionHolder import im.vector.riotredesign.core.pushers.PushersManager import timber.log.Timber @@ -99,4 +100,12 @@ object FcmHelper { val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity) return resultCode == ConnectionResult.SUCCESS } + + fun onEnterForeground(context: Context) { + // No op + } + + fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) { + // TODO FCM fallback + } } diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt index ee4e59e4..d7459b7e 100644 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -16,30 +16,34 @@ package im.vector.riotredesign.push.fcm import androidx.fragment.app.Fragment -import im.vector.matrix.android.api.session.Session -import im.vector.riotredesign.features.settings.troubleshoot.* -import im.vector.riotredesign.push.fcm.troubleshoot.TestFirebaseToken -import im.vector.riotredesign.push.fcm.troubleshoot.TestPlayServices -import im.vector.riotredesign.push.fcm.troubleshoot.TestTokenRegistration +import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager +import im.vector.riotredesign.features.settings.troubleshoot.TestAccountSettings +import im.vector.riotredesign.features.settings.troubleshoot.TestBingRulesSettings +import im.vector.riotredesign.features.settings.troubleshoot.TestDeviceSettings +import im.vector.riotredesign.features.settings.troubleshoot.TestSystemSettings +import im.vector.riotredesign.gplay.features.settings.troubleshoot.TestFirebaseToken +import im.vector.riotredesign.gplay.features.settings.troubleshoot.TestPlayServices +import im.vector.riotredesign.gplay.features.settings.troubleshoot.TestTokenRegistration +import javax.inject.Inject -class NotificationTroubleshootTestManagerFactory { +class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings, + private val testAccountSettings: TestAccountSettings, + private val testDeviceSettings: TestDeviceSettings, + private val testBingRulesSettings: TestBingRulesSettings, + private val testPlayServices: TestPlayServices, + private val testFirebaseToken: TestFirebaseToken, + private val testTokenRegistration: TestTokenRegistration) { - companion object { - fun createTestManager(fragment: Fragment, session: Session?): NotificationTroubleshootTestManager { - val mgr = NotificationTroubleshootTestManager(fragment) - mgr.addTest(TestSystemSettings(fragment)) - if (session != null) { - mgr.addTest(TestAccountSettings(fragment, session)) - } - mgr.addTest(TestDeviceSettings(fragment)) - if (session != null) { - mgr.addTest(TestBingRulesSettings(fragment, session)) - } - mgr.addTest(TestPlayServices(fragment)) - mgr.addTest(TestFirebaseToken(fragment)) - mgr.addTest(TestTokenRegistration(fragment)) - return mgr - } + fun create(fragment: Fragment): NotificationTroubleshootTestManager { + val mgr = NotificationTroubleshootTestManager(fragment) + mgr.addTest(testSystemSettings) + mgr.addTest(testAccountSettings) + mgr.addTest(testDeviceSettings) + mgr.addTest(testBingRulesSettings) + mgr.addTest(testPlayServices) + mgr.addTest(testFirebaseToken) + mgr.addTest(testTokenRegistration) + return mgr } } \ No newline at end of file diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestFirebaseToken.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestFirebaseToken.kt deleted file mode 100644 index 6ab552da..00000000 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestFirebaseToken.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.riotredesign.push.fcm.troubleshoot - -import androidx.fragment.app.Fragment -import com.google.firebase.iid.FirebaseInstanceId -import im.vector.riotredesign.R -import im.vector.riotredesign.core.utils.startAddGoogleAccountIntent -import im.vector.riotredesign.features.settings.troubleshoot.NotificationTroubleshootTestManager -import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest -import timber.log.Timber - -/* -* 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 - val activity = fragment.activity - if (activity != null) { - try { - FirebaseInstanceId.getInstance().instanceId - .addOnCompleteListener(activity) { task -> - if (!task.isSuccessful) { - val errorMsg = if (task.exception == null) "Unknown" else task.exception!!.localizedMessage - //Can't find where this constant is (not documented -or deprecated in docs- and all obfuscated) - if ("SERVICE_NOT_AVAILABLE".equals(errorMsg)) { - description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg) - } else if ("TOO_MANY_REGISTRATIONS".equals(errorMsg)) { - description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed_too_many_registration, errorMsg) - } else if ("ACCOUNT_MISSING".equals(errorMsg)) { - description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg) - quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) { - override fun doFix() { - startAddGoogleAccountIntent(fragment, NotificationTroubleshootTestManager.REQ_CODE_FIX) - } - } - } else { - 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) - Timber.e("Retrieved FCM token success [$it].") - } - status = TestStatus.SUCCESS - } - } - } catch (e: Throwable) { - description = fragment.getString(R.string.settings_troubleshoot_test_fcm_failed, e.localizedMessage) - status = TestStatus.FAILED - } - } else { - status = TestStatus.FAILED - } - } - -} \ No newline at end of file diff --git a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestTokenRegistration.kt b/vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestTokenRegistration.kt deleted file mode 100644 index 912597ec..00000000 --- a/vector/src/gplay/java/im/vector/riotredesign/push/fcm/troubleshoot/TestTokenRegistration.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.riotredesign.push.fcm.troubleshoot - -import androidx.fragment.app.Fragment -import im.vector.riotredesign.R -import im.vector.riotredesign.features.settings.troubleshoot.TroubleshootTest - -/** - * Force registration of the token to HomeServer - */ -class TestTokenRegistration(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) { - - override fun perform() { - /* - TODO - Matrix.getInstance(VectorApp.getInstance().baseContext).pushManager.forceSessionsRegistration(object : MatrixCallback { - 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 - } - }) - */ - - status = TestStatus.FAILED - - } - -} \ No newline at end of file diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index aaae3edb..8fa24217 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -68,15 +68,6 @@ - - - - - - - diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt index e6f65fbc..4fc217a0 100644 --- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt @@ -42,7 +42,6 @@ import im.vector.riotredesign.core.di.DaggerVectorComponent import im.vector.riotredesign.core.di.HasVectorInjector import im.vector.riotredesign.core.di.VectorComponent import im.vector.riotredesign.core.extensions.configureAndStart -import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.riotredesign.features.notifications.NotificationDrawerManager @@ -50,7 +49,6 @@ import im.vector.riotredesign.features.notifications.NotificationUtils import im.vector.riotredesign.features.notifications.PushRuleTriggerListener import im.vector.riotredesign.features.rageshake.VectorFileLogger import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler -import im.vector.riotredesign.features.settings.PreferencesManager import im.vector.riotredesign.features.version.getVersion import im.vector.riotredesign.push.fcm.FcmHelper import timber.log.Timber @@ -113,7 +111,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { - AlarmSyncBroadcastReceiver.cancelAlarm(appContext) + FcmHelper.onEnterForeground(appContext) activeSessionHolder.getSafeActiveSession()?.also { it.stopAnyBackgroundSync() } @@ -123,19 +121,8 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. fun entersBackground() { Timber.i("App entered background") // call persistInfo notificationDrawerManager.persistInfo() - if (FcmHelper.isPushSupported()) { - //TODO FCM fallback - } else { - //TODO check if notifications are enabled for this device - //We need to use alarm in this mode - val activeSession = activeSessionHolder.getSafeActiveSession() - if (activeSession != null && PreferencesManager.areNotificationEnabledForDevice(applicationContext)) { - AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext, activeSession.myUserId, 4_000L) - Timber.i("Alarm scheduled to restart service") - } - } + FcmHelper.onEnterBackground(appContext, activeSessionHolder) } - }) } diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotredesign/core/di/ScreenComponent.kt index 8f3a3180..f20ddc5d 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/ScreenComponent.kt @@ -22,6 +22,7 @@ import dagger.BindsInstance import dagger.Component import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment import im.vector.matrix.android.api.session.Session +import im.vector.riotredesign.core.preference.UserAvatarPreference import im.vector.riotredesign.features.MainActivity import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyFragment import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreSuccessFragment @@ -54,6 +55,7 @@ import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerF import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment import im.vector.riotredesign.features.settings.VectorSettingsActivity import im.vector.riotredesign.features.settings.VectorSettingsNotificationPreferenceFragment +import im.vector.riotredesign.features.settings.VectorSettingsNotificationsTroubleshootFragment import im.vector.riotredesign.features.settings.VectorSettingsPreferencesFragment @Component(dependencies = [VectorComponent::class], modules = [ViewModelModule::class, HomeModule::class]) @@ -122,8 +124,6 @@ interface ScreenComponent { fun inject(mainActivity: MainActivity) - fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment) - fun inject(roomDirectoryActivity: RoomDirectoryActivity) fun inject(bugReportActivity: BugReportActivity) @@ -136,6 +136,12 @@ interface ScreenComponent { fun inject(vectorSettingsNotificationPreferenceFragment: VectorSettingsNotificationPreferenceFragment) + fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment) + + fun inject(userAvatarPreference: UserAvatarPreference) + + fun inject(vectorSettingsNotificationsTroubleshootFragment: VectorSettingsNotificationsTroubleshootFragment) + @Component.Factory interface Factory { fun create(vectorComponent: VectorComponent, diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotredesign/core/di/VectorComponent.kt index 5be30bb0..c32d2d9a 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/VectorComponent.kt @@ -39,6 +39,7 @@ import im.vector.riotredesign.features.notifications.PushRuleTriggerListener import im.vector.riotredesign.features.rageshake.BugReporter import im.vector.riotredesign.features.rageshake.RageShake import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler +import im.vector.riotredesign.gplay.push.fcm.VectorFirebaseMessagingService import javax.inject.Singleton @Component(modules = [VectorModule::class]) @@ -49,6 +50,8 @@ interface VectorComponent { fun inject(vectorApplication: VectorApplication) + fun inject(vectorFirebaseMessagingService: VectorFirebaseMessagingService) + fun matrix(): Matrix fun currentSession(): Session @@ -61,6 +64,8 @@ interface VectorComponent { fun vectorConfiguration(): VectorConfiguration + fun avatarRenderer(): AvatarRenderer + fun activeSessionHolder(): ActiveSessionHolder fun emojiCompatFontProvider(): EmojiCompatFontProvider diff --git a/vector/src/main/java/im/vector/riotredesign/core/extensions/Context.kt b/vector/src/main/java/im/vector/riotredesign/core/extensions/Context.kt new file mode 100644 index 00000000..f9b30a47 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/extensions/Context.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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.riotredesign.core.extensions + +import android.content.Context +import im.vector.riotredesign.core.di.HasVectorInjector +import im.vector.riotredesign.core.di.VectorComponent + +fun Context.vectorComponent(): VectorComponent { + val appContext = applicationContext + if (appContext is HasVectorInjector) { + return appContext.injector() + } else { + throw IllegalStateException("Your application context doesn't implement HasVectorInjector") + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/preference/UserAvatarPreference.kt b/vector/src/main/java/im/vector/riotredesign/core/preference/UserAvatarPreference.kt index 89eb6549..4acd8265 100755 --- a/vector/src/main/java/im/vector/riotredesign/core/preference/UserAvatarPreference.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/preference/UserAvatarPreference.kt @@ -24,6 +24,8 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import im.vector.matrix.android.api.session.Session import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.vectorComponent +import im.vector.riotredesign.features.home.AvatarRenderer open class UserAvatarPreference : Preference { @@ -31,6 +33,8 @@ open class UserAvatarPreference : Preference { internal var mSession: Session? = null private var mLoadingProgressBar: ProgressBar? = null + private var avatarRenderer: AvatarRenderer = context.vectorComponent().avatarRenderer() + constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) @@ -52,11 +56,14 @@ open class UserAvatarPreference : Preference { } open fun refreshAvatar() { - if (null != mAvatarView && null != mSession) { - // TODO - // val myUser = session!!.myUser - // VectorUtils.loadUserAvatar(context, session, mAvatarView, myUser.avatarUrl, myUser.user_id, myUser.displayname) + val session = mSession ?: return + val view = mAvatarView ?: return + session.getUser(session.sessionParams.credentials.userId)?.let { + avatarRenderer.render(it, view) + } ?: run { + avatarRenderer.render(null, session.sessionParams.credentials.userId, null, view) } + } fun setSession(session: Session) { diff --git a/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt index 777e04ca..5d2ee895 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/pushers/PushersManager.kt @@ -1,26 +1,28 @@ package im.vector.riotredesign.core.pushers import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.Session import im.vector.riotredesign.R +import im.vector.riotredesign.core.di.ActiveSessionHolder import im.vector.riotredesign.core.resources.AppNameProvider import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.StringProvider +import java.util.* import javax.inject.Inject private const val DEFAULT_PUSHER_FILE_TAG = "mobile" class PushersManager @Inject constructor( - private val currentSession: Session, + private val activeSessionHolder: ActiveSessionHolder, private val localeProvider: LocaleProvider, private val stringProvider: StringProvider, private val appNameProvider: AppNameProvider ) { - fun registerPusherWithFcmKey(pushKey: String) { - var profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + Math.abs(currentSession.sessionParams.credentials.userId.hashCode()) + fun registerPusherWithFcmKey(pushKey: String): UUID { + val currentSession = activeSessionHolder.getActiveSession() + var profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + Math.abs(currentSession.myUserId.hashCode()) - currentSession.addHttpPusher( + return currentSession.addHttpPusher( pushKey, stringProvider.getString(R.string.pusher_app_id), profileTag, @@ -34,6 +36,7 @@ class PushersManager @Inject constructor( } fun unregisterPusher(pushKey: String, callback: MatrixCallback) { - currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id),callback) + val currentSession = activeSessionHolder.getActiveSession() + currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id), callback) } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/riotredesign/core/utils/SystemUtils.kt index 5c8e3c57..c24ae28c 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/utils/SystemUtils.kt @@ -18,12 +18,17 @@ package im.vector.riotredesign.core.utils import android.annotation.TargetApi import android.app.Activity -import android.content.* +import android.content.ActivityNotFoundException +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent import android.net.Uri import android.os.Build import android.os.PowerManager import android.provider.Settings import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import im.vector.riotredesign.R import im.vector.riotredesign.features.notifications.supportNotificationChannels @@ -44,7 +49,7 @@ import java.util.* fun isIgnoringBatteryOptimizations(context: Context): Boolean { // no issue before Android M, battery optimisations did not exist return Build.VERSION.SDK_INT < Build.VERSION_CODES.M - || (context.getSystemService(Context.POWER_SERVICE) as PowerManager?)?.isIgnoringBatteryOptimizations(context.packageName) == true + || (context.getSystemService(Context.POWER_SERVICE) as PowerManager?)?.isIgnoringBatteryOptimizations(context.packageName) == true } /** @@ -109,22 +114,22 @@ fun getDeviceLocale(context: Context): 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) { +fun startNotificationSettingsIntent(activity: AppCompatActivity, requestCode: Int) { val intent = Intent() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS - intent.putExtra(Settings.EXTRA_APP_PACKAGE, fragment.context?.packageName) + intent.putExtra(Settings.EXTRA_APP_PACKAGE, activity.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) + intent.putExtra("app_package", activity.packageName) + intent.putExtra("app_uid", activity.applicationInfo?.uid) } else { intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS intent.addCategory(Intent.CATEGORY_DEFAULT); - val uri = Uri.fromParts("package", fragment.activity?.packageName, null) + val uri = Uri.fromParts("package", activity.packageName, null) intent.data = uri } - fragment.startActivityForResult(intent, requestCode) + activity.startActivityForResult(intent, requestCode) } /** @@ -140,13 +145,13 @@ fun startNotificationChannelSettingsIntent(fragment: Fragment, channelID: String fragment.startActivity(intent) } -fun startAddGoogleAccountIntent(fragment: Fragment, requestCode: Int) { +fun startAddGoogleAccountIntent(context: AppCompatActivity, requestCode: Int) { try { val intent = Intent(Settings.ACTION_ADD_ACCOUNT) intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google")) - fragment.startActivityForResult(intent, requestCode) + context.startActivityForResult(intent, requestCode) } catch (activityNotFoundException: ActivityNotFoundException) { - fragment.activity?.toast(R.string.error_no_external_application_found) + context.toast(R.string.error_no_external_application_found) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 8bc2253b..f7a3b964 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -512,6 +512,7 @@ class RoomDetailFragment : } else if (state.asyncInviter.complete) { vectorBaseActivity.finish() } + composerLayout.setRoomEncrypted(state.isEncrypted) } private fun renderRoomSummary(state: RoomDetailViewState) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index a81b30b6..d34df056 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -496,7 +496,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun observeRoomSummary() { room.rx().liveRoomSummary() .execute { async -> - copy(asyncRoomSummary = async) + copy( + asyncRoomSummary = async, + isEncrypted = room.isEncrypted() + ) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt index 927bbba1..36408604 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt @@ -46,7 +46,8 @@ data class RoomDetailViewState( val asyncInviter: Async = Uninitialized, val asyncRoomSummary: Async = Uninitialized, val sendMode: SendMode = SendMode.REGULAR, - val selectedEvent: TimelineEvent? = null + val selectedEvent: TimelineEvent? = null, + val isEncrypted: Boolean = false ) : MvRxState { constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/composer/TextComposerView.kt index c0fc2725..f31d655e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/composer/TextComposerView.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/composer/TextComposerView.kt @@ -9,7 +9,6 @@ import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import androidx.core.view.isVisible import androidx.transition.AutoTransition import androidx.transition.Transition import androidx.transition.TransitionManager @@ -113,4 +112,13 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib it.applyTo(this) } } + + fun setRoomEncrypted(isEncrypted: Boolean) { + composerEditText.setHint( + if (isEncrypted) { + R.string.room_message_placeholder_encrypted + } else { + R.string.room_message_placeholder_not_encrypted + }) + } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index cdea18e7..16dd5d1b 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -89,14 +89,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted val event = session.getRoom(roomId)?.getTimeLineEvent(eventId) ?: return state var body: CharSequence? = null val originTs = event.root.originServerTs - when (event.root.type) { + when (event.root.getClearType()) { EventType.MESSAGE -> { val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() - ?: event.root.content.toModel() + ?: event.root.getClearContent().toModel() body = messageContent?.body if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { body = eventHtmlRenderer.render(messageContent.formattedBody - ?: messageContent.body) + ?: messageContent.body) } } EventType.STATE_ROOM_NAME, diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index 44b1ed69..3f170527 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -64,12 +64,9 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat // TODO This is not correct format for error, change it val informationData = messageInformationDataFactory.create(event, nextEvent) - return NoticeItem_() - .avatarRenderer(avatarRenderer) - .noticeText(spannableStr) - return MessageTextItem_() .message(spannableStr) + .avatarRenderer(avatarRenderer) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt index 0c04eba0..2272487e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationBroadcastReceiver.kt @@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.Room import im.vector.riotredesign.R import im.vector.riotredesign.core.di.ActiveSessionHolder -import im.vector.riotredesign.core.di.HasVectorInjector +import im.vector.riotredesign.core.extensions.vectorComponent import timber.log.Timber import java.util.* import javax.inject.Inject @@ -42,10 +42,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent == null || context == null) return Timber.v("NotificationBroadcastReceiver received : $intent") - val appContext = context.applicationContext - if (appContext is HasVectorInjector) { - appContext.injector().inject(this) - } + context.vectorComponent().inject(this) when (intent.action) { NotificationUtils.SMART_REPLY_ACTION -> handleSmartReply(intent, context) diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt index 67e30c57..36534334 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt @@ -376,7 +376,7 @@ object NotificationUtils { val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) // Build the pending intent for when the notification is clicked val openRoomIntent = buildOpenRoomIntent(context, roomInfo.roomId) - val smallIcon = if (roomInfo.shouldBing) R.drawable.icon_notif_important else R.drawable.logo_transparent + val smallIcon = R.drawable.ic_status_bar val channelID = if (roomInfo.shouldBing) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID return NotificationCompat.Builder(context, channelID) @@ -479,7 +479,7 @@ object NotificationUtils { fun buildSimpleEventNotification(context: Context, simpleNotifiableEvent: NotifiableEvent, largeIcon: Bitmap?, matrixId: String): Notification? { val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) // Build the pending intent for when the notification is clicked - val smallIcon = if (simpleNotifiableEvent.noisy) R.drawable.icon_notif_important else R.drawable.logo_transparent + val smallIcon = R.drawable.ic_status_bar val channelID = if (simpleNotifiableEvent.noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID @@ -611,7 +611,7 @@ object NotificationUtils { noisy: Boolean, lastMessageTimestamp: Long): Notification? { val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) - val smallIcon = if (noisy) R.drawable.icon_notif_important else R.drawable.logo_transparent + val smallIcon = R.drawable.ic_status_bar return NotificationCompat.Builder(context, if (noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID) // used in compat < N, after summary is built based on child notifications diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt index 886873e2..3fa30b31 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/PushRuleTriggerListener.kt @@ -28,7 +28,7 @@ import javax.inject.Singleton @Singleton class PushRuleTriggerListener @Inject constructor( private val resolver: NotifiableEventResolver, - private val drawerManager: NotificationDrawerManager + private val notificationDrawerManager: NotificationDrawerManager ) : PushRuleService.PushRuleListener { @@ -42,14 +42,14 @@ class PushRuleTriggerListener @Inject constructor( } val notificationAction = NotificationAction.extractFrom(actions) if (notificationAction.shouldNotify) { - val resolveEvent = resolver.resolveEvent(event, session!!) - if (resolveEvent == null) { + val notifiableEvent = resolver.resolveEvent(event, session!!) + if (notifiableEvent == null) { Timber.v("## Failed to resolve event") //TODO } else { - resolveEvent.noisy = !notificationAction.soundName.isNullOrBlank() - Timber.v("New event to notify $resolveEvent tweaks:$notificationAction") - drawerManager.onNotifiableEventReceived(resolveEvent) + notifiableEvent.noisy = !notificationAction.soundName.isNullOrBlank() + Timber.v("New event to notify $notifiableEvent tweaks:$notificationAction") + notificationDrawerManager.onNotifiableEventReceived(notifiableEvent) } } else { Timber.v("Matched push rule is set to not notify") @@ -57,7 +57,7 @@ class PushRuleTriggerListener @Inject constructor( } override fun batchFinish() { - drawerManager.refreshNotificationDrawer() + notificationDrawerManager.refreshNotificationDrawer() } fun startWithSession(session: Session) { @@ -71,7 +71,7 @@ class PushRuleTriggerListener @Inject constructor( fun stop() { session?.removePushRuleListener(this) session = null - drawerManager.clearAllEvents() + notificationDrawerManager.clearAllEvents() } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java b/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java index cf559797..d103ffb3 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/PreferencesManager.java @@ -25,14 +25,15 @@ import android.net.Uri; import android.provider.MediaStore; import android.text.TextUtils; +import androidx.annotation.Nullable; +import androidx.preference.PreferenceManager; + import java.io.File; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; import im.vector.riotredesign.R; import im.vector.riotredesign.features.homeserver.ServerUrlsRepository; import im.vector.riotredesign.features.themes.ThemeUtils; @@ -51,8 +52,6 @@ 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"; - //TODO delete - 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_NOTIFICATION_ADVANCED_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY"; public static final String SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY = "SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY"; @@ -119,7 +118,7 @@ public class PreferencesManager { public static final String SETTINGS_NOTIFICATIONS_KEY = "SETTINGS_NOTIFICATIONS_KEY"; public static final String SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY = "SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY"; public static final String SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY = "SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY"; - public static final String SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY = "SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY"; + // public static final String SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY = "SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY"; public static final String SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY"; public static final String SETTINGS_SYSTEM_NOISY_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_NOISY_NOTIFICATION_PREFERENCE_KEY"; public static final String SETTINGS_SYSTEM_SILENT_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_SILENT_NOTIFICATION_PREFERENCE_KEY"; @@ -247,6 +246,13 @@ public class PreferencesManager { return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, true); } + public static void setNotificationEnabledForDevice(Context context, Boolean enabled) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, enabled) + .apply(); + } + /** * Tells if we have already asked the user to disable battery optimisations on android >= M devices. * diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt index 15b67efd..5207efd5 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt @@ -53,20 +53,15 @@ class VectorSettingsActivity : VectorBaseActivity(), override fun initUiAndData() { configureToolbar(settingsToolbar) - var vectorSettingsPreferencesFragment: Fragment? = null if (isFirstCreation()) { - vectorSettingsPreferencesFragment = VectorSettingsPreferencesFragmentV2.newInstance() + val vectorSettingsPreferencesFragment = VectorSettingsRootFragment.newInstance() // display the fragment supportFragmentManager.beginTransaction() .replace(R.id.vector_settings_page, vectorSettingsPreferencesFragment, FRAGMENT_TAG) .commit() - } else { - vectorSettingsPreferencesFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) } - supportFragmentManager.addOnBackStackChangedListener(this) - } override fun onDestroy() { @@ -83,9 +78,7 @@ class VectorSettingsActivity : VectorBaseActivity(), override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?): Boolean { var oFragment: Fragment? = null - if ("Legacy" == pref?.title) { - oFragment = VectorSettingsPreferencesFragment.newInstance(session.sessionParams.credentials.userId) - } else if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) { + if (PreferencesManager.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) { oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.sessionParams.credentials.userId) } else if (PreferencesManager.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) { oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.sessionParams.credentials.userId) diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt index bb93ae8f..1392a3ba 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt @@ -36,11 +36,7 @@ import im.vector.riotredesign.features.notifications.NotificationUtils import im.vector.riotredesign.features.notifications.supportNotificationChannels import javax.inject.Inject -class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFragment() { - - // members - @Inject lateinit var session: Session - private var mLoadingView: View? = null +class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseFragment() { // events listener /* TODO @@ -53,10 +49,9 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra override var titleRes: Int = R.string.settings_notification_advanced - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - // define the layout - addPreferencesFromResource(R.xml.vector_settings_notification_advanced_preferences) + override val preferenceXmlRes = R.xml.vector_settings_notification_advanced_preferences + override fun bindPref() { val callNotificationsSystemOptions = findPreference(PreferencesManager.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY) if (supportNotificationChannels()) { callNotificationsSystemOptions.onPreferenceClickListener = Preference.OnPreferenceClickListener { @@ -177,33 +172,6 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra } } - override fun onResume() { - super.onResume() - // find the view from parent activity - mLoadingView = activity!!.findViewById(R.id.vector_settings_spinner_views) - - /* TODO - if (session.isAlive) { - - session.dataHandler.addListener(mEventsListener) - - // refresh anything else - refreshPreferences() - refreshDisplay() - } - */ - } - - override fun onPause() { - super.onPause() - - /* TODO - if (session.isAlive) { - session.dataHandler.removeListener(mEventsListener) - } - */ - } - /** * Refresh the known information about the account */ @@ -246,30 +214,6 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorPreferenceFra } } - - //============================================================================================================== - // Display methods - //============================================================================================================== - - /** - * Display the loading view. - */ - private fun displayLoadingView() { - if (null != mLoadingView) { - mLoadingView!!.visibility = View.VISIBLE - } - } - - /** - * Hide the loading view. - */ - private fun hideLoadingView() { - if (null != mLoadingView) { - mLoadingView!!.visibility = View.GONE - } - } - - /* ========================================================================================== * Companion * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsBaseFragment.kt new file mode 100644 index 00000000..f5abb7ce --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsBaseFragment.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2019 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.riotredesign.features.settings + +import android.content.Context +import android.os.Bundle +import android.text.TextUtils +import android.view.View +import androidx.annotation.CallSuper +import androidx.preference.PreferenceFragmentCompat +import im.vector.matrix.android.api.session.Session +import im.vector.riotredesign.R +import im.vector.riotredesign.core.di.DaggerScreenComponent +import im.vector.riotredesign.core.di.HasScreenInjector +import im.vector.riotredesign.core.di.ScreenComponent +import im.vector.riotredesign.core.platform.VectorBaseActivity +import im.vector.riotredesign.core.utils.toast +import timber.log.Timber + +abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), HasScreenInjector { + + val vectorActivity: VectorBaseActivity by lazy { + activity as VectorBaseActivity + } + + private var mLoadingView: View? = null + + // members + protected lateinit var session: Session + private lateinit var screenComponent: ScreenComponent + + abstract val preferenceXmlRes: Int + + @CallSuper + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(preferenceXmlRes) + bindPref() + } + + override fun onAttach(context: Context) { + screenComponent = DaggerScreenComponent.factory().create(vectorActivity.getVectorComponent(), vectorActivity) + super.onAttach(context) + session = screenComponent.session() + injectWith(injector()) + } + + protected open fun injectWith(injector: ScreenComponent) = Unit + + override fun injector(): ScreenComponent { + return screenComponent + } + + override fun onResume() { + super.onResume() + Timber.v("onResume Fragment ${this.javaClass.simpleName}") + vectorActivity.supportActionBar?.setTitle(titleRes) + // find the view from parent activity + mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views) + } + + abstract fun bindPref() + + abstract var titleRes: Int + + /* ========================================================================================== + * Protected + * ========================================================================================== */ + + protected fun notImplemented() { + // Snackbar cannot be display on PreferenceFragment + // Snackbar.make(view!!, R.string.not_implemented, Snackbar.LENGTH_SHORT) + activity?.toast(R.string.not_implemented) + } + + /** + * Display the loading view. + */ + protected fun displayLoadingView() { + // search the loading view from the upper view + if (null == mLoadingView) { + var parent = view + + while (parent != null && mLoadingView == null) { + mLoadingView = parent.findViewById(R.id.vector_settings_spinner_views) + parent = parent.parent as View + } + } else { + mLoadingView?.visibility = View.VISIBLE + } + } + + /** + * Hide the loading view. + */ + protected fun hideLoadingView() { + mLoadingView?.visibility = View.GONE + } + + /** + * Hide the loading view and refresh the preferences. + * + * @param refresh true to refresh the display + */ + protected fun hideLoadingView(refresh: Boolean) { + mLoadingView?.visibility = View.GONE + + if (refresh) { + // TODO refreshDisplay() + } + } + + /** + * A request has been processed. + * Display a toast if there is a an error message + * + * @param errorMessage the error message + */ + protected fun onCommonDone(errorMessage: String?) { + if (!isAdded) { + return + } + activity?.runOnUiThread { + if (!TextUtils.isEmpty(errorMessage) && errorMessage != null) { + activity?.toast(errorMessage!!) + } + hideLoadingView() + } + } + + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsFlairFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsFlairFragment.kt new file mode 100644 index 00000000..abab4160 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsFlairFragment.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2019 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.riotredesign.features.settings + +import androidx.preference.PreferenceCategory +import im.vector.riotredesign.R +import im.vector.riotredesign.core.preference.ProgressBarPreference + +class VectorSettingsFlairFragment : VectorSettingsBaseFragment() { + + override var titleRes = R.string.settings_flair + override val preferenceXmlRes = R.xml.vector_settings_flair + + // current publicised group list + private var mPublicisedGroups: MutableSet? = null + + // Group Flairs + private val mGroupsFlairCategory by lazy { + findPreference(PreferencesManager.SETTINGS_GROUPS_FLAIR_KEY) as PreferenceCategory + } + + override fun bindPref() { + // Flair + refreshGroupFlairsList() + } + + //============================================================================================================== + // Group flairs management + //============================================================================================================== + + /** + * Force the refresh of the devices list.

+ * The devices list is the list of the devices where the user as looged in. + * It can be any mobile device, as any browser. + */ + private fun refreshGroupFlairsList() { + // display a spinner while refreshing + if (0 == mGroupsFlairCategory.preferenceCount) { + activity?.let { + val preference = ProgressBarPreference(it) + mGroupsFlairCategory.addPreference(preference) + } + } + + /* + TODO + session.groupsManager.getUserPublicisedGroups(session.myUserId, true, object : MatrixCallback> { + override fun onSuccess(publicisedGroups: Set) { + // clear everything + mGroupsFlairCategory.removeAll() + + if (publicisedGroups.isEmpty()) { + val vectorGroupPreference = Preference(activity) + vectorGroupPreference.title = resources.getString(R.string.settings_without_flair) + mGroupsFlairCategory.addPreference(vectorGroupPreference) + } else { + buildGroupsList(publicisedGroups) + } + } + + override fun onNetworkError(e: Exception) { + // NOP + } + + override fun onMatrixError(e: MatrixError) { + // NOP + } + + override fun onUnexpectedError(e: Exception) { + // NOP + } + }) + */ + } + + /** + * Build the groups list. + * + * @param publicisedGroups the publicised groups list. + */ + private fun buildGroupsList(publicisedGroups: Set) { + var isNewList = true + + mPublicisedGroups?.let { + if (it.size == publicisedGroups.size) { + isNewList = !it.containsAll(publicisedGroups) + } + } + + if (isNewList) { + /* + TODO + val joinedGroups = ArrayList(session.groupsManager.joinedGroups) + Collections.sort(joinedGroups, Group.mGroupsComparator) + + mPublicisedGroups = publicisedGroups.toMutableSet() + + for ((prefIndex, group) in joinedGroups.withIndex()) { + val vectorGroupPreference = VectorGroupPreference(activity!!) + vectorGroupPreference.key = DEVICES_PREFERENCE_KEY_BASE + prefIndex + + vectorGroupPreference.setGroup(group, session) + vectorGroupPreference.title = group.displayName + vectorGroupPreference.summary = group.groupId + + vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId) + mGroupsFlairCategory.addPreference(vectorGroupPreference) + + vectorGroupPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + if (newValue is Boolean) { + /* + * if mPublicisedGroup is null somehow, then + * we cant check it contains groupId or not + * so set isFlaired to false + */ + val isFlaired = mPublicisedGroups?.contains(group.groupId) ?: false + + if (newValue != isFlaired) { + displayLoadingView() + session.groupsManager.updateGroupPublicity(group.groupId, newValue, object : MatrixCallback { + override fun onSuccess(info: Void?) { + hideLoadingView() + if (newValue) { + mPublicisedGroups?.add(group.groupId) + } else { + mPublicisedGroups?.remove(group.groupId) + } + } + + private fun onError() { + hideLoadingView() + // restore default value + vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId) + } + + override fun onNetworkError(e: Exception) { + onError() + } + + override fun onMatrixError(e: MatrixError) { + onError() + } + + override fun onUnexpectedError(e: Exception) { + onError() + } + }) + } + } + true + } + } + */ + } + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsGeneralFragment.kt new file mode 100644 index 00000000..8b52cebc --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsGeneralFragment.kt @@ -0,0 +1,893 @@ +/* + * Copyright 2019 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.riotredesign.features.settings + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.AsyncTask +import android.text.Editable +import android.text.TextUtils +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.ImageView +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.preference.EditTextPreference +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.showPassword +import im.vector.riotredesign.core.platform.SimpleTextWatcher +import im.vector.riotredesign.core.preference.UserAvatarPreference +import im.vector.riotredesign.core.preference.VectorPreference +import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA +import im.vector.riotredesign.core.utils.allGranted +import im.vector.riotredesign.core.utils.copyToClipboard +import im.vector.riotredesign.core.utils.toast +import im.vector.riotredesign.features.MainActivity +import im.vector.riotredesign.features.themes.ThemeUtils +import java.lang.ref.WeakReference +import java.util.* + +class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { + + override var titleRes = R.string.settings_general_title + override val preferenceXmlRes = R.xml.vector_settings_general + + private var mDisplayedEmails = ArrayList() + private var mDisplayedPhoneNumber = ArrayList() + + private val mUserSettingsCategory by lazy { + findPreference(PreferencesManager.SETTINGS_USER_SETTINGS_PREFERENCE_KEY) as PreferenceCategory + } + private val mUserAvatarPreference by lazy { + findPreference(PreferencesManager.SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY) as UserAvatarPreference + } + private val mDisplayNamePreference by lazy { + findPreference(PreferencesManager.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY) as EditTextPreference + } + private val mPasswordPreference by lazy { + findPreference(PreferencesManager.SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY) + } + + // Local contacts + private val mContactSettingsCategory by lazy { + findPreference(PreferencesManager.SETTINGS_CONTACT_PREFERENCE_KEYS) as PreferenceCategory + } + + private val mContactPhonebookCountryPreference by lazy { + findPreference(PreferencesManager.SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY) + } + + + override fun bindPref() { + // Avatar + mUserAvatarPreference.let { + it.setSession(session) + it.onPreferenceClickListener = Preference.OnPreferenceClickListener { + onUpdateAvatarClick() + false + } + } + + // Display name + mDisplayNamePreference.let { + it.summary = session.getUser(session.sessionParams.credentials.userId)?.displayName ?: "" + it.text = it.summary.toString() + it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + onDisplayNameClick(newValue?.let { (it as String).trim() }) + false + } + } + + // Password + mPasswordPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + notImplemented() + // onPasswordUpdateClick() + false + } + + // Add Email + (findPreference(ADD_EMAIL_PREFERENCE_KEY) as EditTextPreference).let { + // It does not work on XML, do it here + it.icon = activity?.let { + ThemeUtils.tintDrawable(it, + ContextCompat.getDrawable(it, R.drawable.ic_add_black)!!, R.attr.vctr_settings_icon_tint_color) + } + + // Unfortunately, this is not supported in lib v7 + // it.editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + + it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + notImplemented() + //addEmail((newValue as String).trim()) + false + } + } + + // Add phone number + findPreference(ADD_PHONE_NUMBER_PREFERENCE_KEY).let { + // It does not work on XML, do it here + it.icon = activity?.let { + ThemeUtils.tintDrawable(it, + ContextCompat.getDrawable(it, R.drawable.ic_add_black)!!, R.attr.vctr_settings_icon_tint_color) + } + + it.setOnPreferenceClickListener { + notImplemented() + // TODO val intent = PhoneNumberAdditionActivity.getIntent(activity, session.credentials.userId) + // startActivityForResult(intent, REQUEST_NEW_PHONE_NUMBER) + true + } + } + + // Advanced settings + + // user account + findPreference(PreferencesManager.SETTINGS_LOGGED_IN_PREFERENCE_KEY) + .summary = session.sessionParams.credentials.userId + + // home server + findPreference(PreferencesManager.SETTINGS_HOME_SERVER_PREFERENCE_KEY) + .summary = session.sessionParams.homeServerConnectionConfig.homeServerUri.toString() + + // identity server + findPreference(PreferencesManager.SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY) + .summary = session.sessionParams.homeServerConnectionConfig.identityServerUri.toString() + + + refreshEmailsList() + refreshPhoneNumbersList() + // Contacts + setContactsPreferences() + + // clear cache + findPreference(PreferencesManager.SETTINGS_CLEAR_CACHE_PREFERENCE_KEY).let { + /* + TODO + MXSession.getApplicationSizeCaches(activity, object : SimpleApiCallback() { + override fun onSuccess(size: Long) { + if (null != activity) { + it.summary = android.text.format.Formatter.formatFileSize(activity, size) + } + } + }) + */ + + it.onPreferenceClickListener = Preference.OnPreferenceClickListener { + displayLoadingView() + MainActivity.restartApp(activity!!, clearCache = true, clearCredentials = false) + false + } + } + + // clear medias cache + findPreference(PreferencesManager.SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY).let { + /* + TODO + MXMediaCache.getCachesSize(activity, object : SimpleApiCallback() { + override fun onSuccess(size: Long) { + if (null != activity) { + it.summary = android.text.format.Formatter.formatFileSize(activity, size) + } + } + }) + */ + + it.onPreferenceClickListener = Preference.OnPreferenceClickListener { + notImplemented() + /* TODO + displayLoadingView() + + val task = ClearMediaCacheAsyncTask( + backgroundTask = { + session.mediaCache.clear() + activity?.let { it -> Glide.get(it).clearDiskCache() } + }, + onCompleteTask = { + hideLoadingView() + + MXMediaCache.getCachesSize(activity, object : SimpleApiCallback() { + override fun onSuccess(size: Long) { + it.summary = Formatter.formatFileSize(activity, size) + } + }) + } + ) + + try { + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } catch (e: Exception) { + Timber.e(e, "## session.getMediaCache().clear() failed " + e.message) + task.cancel(true) + hideLoadingView() + } + */ + + false + } + } + + // Deactivate account section + + // deactivate account + findPreference(PreferencesManager.SETTINGS_DEACTIVATE_ACCOUNT_KEY) + .onPreferenceClickListener = Preference.OnPreferenceClickListener { + activity?.let { + notImplemented() + // TODO startActivity(DeactivateAccountActivity.getIntent(it)) + } + + false + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (allGranted(grantResults)) { + if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) { + changeAvatar() + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (resultCode == Activity.RESULT_OK) { + when (requestCode) { + REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList() + REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data) + /* TODO + VectorUtils.TAKE_IMAGE -> { + val thumbnailUri = VectorUtils.getThumbnailUriFromIntent(activity, data, session.mediaCache) + + if (null != thumbnailUri) { + displayLoadingView() + + val resource = ResourceUtils.openResource(activity, thumbnailUri, null) + + if (null != resource) { + session.mediaCache.uploadContent(resource.mContentStream, null, resource.mMimeType, null, object : MXMediaUploadListener() { + + override fun onUploadError(uploadId: String?, serverResponseCode: Int, serverErrorMessage: String?) { + activity?.runOnUiThread { onCommonDone(serverResponseCode.toString() + " : " + serverErrorMessage) } + } + + override fun onUploadComplete(uploadId: String?, contentUri: String?) { + activity?.runOnUiThread { + session.myUser.updateAvatarUrl(contentUri, object : MatrixCallback { + override fun onSuccess(info: Void?) { + onCommonDone(null) + refreshDisplay() + } + + override fun onNetworkError(e: Exception) { + onCommonDone(e.localizedMessage) + } + + override fun onMatrixError(e: MatrixError) { + if (MatrixError.M_CONSENT_NOT_GIVEN == e.errcode) { + activity?.runOnUiThread { + hideLoadingView() + (activity as VectorAppCompatActivity).consentNotGivenHelper.displayDialog(e) + } + } else { + onCommonDone(e.localizedMessage) + } + } + + override fun onUnexpectedError(e: Exception) { + onCommonDone(e.localizedMessage) + } + }) + } + } + }) + } + } + } + */ + } + } + } + + /** + * Update the avatar. + */ + private fun onUpdateAvatarClick() { + notImplemented() + + /* TODO + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { + changeAvatar() + } + */ + } + + private fun changeAvatar() { + /* TODO + val intent = Intent(activity, VectorMediaPickerActivity::class.java) + intent.putExtra(VectorMediaPickerActivity.EXTRA_AVATAR_MODE, true) + startActivityForResult(intent, VectorUtils.TAKE_IMAGE) + */ + } + + + //============================================================================================================== + // contacts management + //============================================================================================================== + + private fun setContactsPreferences() { + /* TODO + // Permission + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // on Android >= 23, use the system one + mContactSettingsCategory.removePreference(findPreference(ContactsManager.CONTACTS_BOOK_ACCESS_KEY)) + } + // Phonebook country + mContactPhonebookCountryPreference.summary = PhoneNumberUtils.getHumanCountryCode(PhoneNumberUtils.getCountryCode(activity)) + + mContactPhonebookCountryPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + val intent = CountryPickerActivity.getIntent(activity, true) + startActivityForResult(intent, REQUEST_PHONEBOOK_COUNTRY) + true + } + */ + } + + private fun onPhonebookCountryUpdate(data: Intent?) { + /* TODO + if (data != null && data.hasExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_NAME) + && data.hasExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_CODE)) { + val countryCode = data.getStringExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_CODE) + if (!TextUtils.equals(countryCode, PhoneNumberUtils.getCountryCode(activity))) { + PhoneNumberUtils.setCountryCode(activity, countryCode) + mContactPhonebookCountryPreference.summary = data.getStringExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_NAME) + } + } + */ + } + + //============================================================================================================== + // Phone number management + //============================================================================================================== + + /** + * Refresh phone number list + */ + private fun refreshPhoneNumbersList() { + /* TODO + val currentPhoneNumber3PID = ArrayList(session.myUser.getlinkedPhoneNumbers()) + + val phoneNumberList = ArrayList() + for (identifier in currentPhoneNumber3PID) { + phoneNumberList.add(identifier.address) + } + + // check first if there is an update + var isNewList = true + if (phoneNumberList.size == mDisplayedPhoneNumber.size) { + isNewList = !mDisplayedPhoneNumber.containsAll(phoneNumberList) + } + + if (isNewList) { + // remove the displayed one + run { + var index = 0 + while (true) { + val preference = mUserSettingsCategory.findPreference(PHONE_NUMBER_PREFERENCE_KEY_BASE + index) + + if (null != preference) { + mUserSettingsCategory.removePreference(preference) + } else { + break + } + index++ + } + } + + // add new phone number list + mDisplayedPhoneNumber = phoneNumberList + + val addPhoneBtn = mUserSettingsCategory.findPreference(ADD_PHONE_NUMBER_PREFERENCE_KEY) + ?: return + + var order = addPhoneBtn.order + + for ((index, phoneNumber3PID) in currentPhoneNumber3PID.withIndex()) { + val preference = VectorPreference(activity!!) + + preference.title = getString(R.string.settings_phone_number) + var phoneNumberFormatted = phoneNumber3PID.address + try { + // Attempt to format phone number + val phoneNumber = PhoneNumberUtil.getInstance().parse("+$phoneNumberFormatted", null) + phoneNumberFormatted = PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL) + } catch (e: NumberParseException) { + // Do nothing, we will display raw version + } + + preference.summary = phoneNumberFormatted + preference.key = PHONE_NUMBER_PREFERENCE_KEY_BASE + index + preference.order = order + + preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + displayDelete3PIDConfirmationDialog(phoneNumber3PID, preference.summary) + true + } + + preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener { + override fun onPreferenceLongClick(preference: Preference): Boolean { + activity?.let { copyToClipboard(it, phoneNumber3PID.address) } + return true + } + } + + order++ + mUserSettingsCategory.addPreference(preference) + } + + addPhoneBtn.order = order + } */ + } + + //============================================================================================================== + // Email management + //============================================================================================================== + + /** + * Refresh the emails list + */ + private fun refreshEmailsList() { + val currentEmail3PID = emptyList() // TODO ArrayList(session.myUser.getlinkedEmails()) + + val newEmailsList = ArrayList() + for (identifier in currentEmail3PID) { + // TODO newEmailsList.add(identifier.address) + } + + // check first if there is an update + var isNewList = true + if (newEmailsList.size == mDisplayedEmails.size) { + isNewList = !mDisplayedEmails.containsAll(newEmailsList) + } + + if (isNewList) { + // remove the displayed one + run { + var index = 0 + while (true) { + val preference = mUserSettingsCategory.findPreference(EMAIL_PREFERENCE_KEY_BASE + index) + + if (null != preference) { + mUserSettingsCategory.removePreference(preference) + } else { + break + } + index++ + } + } + + // add new emails list + mDisplayedEmails = newEmailsList + + val addEmailBtn = mUserSettingsCategory.findPreference(ADD_EMAIL_PREFERENCE_KEY) + ?: return + + var order = addEmailBtn.order + + for ((index, email3PID) in currentEmail3PID.withIndex()) { + val preference = VectorPreference(activity!!) + + preference.title = getString(R.string.settings_email_address) + preference.summary = "TODO" // email3PID.address + preference.key = EMAIL_PREFERENCE_KEY_BASE + index + preference.order = order + + preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { pref -> + displayDelete3PIDConfirmationDialog(/* TODO email3PID, */ pref.summary) + true + } + + preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener { + override fun onPreferenceLongClick(preference: Preference): Boolean { + activity?.let { copyToClipboard(it, "TODO") } //email3PID.address) } + return true + } + } + + mUserSettingsCategory.addPreference(preference) + + order++ + } + + addEmailBtn.order = order + } + } + + /** + * Attempt to add a new email to the account + * + * @param email the email to add. + */ + private fun addEmail(email: String) { + // check first if the email syntax is valid + // if email is null , then also its invalid email + if (TextUtils.isEmpty(email) || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) { + activity?.toast(R.string.auth_invalid_email) + return + } + + // check first if the email syntax is valid + if (mDisplayedEmails.indexOf(email) >= 0) { + activity?.toast(R.string.auth_email_already_defined) + return + } + + notImplemented() + /* TODO + val pid = ThreePid(email, ThreePid.MEDIUM_EMAIL) + + displayLoadingView() + + session.myUser.requestEmailValidationToken(pid, object : MatrixCallback { + override fun onSuccess(info: Void?) { + activity?.runOnUiThread { showEmailValidationDialog(pid) } + } + + override fun onNetworkError(e: Exception) { + onCommonDone(e.localizedMessage) + } + + override fun onMatrixError(e: MatrixError) { + if (TextUtils.equals(MatrixError.THREEPID_IN_USE, e.errcode)) { + onCommonDone(getString(R.string.account_email_already_used_error)) + } else { + onCommonDone(e.localizedMessage) + } + } + + override fun onUnexpectedError(e: Exception) { + onCommonDone(e.localizedMessage) + } + }) + */ + } + + /** + * Show an email validation dialog to warn the user tho valid his email link. + * + * @param pid the used pid. + */ + /* TODO + private fun showEmailValidationDialog(pid: ThreePid) { + activity?.let { + AlertDialog.Builder(it) + .setTitle(R.string.account_email_validation_title) + .setMessage(R.string.account_email_validation_message) + .setPositiveButton(R.string._continue) { _, _ -> + session.myUser.add3Pid(pid, true, object : MatrixCallback { + override fun onSuccess(info: Void?) { + it.runOnUiThread { + hideLoadingView() + refreshEmailsList() + } + } + + override fun onNetworkError(e: Exception) { + onCommonDone(e.localizedMessage) + } + + override fun onMatrixError(e: MatrixError) { + if (TextUtils.equals(e.errcode, MatrixError.THREEPID_AUTH_FAILED)) { + it.runOnUiThread { + hideLoadingView() + it.toast(R.string.account_email_validation_error) + } + } else { + onCommonDone(e.localizedMessage) + } + } + + override fun onUnexpectedError(e: Exception) { + onCommonDone(e.localizedMessage) + } + }) + } + .setNegativeButton(R.string.cancel) { _, _ -> + hideLoadingView() + } + .show() + } + } */ + + + /** + * Display a dialog which asks confirmation for the deletion of a 3pid + * + * @param pid the 3pid to delete + * @param preferenceSummary the displayed 3pid + */ + private fun displayDelete3PIDConfirmationDialog(/* TODO pid: ThirdPartyIdentifier,*/ preferenceSummary: CharSequence) { + val mediumFriendlyName = "TODO" // ThreePid.getMediumFriendlyName(pid.medium, activity).toLowerCase(VectorLocale.applicationLocale) + val dialogMessage = getString(R.string.settings_delete_threepid_confirmation, mediumFriendlyName, preferenceSummary) + + activity?.let { + AlertDialog.Builder(it) + .setTitle(R.string.dialog_title_confirmation) + .setMessage(dialogMessage) + .setPositiveButton(R.string.remove) { _, _ -> + notImplemented() + /* TODO + displayLoadingView() + + session.myUser.delete3Pid(pid, object : MatrixCallback { + override fun onSuccess(info: Void?) { + when (pid.medium) { + ThreePid.MEDIUM_EMAIL -> refreshEmailsList() + ThreePid.MEDIUM_MSISDN -> refreshPhoneNumbersList() + } + onCommonDone(null) + } + + override fun onNetworkError(e: Exception) { + onCommonDone(e.localizedMessage) + } + + override fun onMatrixError(e: MatrixError) { + onCommonDone(e.localizedMessage) + } + + override fun onUnexpectedError(e: Exception) { + onCommonDone(e.localizedMessage) + } + }) + */ + } + .setNegativeButton(R.string.cancel, null) + .show() + } + } + + + /** + * Update the password. + */ + private fun onPasswordUpdateClick() { + activity?.let { activity -> + val view: ViewGroup = activity.layoutInflater.inflate(R.layout.dialog_change_password, null) as ViewGroup + + val showPassword: ImageView = view.findViewById(R.id.change_password_show_passwords) + val oldPasswordTil: TextInputLayout = view.findViewById(R.id.change_password_old_pwd_til) + val oldPasswordText: TextInputEditText = view.findViewById(R.id.change_password_old_pwd_text) + val newPasswordText: TextInputEditText = view.findViewById(R.id.change_password_new_pwd_text) + val confirmNewPasswordTil: TextInputLayout = view.findViewById(R.id.change_password_confirm_new_pwd_til) + val confirmNewPasswordText: TextInputEditText = view.findViewById(R.id.change_password_confirm_new_pwd_text) + val changePasswordLoader: View = view.findViewById(R.id.change_password_loader) + + var passwordShown = false + + showPassword.setOnClickListener(object : View.OnClickListener { + override fun onClick(v: View?) { + passwordShown = !passwordShown + + oldPasswordText.showPassword(passwordShown) + newPasswordText.showPassword(passwordShown) + confirmNewPasswordText.showPassword(passwordShown) + + showPassword.setImageResource(if (passwordShown) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) + } + }) + + val dialog = AlertDialog.Builder(activity) + .setView(view) + .setPositiveButton(R.string.settings_change_password_submit, null) + .setNegativeButton(R.string.cancel, null) + .setOnDismissListener { + val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(view.applicationWindowToken, 0) + } + .create() + + dialog.setOnShowListener { + val updateButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) + updateButton.isEnabled = false + + fun updateUi() { + val oldPwd = oldPasswordText.text.toString().trim() + val newPwd = newPasswordText.text.toString().trim() + val newConfirmPwd = confirmNewPasswordText.text.toString().trim() + + updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && TextUtils.equals(newPwd, newConfirmPwd) + + if (newPwd.isNotEmpty() && newConfirmPwd.isNotEmpty() && !TextUtils.equals(newPwd, newConfirmPwd)) { + confirmNewPasswordTil.error = getString(R.string.passwords_do_not_match) + } + } + + oldPasswordText.addTextChangedListener(object : SimpleTextWatcher() { + override fun afterTextChanged(s: Editable) { + oldPasswordTil.error = null + updateUi() + } + }) + + newPasswordText.addTextChangedListener(object : SimpleTextWatcher() { + override fun afterTextChanged(s: Editable) { + confirmNewPasswordTil.error = null + updateUi() + } + }) + + confirmNewPasswordText.addTextChangedListener(object : SimpleTextWatcher() { + override fun afterTextChanged(s: Editable) { + confirmNewPasswordTil.error = null + updateUi() + } + }) + + fun showPasswordLoadingView(toShow: Boolean) { + if (toShow) { + showPassword.isEnabled = false + oldPasswordText.isEnabled = false + newPasswordText.isEnabled = false + confirmNewPasswordText.isEnabled = false + changePasswordLoader.isVisible = true + updateButton.isEnabled = false + } else { + showPassword.isEnabled = true + oldPasswordText.isEnabled = true + newPasswordText.isEnabled = true + confirmNewPasswordText.isEnabled = true + changePasswordLoader.isVisible = false + updateButton.isEnabled = true + } + } + + updateButton.setOnClickListener { + if (passwordShown) { + // Hide passwords during processing + showPassword.performClick() + } + + val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(view.applicationWindowToken, 0) + + val oldPwd = oldPasswordText.text.toString().trim() + val newPwd = newPasswordText.text.toString().trim() + + notImplemented() + /* TODO + showPasswordLoadingView(true) + + session.updatePassword(oldPwd, newPwd, object : MatrixCallback { + private fun onDone(@StringRes textResId: Int) { + showPasswordLoadingView(false) + + if (textResId == R.string.settings_fail_to_update_password_invalid_current_password) { + oldPasswordTil.error = getString(textResId) + } else { + dialog.dismiss() + activity.toast(textResId, Toast.LENGTH_LONG) + } + } + + override fun onSuccess(info: Void?) { + onDone(R.string.settings_password_updated) + } + + override fun onNetworkError(e: Exception) { + onDone(R.string.settings_fail_to_update_password) + } + + override fun onMatrixError(e: MatrixError) { + if (e.error == "Invalid password") { + onDone(R.string.settings_fail_to_update_password_invalid_current_password) + } else { + dialog.dismiss() + onDone(R.string.settings_fail_to_update_password) + } + } + + override fun onUnexpectedError(e: Exception) { + onDone(R.string.settings_fail_to_update_password) + } + }) + */ + } + } + dialog.show() + } + } + + /** + * Update the displayname. + */ + private fun onDisplayNameClick(value: String?) { + notImplemented() + /* TODO + if (!TextUtils.equals(session.myUser.displayname, value)) { + displayLoadingView() + + session.myUser.updateDisplayName(value, object : MatrixCallback { + override fun onSuccess(info: Void?) { + // refresh the settings value + PreferenceManager.getDefaultSharedPreferences(activity).edit { + putString(PreferencesManager.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY, value) + } + + onCommonDone(null) + + refreshDisplay() + } + + override fun onNetworkError(e: Exception) { + onCommonDone(e.localizedMessage) + } + + override fun onMatrixError(e: MatrixError) { + if (MatrixError.M_CONSENT_NOT_GIVEN == e.errcode) { + activity?.runOnUiThread { + hideLoadingView() + (activity as VectorAppCompatActivity).consentNotGivenHelper.displayDialog(e) + } + } else { + onCommonDone(e.localizedMessage) + } + } + + override fun onUnexpectedError(e: Exception) { + onCommonDone(e.localizedMessage) + } + }) + } + */ + } + + private class ClearMediaCacheAsyncTask internal constructor( + backgroundTask: () -> Unit, + onCompleteTask: () -> Unit + ) : AsyncTask() { + + private val backgroundTaskReference = WeakReference(backgroundTask) + private val onCompleteTaskReference = WeakReference(onCompleteTask) + override fun doInBackground(vararg params: Unit?) { + backgroundTaskReference.get()?.invoke() + } + + override fun onPostExecute(result: Unit?) { + super.onPostExecute(result) + onCompleteTaskReference.get()?.invoke() + } + } + + companion object { + private const val ADD_EMAIL_PREFERENCE_KEY = "ADD_EMAIL_PREFERENCE_KEY" + private const val ADD_PHONE_NUMBER_PREFERENCE_KEY = "ADD_PHONE_NUMBER_PREFERENCE_KEY" + + private const val EMAIL_PREFERENCE_KEY_BASE = "EMAIL_PREFERENCE_KEY_BASE" + private const val PHONE_NUMBER_PREFERENCE_KEY_BASE = "PHONE_NUMBER_PREFERENCE_KEY_BASE" + + private const val REQUEST_NEW_PHONE_NUMBER = 456 + private const val REQUEST_PHONEBOOK_COUNTRY = 789 + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsHelpAboutFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsHelpAboutFragment.kt new file mode 100644 index 00000000..f67f89bb --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsHelpAboutFragment.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2019 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.riotredesign.features.settings + +import android.content.Intent +import android.net.Uri +import android.provider.Settings +import androidx.preference.Preference +import com.google.android.gms.oss.licenses.OssLicensesMenuActivity +import im.vector.matrix.android.api.Matrix +import im.vector.riotredesign.R +import im.vector.riotredesign.core.utils.copyToClipboard +import im.vector.riotredesign.core.utils.displayInWebView +import im.vector.riotredesign.features.version.getVersion + +class VectorSettingsHelpAboutFragment : VectorSettingsBaseFragment() { + + override var titleRes = R.string.preference_root_help_about + override val preferenceXmlRes = R.xml.vector_settings_help_about + + override fun bindPref() { + // preference to start the App info screen, to facilitate App permissions access + findPreference(APP_INFO_LINK_PREFERENCE_KEY) + .onPreferenceClickListener = Preference.OnPreferenceClickListener { + + activity?.let { + val intent = Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + val uri = Uri.fromParts("package", requireContext().packageName, null) + + data = uri + } + it.applicationContext.startActivity(intent) + } + + true + } + + // application version + (findPreference(PreferencesManager.SETTINGS_VERSION_PREFERENCE_KEY)).let { + it.summary = getVersion(longFormat = false, useBuildNumber = true) + + it.setOnPreferenceClickListener { pref -> + copyToClipboard(requireContext(), pref.summary) + true + } + } + + // SDK version + (findPreference(PreferencesManager.SETTINGS_SDK_VERSION_PREFERENCE_KEY)).let { + it.summary = Matrix.getSdkVersion() + + it.setOnPreferenceClickListener { pref -> + copyToClipboard(requireContext(), pref.summary) + true + } + } + + // olm version + findPreference(PreferencesManager.SETTINGS_OLM_VERSION_PREFERENCE_KEY) + .summary = session.getCryptoVersion(requireContext(), false) + + // copyright + findPreference(PreferencesManager.SETTINGS_COPYRIGHT_PREFERENCE_KEY) + .onPreferenceClickListener = Preference.OnPreferenceClickListener { + activity?.displayInWebView(VectorSettingsUrls.COPYRIGHT) + false + } + + // terms & conditions + findPreference(PreferencesManager.SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY) + .onPreferenceClickListener = Preference.OnPreferenceClickListener { + activity?.displayInWebView(VectorSettingsUrls.TAC) + false + } + + // privacy policy + findPreference(PreferencesManager.SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY) + .onPreferenceClickListener = Preference.OnPreferenceClickListener { + activity?.displayInWebView(VectorSettingsUrls.PRIVACY_POLICY) + false + } + + // third party notice + findPreference(PreferencesManager.SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY) + .onPreferenceClickListener = Preference.OnPreferenceClickListener { + activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES) + false + } + + findPreference(PreferencesManager.SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY) + .onPreferenceClickListener = Preference.OnPreferenceClickListener { + // See https://developers.google.com/android/guides/opensource + startActivity(Intent(requireActivity(), OssLicensesMenuActivity::class.java)) + false + } + } + + companion object { + private const val APP_INFO_LINK_PREFERENCE_KEY = "APP_INFO_LINK_PREFERENCE_KEY" + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsIgnoredUsersFragment.kt new file mode 100644 index 00000000..5fce7b63 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsIgnoredUsersFragment.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2019 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.riotredesign.features.settings + +import androidx.appcompat.app.AlertDialog +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import im.vector.riotredesign.R +import java.util.ArrayList +import kotlin.Comparator +import kotlin.String +import kotlin.getValue +import kotlin.lazy +import kotlin.let + +class VectorSettingsIgnoredUsersFragment : VectorSettingsBaseFragment() { + + override var titleRes = R.string.settings_ignored_users + override val preferenceXmlRes = R.xml.vector_settings_ignored_users + + // displayed the ignored users list + private val mIgnoredUserSettingsCategoryDivider by lazy { + findPreference(PreferencesManager.SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY) + } + private val mIgnoredUserSettingsCategory by lazy { + findPreference(PreferencesManager.SETTINGS_IGNORED_USERS_PREFERENCE_KEY) as PreferenceCategory + } + + override fun bindPref() { + // Ignore users + refreshIgnoredUsersList() + } + + //============================================================================================================== + // ignored users list management + //============================================================================================================== + + /** + * Refresh the ignored users list + */ + private fun refreshIgnoredUsersList() { + val ignoredUsersList = mutableListOf() // TODO session.dataHandler.ignoredUserIds + + ignoredUsersList.sortWith(Comparator { u1, u2 -> + u1.toLowerCase(VectorLocale.applicationLocale).compareTo(u2.toLowerCase(VectorLocale.applicationLocale)) + }) + + val preferenceScreen = preferenceScreen + + preferenceScreen.removePreference(mIgnoredUserSettingsCategory) + preferenceScreen.removePreference(mIgnoredUserSettingsCategoryDivider) + mIgnoredUserSettingsCategory.removeAll() + + if (ignoredUsersList.size > 0) { + preferenceScreen.addPreference(mIgnoredUserSettingsCategoryDivider) + preferenceScreen.addPreference(mIgnoredUserSettingsCategory) + + for (userId in ignoredUsersList) { + val preference = Preference(activity) + + preference.title = userId + preference.key = IGNORED_USER_KEY_BASE + userId + + preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + activity?.let { + AlertDialog.Builder(it) + .setMessage(getString(R.string.settings_unignore_user, userId)) + .setPositiveButton(R.string.yes) { _, _ -> + displayLoadingView() + + val idsList = ArrayList() + idsList.add(userId) + + notImplemented() + /* TODO + session.unIgnoreUsers(idsList, object : MatrixCallback { + override fun onSuccess(info: Void?) { + onCommonDone(null) + } + + override fun onNetworkError(e: Exception) { + onCommonDone(e.localizedMessage) + } + + override fun onMatrixError(e: MatrixError) { + onCommonDone(e.localizedMessage) + } + + override fun onUnexpectedError(e: Exception) { + onCommonDone(e.localizedMessage) + } + }) + */ + } + .setNegativeButton(R.string.no, null) + .show() + } + + false + } + + mIgnoredUserSettingsCategory.addPreference(preference) + } + } + } + + companion object { + private const val IGNORED_USER_KEY_BASE = "IGNORED_USER_KEY_BASE" + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsLabsFragment.kt new file mode 100644 index 00000000..4c9f1bee --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsLabsFragment.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2019 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.riotredesign.features.settings + +import android.text.TextUtils +import androidx.appcompat.app.AlertDialog +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.SwitchPreference +import im.vector.riotredesign.R + +class VectorSettingsLabsFragment : VectorSettingsBaseFragment() { + + override var titleRes = R.string.room_settings_labs_pref_title + override val preferenceXmlRes = R.xml.vector_settings_labs + + private val mLabsCategory by lazy { + findPreference(PreferencesManager.SETTINGS_LABS_PREFERENCE_KEY) as PreferenceCategory + } + + override fun bindPref() { + // Lab + val useCryptoPref = findPreference(PreferencesManager.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY) as SwitchPreference + val cryptoIsEnabledPref = findPreference(PreferencesManager.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY) + + + if (session.isCryptoEnabled()) { + mLabsCategory.removePreference(useCryptoPref) + + cryptoIsEnabledPref.isEnabled = false + } else { + mLabsCategory.removePreference(cryptoIsEnabledPref) + + useCryptoPref.isChecked = false + + useCryptoPref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValueAsVoid -> + if (TextUtils.isEmpty(session.sessionParams.credentials.deviceId)) { + activity?.let { activity -> + AlertDialog.Builder(activity) + .setMessage(R.string.room_settings_labs_end_to_end_warnings) + .setPositiveButton(R.string.logout) { _, _ -> + notImplemented() + // TODO CommonActivityUtils.logout(activity) + } + .setNegativeButton(R.string.cancel) { _, _ -> + useCryptoPref.isChecked = false + } + .setOnCancelListener { + useCryptoPref.isChecked = false + } + .show() + } + } else { + val newValue = newValueAsVoid as Boolean + + if (session.isCryptoEnabled() != newValue) { + notImplemented() + /* TODO + displayLoadingView() + + session.enableCrypto(newValue, object : MatrixCallback { + private fun refresh() { + activity?.runOnUiThread { + hideLoadingView() + useCryptoPref.isChecked = session.isCryptoEnabled + + if (session.isCryptoEnabled) { + mLabsCategory.removePreference(useCryptoPref) + mLabsCategory.addPreference(cryptoIsEnabledPref) + } + } + } + + override fun onSuccess(info: Void?) { + useCryptoPref.isEnabled = false + refresh() + } + + override fun onNetworkError(e: Exception) { + useCryptoPref.isChecked = false + } + + override fun onMatrixError(e: MatrixError) { + useCryptoPref.isChecked = false + } + + override fun onUnexpectedError(e: Exception) { + useCryptoPref.isChecked = false + } + }) + */ + } + } + + true + } + } + + // SaveMode Management + findPreference(PreferencesManager.SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY) + .onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + notImplemented() + /* TODO + val sessions = Matrix.getMXSessions(activity) + for (session in sessions) { + session.setUseDataSaveMode(newValue as Boolean) + } + */ + + true + } + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt index 7b94dedd..ead9f9e3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationFragment.kt @@ -16,68 +16,117 @@ package im.vector.riotredesign.features.settings -import android.content.Context -import android.os.Bundle +import android.widget.Toast import androidx.preference.Preference import androidx.preference.SwitchPreference -import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.pushrules.RuleIds import im.vector.riotredesign.R import im.vector.riotredesign.core.di.ActiveSessionHolder -import im.vector.riotredesign.core.di.DaggerScreenComponent -import im.vector.riotredesign.core.platform.VectorPreferenceFragment +import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.core.pushers.PushersManager import im.vector.riotredesign.push.fcm.FcmHelper import javax.inject.Inject // Referenced in vector_settings_preferences_root.xml -class VectorSettingsNotificationPreferenceFragment : VectorPreferenceFragment() { - +class VectorSettingsNotificationPreferenceFragment : VectorSettingsBaseFragment() { override var titleRes: Int = R.string.settings_notifications + override val preferenceXmlRes = R.xml.vector_settings_notifications @Inject lateinit var pushManager: PushersManager @Inject lateinit var activeSessionHolder: ActiveSessionHolder - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.vector_settings_notifications) + override fun bindPref() { + findPreference(PreferencesManager.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY).let { pref -> + val pushRuleService = session + val mRuleMaster = pushRuleService.getPushRules() + .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } + + if (mRuleMaster == null) { + pref.isVisible = false + return + } + + val areNotifEnabledAtAccountLevelt = !mRuleMaster.enabled + (pref as SwitchPreference).isChecked = areNotifEnabledAtAccountLevelt + } } - override fun onAttach(context: Context) { - val screenComponent = DaggerScreenComponent.factory().create(vectorActivity.getVectorComponent(), vectorActivity) - super.onAttach(context) - screenComponent.inject(this) + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) } - override fun onResume() { super.onResume() activeSessionHolder.getSafeActiveSession()?.refreshPushers() } override fun onPreferenceTreeClick(preference: Preference?): Boolean { - if (preference?.key == PreferencesManager.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) { - val switchPref = preference as SwitchPreference - if (switchPref.isChecked) { - FcmHelper.getFcmToken(requireContext())?.let { - if (PreferencesManager.areNotificationEnabledForDevice(requireContext())) { - pushManager.registerPusherWithFcmKey(it) - } - } - } else { - FcmHelper.getFcmToken(requireContext())?.let { - pushManager.unregisterPusher(it, object : MatrixCallback { - override fun onSuccess(data: Unit) { - super.onSuccess(data) - } - override fun onFailure(failure: Throwable) { - super.onFailure(failure) - } - }) - } + return when (preference?.key) { + PreferencesManager.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY -> { + updateEnabledForDevice(preference) + true + } + PreferencesManager.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY -> { + updateEnabledForAccount(preference) + true + } + else -> { + return super.onPreferenceTreeClick(preference) } } - return super.onPreferenceTreeClick(preference) + + } + + private fun updateEnabledForDevice(preference: Preference?) { + val switchPref = preference as SwitchPreference + if (switchPref.isChecked) { + FcmHelper.getFcmToken(requireContext())?.let { + if (PreferencesManager.areNotificationEnabledForDevice(requireContext())) { + pushManager.registerPusherWithFcmKey(it) + } + } + } else { + FcmHelper.getFcmToken(requireContext())?.let { + pushManager.unregisterPusher(it, object : MatrixCallback { + override fun onSuccess(data: Unit) { + session.refreshPushers() + super.onSuccess(data) + } + + override fun onFailure(failure: Throwable) { + session.refreshPushers() + Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show() + } + }) + } + } + } + + + private fun updateEnabledForAccount(preference: Preference?) { + val pushRuleService = session + val switchPref = preference as SwitchPreference + pushRuleService.getPushRules() + .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } + ?.let { + //Trick, we must enable this room to disable notifications + pushRuleService.updatePushRuleEnableStatus("override", it, !switchPref.isChecked, + object : MatrixCallback { + + override fun onSuccess(data: Unit) { + pushRuleService.fetchPushRules() + } + + override fun onFailure(failure: Throwable) { + //revert the check box + switchPref.isChecked = !switchPref.isChecked + Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show() + } + }) + } + } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt index 700fd244..0db2bfd6 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt @@ -30,6 +30,7 @@ import androidx.transition.TransitionManager import butterknife.BindView import im.vector.matrix.android.api.session.Session import im.vector.riotredesign.R +import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.core.extensions.withArgs import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseFragment @@ -56,15 +57,15 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() { // members @Inject lateinit var session: Session @Inject lateinit var bugReporter: BugReporter + @Inject lateinit var testManagerFactory: NotificationTroubleshootTestManagerFactory + override fun getLayoutResId() = R.layout.fragment_settings_notifications_troubleshoot private var interactionListener: VectorSettingsFragmentInteractionListener? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val appContext = activity!!.applicationContext + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -74,7 +75,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() { mRecyclerView.layoutManager = layoutManager val dividerItemDecoration = DividerItemDecoration(mRecyclerView.context, - layoutManager.orientation) + layoutManager.orientation) mRecyclerView.addItemDecoration(dividerItemDecoration) @@ -91,10 +92,8 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() { private fun startUI() { mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_running_status, - 0, 0) - - testManager = NotificationTroubleshootTestManagerFactory.createTestManager(this, session) - + 0, 0) + testManager = testManagerFactory.create(this) testManager?.statusListener = { troubleshootTestManager -> if (isAdded) { TransitionManager.beginDelayedTransition(mBottomView) @@ -104,7 +103,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() { mSummaryButton.visibility = View.GONE mRunButton.visibility = View.VISIBLE } - TroubleshootTest.TestStatus.RUNNING -> { + TroubleshootTest.TestStatus.RUNNING -> { //Forces int type because it's breaking lint val size: Int = troubleshootTestManager.testList.size val currentTestIndex: Int = troubleshootTestManager.currentTestIndex @@ -116,7 +115,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() { mSummaryButton.visibility = View.GONE mRunButton.visibility = View.GONE } - TroubleshootTest.TestStatus.FAILED -> { + TroubleshootTest.TestStatus.FAILED -> { //check if there are quick fixes var hasQuickFix = false testManager?.testList?.let { @@ -135,7 +134,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() { mSummaryButton.visibility = View.VISIBLE mRunButton.visibility = View.VISIBLE } - TroubleshootTest.TestStatus.SUCCESS -> { + TroubleshootTest.TestStatus.SUCCESS -> { mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_success_status) mSummaryButton.visibility = View.VISIBLE mRunButton.visibility = View.VISIBLE diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt old mode 100755 new mode 100644 index 4d9abbe0..a77f0f73 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 New Vector Ltd + * Copyright 2019 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. @@ -16,361 +16,61 @@ package im.vector.riotredesign.features.settings -import android.annotation.SuppressLint import android.app.Activity import android.content.Context -import android.content.DialogInterface import android.content.Intent -import android.content.SharedPreferences -import android.graphics.Typeface -import android.media.RingtoneManager -import android.net.Uri -import android.os.AsyncTask -import android.os.Bundle -import android.provider.Settings -import android.text.Editable import android.text.TextUtils -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.InputMethodManager -import android.widget.Button import android.widget.CheckedTextView -import android.widget.EditText -import android.widget.ImageView import android.widget.LinearLayout -import android.widget.TextView import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat -import androidx.core.content.edit -import androidx.core.view.isVisible -import androidx.preference.EditTextPreference import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceManager import androidx.preference.SwitchPreference -import com.google.android.gms.oss.licenses.OssLicensesMenuActivity -import com.google.android.material.textfield.TextInputEditText -import com.google.android.material.textfield.TextInputLayout -import im.vector.matrix.android.api.Matrix -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable -import im.vector.matrix.android.api.extensions.sortByLastSeen -import im.vector.matrix.android.api.failure.Failure -import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.internal.auth.data.LoginFlowTypes -import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult -import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo -import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.riotredesign.R -import im.vector.riotredesign.core.di.DaggerScreenComponent -import im.vector.riotredesign.core.dialogs.ExportKeysDialog -import im.vector.riotredesign.core.extensions.showPassword -import im.vector.riotredesign.core.extensions.withArgs -import im.vector.riotredesign.core.intent.ExternalIntentData -import im.vector.riotredesign.core.intent.analyseIntent -import im.vector.riotredesign.core.intent.getFilenameFromUri -import im.vector.riotredesign.core.platform.SimpleTextWatcher -import im.vector.riotredesign.core.platform.VectorPreferenceFragment -import im.vector.riotredesign.core.preference.BingRule -import im.vector.riotredesign.core.preference.ProgressBarPreference -import im.vector.riotredesign.core.preference.UserAvatarPreference -import im.vector.riotredesign.core.preference.VectorPreference -import im.vector.riotredesign.core.utils.PERMISSIONS_FOR_WRITING_FILES -import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_EXPORT_KEYS -import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA -import im.vector.riotredesign.core.utils.allGranted -import im.vector.riotredesign.core.utils.checkPermissions -import im.vector.riotredesign.core.utils.copyToClipboard -import im.vector.riotredesign.core.utils.displayInWebView -import im.vector.riotredesign.core.utils.getCallRingtoneName -import im.vector.riotredesign.core.utils.getCallRingtoneUri -import im.vector.riotredesign.core.utils.openFileSelection -import im.vector.riotredesign.core.utils.setCallRingtoneUri -import im.vector.riotredesign.core.utils.setUseRiotDefaultRingtone -import im.vector.riotredesign.core.utils.toast -import im.vector.riotredesign.features.MainActivity +import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.features.configuration.VectorConfiguration -import im.vector.riotredesign.features.crypto.keys.KeysExporter -import im.vector.riotredesign.features.crypto.keys.KeysImporter -import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.riotredesign.features.themes.ThemeUtils -import im.vector.riotredesign.features.version.getVersion -import timber.log.Timber -import java.lang.ref.WeakReference -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.* import javax.inject.Inject -class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { +class VectorSettingsPreferencesFragment : VectorSettingsBaseFragment() { - override var titleRes: Int = R.string.title_activity_settings - // members - @Inject lateinit var session: Session - @Inject lateinit var vectorConfiguration: VectorConfiguration + override var titleRes = R.string.settings_preferences + override val preferenceXmlRes = R.xml.vector_settings_preferences - // disable some updates if there is - // TODO private val mNetworkListener = IMXNetworkEventListener { refreshDisplay() } - // events listener - // TODO private val mEventsListener = object : MXEventListener() { - // TODO override fun onBingRulesUpdate() { - // TODO refreshPreferences() - // TODO refreshDisplay() - // TODO } - - // TODO override fun onAccountInfoUpdate(myUser: MyUser) { - // TODO // refresh the settings value - // TODO PreferenceManager.getDefaultSharedPreferences(VectorApp.getInstance().applicationContext).edit { - // TODO putString(PreferencesManager.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY, myUser.displayname) - // TODO } - - // TODO refreshDisplay() - // TODO } - // TODO } - - - private var mLoadingView: View? = null - - private var mDisplayedEmails = ArrayList() - private var mDisplayedPhoneNumber = ArrayList() - - private var mMyDeviceInfo: DeviceInfo? = null - - // TODO private var mDisplayedPushers = ArrayList() - - private var interactionListener: VectorSettingsFragmentInteractionListener? = null - - // devices: device IDs and device names - private var mDevicesNameList: List = ArrayList() - // used to avoid requesting to enter the password for each deletion - private var mAccountPassword: String = "" - - - // current publicised group list - private var mPublicisedGroups: MutableSet? = null - - /* ========================================================================================== - * Preferences - * ========================================================================================== */ - - private val mUserSettingsCategory by lazy { - findPreference(PreferencesManager.SETTINGS_USER_SETTINGS_PREFERENCE_KEY) as PreferenceCategory - } - private val mUserAvatarPreference by lazy { - findPreference(PreferencesManager.SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY) as UserAvatarPreference - } - private val mDisplayNamePreference by lazy { - findPreference(PreferencesManager.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY) as EditTextPreference - } - private val mPasswordPreference by lazy { - findPreference(PreferencesManager.SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY) - } - - // Local contacts - private val mContactSettingsCategory by lazy { - findPreference(PreferencesManager.SETTINGS_CONTACT_PREFERENCE_KEYS) as PreferenceCategory - } - - private val mContactPhonebookCountryPreference by lazy { - findPreference(PreferencesManager.SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY) - } - - // Group Flairs - private val mGroupsFlairCategory by lazy { - findPreference(PreferencesManager.SETTINGS_GROUPS_FLAIR_KEY) as PreferenceCategory - } - - // cryptography - private val mCryptographyCategory by lazy { - findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY) as PreferenceCategory - } - private val mCryptographyCategoryDivider by lazy { - findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY) - } - // cryptography manage - private val mCryptographyManageCategory by lazy { - findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY) as PreferenceCategory - } - private val mCryptographyManageCategoryDivider by lazy { - findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY) - } - // displayed pushers - private val mPushersSettingsDivider by lazy { - findPreference(PreferencesManager.SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY) - } - private val mPushersSettingsCategory by lazy { - findPreference(PreferencesManager.SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY) as PreferenceCategory - } - private val mDevicesListSettingsCategory by lazy { - findPreference(PreferencesManager.SETTINGS_DEVICES_LIST_PREFERENCE_KEY) as PreferenceCategory - } - private val mDevicesListSettingsCategoryDivider by lazy { - findPreference(PreferencesManager.SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY) - } - // displayed the ignored users list - private val mIgnoredUserSettingsCategoryDivider by lazy { - findPreference(PreferencesManager.SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY) - } - private val mIgnoredUserSettingsCategory by lazy { - findPreference(PreferencesManager.SETTINGS_IGNORED_USERS_PREFERENCE_KEY) as PreferenceCategory - } - // background sync category - private val mSyncRequestTimeoutPreference by lazy { - // ? Cause it can be removed - findPreference(PreferencesManager.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY) as EditTextPreference? - } - private val mSyncRequestDelayPreference by lazy { - // ? Cause it can be removed - findPreference(PreferencesManager.SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY) as EditTextPreference? - } - private val mLabsCategory by lazy { - findPreference(PreferencesManager.SETTINGS_LABS_PREFERENCE_KEY) as PreferenceCategory - } - private val backgroundSyncCategory by lazy { - findPreference(PreferencesManager.SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY) - } - private val backgroundSyncDivider by lazy { - findPreference(PreferencesManager.SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY) - } - private val backgroundSyncPreference by lazy { - findPreference(PreferencesManager.SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY) as SwitchPreference - } - private val mUseRiotCallRingtonePreference by lazy { - findPreference(PreferencesManager.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY) as SwitchPreference - } - private val mCallRingtonePreference by lazy { - findPreference(PreferencesManager.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY) - } - private val notificationsSettingsCategory by lazy { - findPreference(PreferencesManager.SETTINGS_NOTIFICATIONS_KEY) as PreferenceCategory - } - private val mNotificationPrivacyPreference by lazy { - findPreference(PreferencesManager.SETTINGS_NOTIFICATION_PRIVACY_PREFERENCE_KEY) - } private val selectedLanguagePreference by lazy { findPreference(PreferencesManager.SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY) } private val textSizePreference by lazy { findPreference(PreferencesManager.SETTINGS_INTERFACE_TEXT_SIZE_KEY) } - private val cryptoInfoDeviceNamePreference by lazy { - findPreference(PreferencesManager.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY) as VectorPreference - } - private val cryptoInfoDeviceIdPreference by lazy { - findPreference(PreferencesManager.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY) + + @Inject lateinit var vectorConfiguration: VectorConfiguration + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) } - private val manageBackupPref by lazy { - findPreference(PreferencesManager.SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY) - } - - private val exportPref by lazy { - findPreference(PreferencesManager.SETTINGS_ENCRYPTION_EXPORT_E2E_ROOM_KEYS_PREFERENCE_KEY) - } - - private val importPref by lazy { - findPreference(PreferencesManager.SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY) - } - - private val cryptoInfoTextPreference by lazy { - findPreference(PreferencesManager.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY) - } - // encrypt to unverified devices - private val sendToUnverifiedDevicesPref by lazy { - findPreference(PreferencesManager.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY) as SwitchPreference - } - - /* ========================================================================================== - * Life cycle - * ========================================================================================== */ - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - val appContext = activity?.applicationContext - - // retrieve the arguments - /* - val sessionArg = Matrix.getInstance(appContext).getSession(arguments?.getString(ARG_MATRIX_ID)) - - // sanity checks - if (null == sessionArg || !sessionArg.isAlive) { - activity?.finish() - return - } - - session = sessionArg - */ - - // define the layout - addPreferencesFromResource(R.xml.vector_settings_preferences) - - // Avatar - mUserAvatarPreference.let { - it.setSession(session) - it.onPreferenceClickListener = Preference.OnPreferenceClickListener { - onUpdateAvatarClick() - false - } - } - - // Display name - mDisplayNamePreference.let { - it.summary = "TODO" // session.myUser.displayname - it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - onDisplayNameClick(newValue?.let { (it as String).trim() }) - false - } - } - - // Password - mPasswordPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - onPasswordUpdateClick() - false - } - - // Add Email - (findPreference(ADD_EMAIL_PREFERENCE_KEY) as EditTextPreference).let { - // It does not work on XML, do it here - it.icon = activity?.let { - ThemeUtils.tintDrawable(it, - ContextCompat.getDrawable(it, R.drawable.ic_add_black)!!, R.attr.vctr_settings_icon_tint_color) - } - - // Unfortunately, this is not supported in lib v7 - // it.editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - - it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - addEmail((newValue as String).trim()) - false - } - } - - // Add phone number - findPreference(ADD_PHONE_NUMBER_PREFERENCE_KEY).let { - // It does not work on XML, do it here - it.icon = activity?.let { - ThemeUtils.tintDrawable(it, - ContextCompat.getDrawable(it, R.drawable.ic_add_black)!!, R.attr.vctr_settings_icon_tint_color) - } - - it.setOnPreferenceClickListener { - notImplemented() - // TODO val intent = PhoneNumberAdditionActivity.getIntent(activity, session.credentials.userId) - // startActivityForResult(intent, REQUEST_NEW_PHONE_NUMBER) - true - } - } - - refreshEmailsList() - refreshPhoneNumbersList() - - // Contacts - setContactsPreferences() + override fun bindPref() { // user interface preferences setUserInterfacePreferences() + // Themes + findPreference(ThemeUtils.APPLICATION_THEME_KEY) + .onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + if (newValue is String) { + vectorConfiguration.updateApplicationTheme(newValue) + // Restart the Activity + activity?.let { + // Note: recreate does not apply the color correctly + it.startActivity(it.intent) + it.finish() + } + true + } else { + false + } + } + // Url preview (findPreference(PreferencesManager.SETTINGS_SHOW_URL_PREVIEW_KEY) as SwitchPreference).let { /* @@ -411,320 +111,6 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref */ } - // Themes - findPreference(ThemeUtils.APPLICATION_THEME_KEY) - .onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - if (newValue is String) { - vectorConfiguration.updateApplicationTheme(newValue) - // Restart the Activity - activity?.let { - // Note: recreate does not apply the color correctly - it.startActivity(it.intent) - it.finish() - } - true - } else { - false - } - } - - // Flair - refreshGroupFlairsList() - - // push rules - - // Notification privacy - mNotificationPrivacyPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - notImplemented() - // TODO startActivity(NotificationPrivacyActivity.getIntent(activity)) - true - } - refreshNotificationPrivacy() - - for (preferenceKey in mPrefKeyToBingRuleId.keys) { - val preference = findPreference(preferenceKey) - - if (null != preference) { - if (preference is SwitchPreference) { - preference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValueAsVoid -> - // on some old android APIs, - // the callback is called even if there is no user interaction - // so the value will be checked to ensure there is really no update. - onPushRuleClick(preference.key, newValueAsVoid as Boolean) - true - } - } - } - } - - // background sync tuning settings - // these settings are useless and hidden if the app is registered to the FCM push service - /* - TODO - val pushManager = Matrix.getInstance(appContext).pushManager - if (pushManager.useFcm() && pushManager.hasRegistrationToken()) { - // Hide the section - preferenceScreen.removePreference(backgroundSyncDivider) - preferenceScreen.removePreference(backgroundSyncCategory) - } else { - backgroundSyncPreference.let { - it.isChecked = pushManager.isBackgroundSyncAllowed - - it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, aNewValue -> - val newValue = aNewValue as Boolean - - if (newValue != pushManager.isBackgroundSyncAllowed) { - pushManager.isBackgroundSyncAllowed = newValue - } - - displayLoadingView() - - Matrix.getInstance(activity)?.pushManager?.forceSessionsRegistration(object : MatrixCallback { - override fun onSuccess(info: Void?) { - hideLoadingView() - } - - override fun onMatrixError(e: MatrixError?) { - hideLoadingView() - } - - override fun onNetworkError(e: java.lang.Exception?) { - hideLoadingView() - } - - override fun onUnexpectedError(e: java.lang.Exception?) { - hideLoadingView() - } - }) - - true - } - } - } - */ - - // Push target - refreshPushersList() - - // Ignore users - refreshIgnoredUsersList() - - // Lab - val useCryptoPref = findPreference(PreferencesManager.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY) as SwitchPreference - val cryptoIsEnabledPref = findPreference(PreferencesManager.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY) - - - if (session.isCryptoEnabled()) { - mLabsCategory.removePreference(useCryptoPref) - - cryptoIsEnabledPref.isEnabled = false - } else { - mLabsCategory.removePreference(cryptoIsEnabledPref) - - useCryptoPref.isChecked = false - - useCryptoPref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValueAsVoid -> - if (TextUtils.isEmpty(session.sessionParams.credentials.deviceId)) { - activity?.let { activity -> - AlertDialog.Builder(activity) - .setMessage(R.string.room_settings_labs_end_to_end_warnings) - .setPositiveButton(R.string.logout) { _, _ -> - notImplemented() - // TODO CommonActivityUtils.logout(activity) - } - .setNegativeButton(R.string.cancel) { _, _ -> - useCryptoPref.isChecked = false - } - .setOnCancelListener { - useCryptoPref.isChecked = false - } - .show() - } - } else { - val newValue = newValueAsVoid as Boolean - - if (session.isCryptoEnabled() != newValue) { - notImplemented() - /* TODO - displayLoadingView() - - session.enableCrypto(newValue, object : MatrixCallback { - private fun refresh() { - activity?.runOnUiThread { - hideLoadingView() - useCryptoPref.isChecked = session.isCryptoEnabled - - if (session.isCryptoEnabled) { - mLabsCategory.removePreference(useCryptoPref) - mLabsCategory.addPreference(cryptoIsEnabledPref) - } - } - } - - override fun onSuccess(info: Void?) { - useCryptoPref.isEnabled = false - refresh() - } - - override fun onNetworkError(e: Exception) { - useCryptoPref.isChecked = false - } - - override fun onMatrixError(e: MatrixError) { - useCryptoPref.isChecked = false - } - - override fun onUnexpectedError(e: Exception) { - useCryptoPref.isChecked = false - } - }) - */ - } - } - - true - } - } - - // SaveMode Management - findPreference(PreferencesManager.SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY) - .onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - notImplemented() - /* TODO - val sessions = Matrix.getMXSessions(activity) - for (session in sessions) { - session.setUseDataSaveMode(newValue as Boolean) - } - */ - - true - } - - // Device list - refreshDevicesList() - - //Refresh Key Management section - refreshKeysManagementSection() - - // Advanced settings - - // user account - findPreference(PreferencesManager.SETTINGS_LOGGED_IN_PREFERENCE_KEY) - .summary = session.sessionParams.credentials.userId - - // home server - findPreference(PreferencesManager.SETTINGS_HOME_SERVER_PREFERENCE_KEY) - .summary = session.sessionParams.homeServerConnectionConfig.homeServerUri.toString() - - // identity server - findPreference(PreferencesManager.SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY) - .summary = session.sessionParams.homeServerConnectionConfig.identityServerUri.toString() - - // Analytics - - // Analytics tracking management - (findPreference(PreferencesManager.SETTINGS_USE_ANALYTICS_KEY) as SwitchPreference).let { - // On if the analytics tracking is activated - it.isChecked = PreferencesManager.useAnalytics(appContext) - - it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - PreferencesManager.setUseAnalytics(appContext, newValue as Boolean) - true - } - } - - // Rageshake Management - (findPreference(PreferencesManager.SETTINGS_USE_RAGE_SHAKE_KEY) as SwitchPreference).let { - it.isChecked = PreferencesManager.useRageshake(appContext) - - it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - PreferencesManager.setUseRageshake(appContext, newValue as Boolean) - true - } - } - - // preference to start the App info screen, to facilitate App permissions access - findPreference(APP_INFO_LINK_PREFERENCE_KEY) - .onPreferenceClickListener = Preference.OnPreferenceClickListener { - - activity?.let { - val intent = Intent().apply { - action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - val uri = appContext?.let { Uri.fromParts("package", it.packageName, null) } - - data = uri - } - it.applicationContext.startActivity(intent) - } - - true - } - - // application version - (findPreference(PreferencesManager.SETTINGS_VERSION_PREFERENCE_KEY)).let { - it.summary = getVersion(longFormat = false, useBuildNumber = true) - - it.setOnPreferenceClickListener { pref -> - appContext?.let { ctx -> - copyToClipboard(ctx, pref.summary) - } - true - } - } - - // SDK version - (findPreference(PreferencesManager.SETTINGS_SDK_VERSION_PREFERENCE_KEY)).let { - it.summary = Matrix.getSdkVersion() - - it.setOnPreferenceClickListener { pref -> - appContext?.let { ctx -> - copyToClipboard(ctx, pref.summary) - } - true - } - } - - // olm version - findPreference(PreferencesManager.SETTINGS_OLM_VERSION_PREFERENCE_KEY) - .summary = session.getCryptoVersion(requireContext(), false) - - // copyright - findPreference(PreferencesManager.SETTINGS_COPYRIGHT_PREFERENCE_KEY) - .onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.displayInWebView(VectorSettingsUrls.COPYRIGHT) - false - } - - // terms & conditions - findPreference(PreferencesManager.SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY) - .onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.displayInWebView(VectorSettingsUrls.TAC) - false - } - - // privacy policy - findPreference(PreferencesManager.SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY) - .onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.displayInWebView(VectorSettingsUrls.PRIVACY_POLICY) - false - } - - // third party notice - findPreference(PreferencesManager.SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY) - .onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES) - false - } - - findPreference(PreferencesManager.SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY) - .onPreferenceClickListener = Preference.OnPreferenceClickListener { - // See https://developers.google.com/android/guides/opensource - startActivity(Intent(requireActivity(), OssLicensesMenuActivity::class.java)) - false - } - // update keep medias period findPreference(PreferencesManager.SETTINGS_MEDIA_SAVING_PERIOD_KEY).let { it.summary = PreferencesManager.getSelectedMediasSavingPeriodString(activity) @@ -745,682 +131,6 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref false } } - - // clear medias cache - findPreference(PreferencesManager.SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY).let { - /* - TODO - MXMediaCache.getCachesSize(activity, object : SimpleApiCallback() { - override fun onSuccess(size: Long) { - if (null != activity) { - it.summary = android.text.format.Formatter.formatFileSize(activity, size) - } - } - }) - */ - - it.onPreferenceClickListener = Preference.OnPreferenceClickListener { - notImplemented() - /* TODO - displayLoadingView() - - val task = ClearMediaCacheAsyncTask @Inject constructor( - backgroundTask = { - session.mediaCache.clear() - activity?.let { it -> Glide.get(it).clearDiskCache() } - }, - onCompleteTask = { - hideLoadingView() - - MXMediaCache.getCachesSize(activity, object : SimpleApiCallback() { - override fun onSuccess(size: Long) { - it.summary = Formatter.formatFileSize(activity, size) - } - }) - } - ) - - try { - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) - } catch (e: Exception) { - Timber.e(e, "## session.getMediaCache().clear() failed " + e.message) - task.cancel(true) - hideLoadingView() - } - */ - - false - } - } - - // Incoming call sounds - mUseRiotCallRingtonePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.let { setUseRiotDefaultRingtone(it, mUseRiotCallRingtonePreference.isChecked) } - false - } - - mCallRingtonePreference.let { - activity?.let { activity -> it.summary = getCallRingtoneName(activity) } - it.onPreferenceClickListener = Preference.OnPreferenceClickListener { - displayRingtonePicker() - false - } - } - - // clear cache - findPreference(PreferencesManager.SETTINGS_CLEAR_CACHE_PREFERENCE_KEY).let { - /* - TODO - MXSession.getApplicationSizeCaches(activity, object : SimpleApiCallback() { - override fun onSuccess(size: Long) { - if (null != activity) { - it.summary = android.text.format.Formatter.formatFileSize(activity, size) - } - } - }) - */ - - it.onPreferenceClickListener = Preference.OnPreferenceClickListener { - displayLoadingView() - MainActivity.restartApp(activity!!, clearCache = true, clearCredentials = false) - false - } - } - - // Deactivate account section - - // deactivate account - findPreference(PreferencesManager.SETTINGS_DEACTIVATE_ACCOUNT_KEY) - .onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.let { - notImplemented() - // TODO startActivity(DeactivateAccountActivity.getIntent(it)) - } - - false - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = super.onCreateView(inflater, container, savedInstanceState) - - view?.apply { - val listView = findViewById(android.R.id.list) - listView?.setPadding(0, 0, 0, 0) - } - - return view - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - // if the user toggles the contacts book permission - /* TODO - if (TextUtils.equals(key, ContactsManager.CONTACTS_BOOK_ACCESS_KEY)) { - // reset the current snapshot - ContactsManager.getInstance().clearSnapshot() - } - */ - } - - override fun onAttach(context: Context) { - val screenComponent = DaggerScreenComponent.factory().create(vectorActivity.getVectorComponent(), vectorActivity) - super.onAttach(context) - screenComponent.inject(this) - if (context is VectorSettingsFragmentInteractionListener) { - interactionListener = context - } - - } - - override fun onDetach() { - interactionListener = null - super.onDetach() - } - - override fun onResume() { - super.onResume() - - // find the view from parent activity - mLoadingView = activity?.findViewById(R.id.vector_settings_spinner_views) - - /* TODO - if (session.isAlive) { - val context = activity?.applicationContext - - session.dataHandler.addListener(mEventsListener) - - Matrix.getInstance(context)?.addNetworkEventListener(mNetworkListener) - - session.myUser.refreshThirdPartyIdentifiers(object : SimpleApiCallback() { - override fun onSuccess(info: Void?) { - // ensure that the activity still exists - // and the result is called in the right thread - activity?.runOnUiThread { - refreshEmailsList() - refreshPhoneNumbersList() - } - } - }) - - Matrix.getInstance(context)?.pushManager?.refreshPushersList(Matrix.getInstance(context)?.sessions, object : SimpleApiCallback(activity) { - override fun onSuccess(info: Void?) { - refreshPushersList() - } - }) - - PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this) - - // refresh anything else - refreshPreferences() - refreshNotificationPrivacy() - refreshDisplay() - refreshBackgroundSyncPrefs() - } - */ - - interactionListener?.requestedKeyToHighlight()?.let { key -> - interactionListener?.requestHighlightPreferenceKeyOnResume(null) - val preference = findPreference(key) - (preference as? VectorPreference)?.isHighlighted = true - } - } - - override fun onPause() { - super.onPause() - - val context = activity?.applicationContext - - /* TODO - if (session.isAlive) { - session.dataHandler.removeListener(mEventsListener) - Matrix.getInstance(context)?.removeNetworkEventListener(mNetworkListener) - } - */ - - PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this) - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (allGranted(grantResults)) { - if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) { - changeAvatar() - } else if (requestCode == PERMISSION_REQUEST_CODE_EXPORT_KEYS) { - exportKeys() - } - } - } - - //============================================================================================================== - // Display methods - //============================================================================================================== - - /** - * Display the loading view. - */ - private fun displayLoadingView() { - // search the loading view from the upper view - if (null == mLoadingView) { - var parent = view - - while (parent != null && mLoadingView == null) { - mLoadingView = parent.findViewById(R.id.vector_settings_spinner_views) - parent = parent.parent as View - } - } else { - mLoadingView?.visibility = View.VISIBLE - } - } - - /** - * Hide the loading view. - */ - private fun hideLoadingView() { - mLoadingView?.visibility = View.GONE - } - - /** - * Hide the loading view and refresh the preferences. - * - * @param refresh true to refresh the display - */ - private fun hideLoadingView(refresh: Boolean) { - mLoadingView?.visibility = View.GONE - - if (refresh) { - refreshDisplay() - } - } - - /** - * Refresh the preferences. - */ - private fun refreshDisplay() { - /* TODO - // If Matrix instance is null, then connection can't be there - val isConnected = Matrix.getInstance(activity)?.isConnected ?: false - val appContext = activity?.applicationContext - - val preferenceManager = preferenceManager - - // refresh the avatar - mUserAvatarPreference.refreshAvatar() - mUserAvatarPreference.isEnabled = isConnected - - // refresh the display name - mDisplayNamePreference.summary = session.myUser.displayname - mDisplayNamePreference.text = session.myUser.displayname - mDisplayNamePreference.isEnabled = isConnected - - // change password - mPasswordPreference.isEnabled = isConnected - - // update the push rules - val preferences = PreferenceManager.getDefaultSharedPreferences(appContext) - - val rules = session.dataHandler.pushRules() - - val pushManager = Matrix.getInstance(appContext)?.pushManager - - for (preferenceKey in mPrefKeyToBingRuleId.keys) { - val preference = preferenceManager.findPreference(preferenceKey) - - if (null != preference) { - - if (preference is SwitchPreference) { - when (preferenceKey) { - PreferencesManager.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY -> - preference.isChecked = pushManager?.areDeviceNotificationsAllowed() ?: true - - PreferencesManager.SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY -> { - preference.isChecked = pushManager?.isScreenTurnedOn ?: false - preference.isEnabled = pushManager?.areDeviceNotificationsAllowed() ?: true - } - else -> { - preference.isEnabled = null != rules && isConnected - preference.isChecked = preferences.getBoolean(preferenceKey, false) - } - } - } - } - } - - // If notifications are disabled for the current user account or for the current user device - // The others notifications settings have to be disable too - val areNotificationAllowed = rules?.findDefaultRule(BingRule.RULE_ID_DISABLE_ALL)?.isEnabled == true - - mNotificationPrivacyPreference.isEnabled = !areNotificationAllowed - && (pushManager?.areDeviceNotificationsAllowed() ?: true) && pushManager?.useFcm() ?: true - */ - } - - //============================================================================================================== - // Update items methods - //============================================================================================================== - - /** - * Update the password. - */ - private fun onPasswordUpdateClick() { - activity?.let { activity -> - val view: ViewGroup = activity.layoutInflater.inflate(R.layout.dialog_change_password, null) as ViewGroup - - val showPassword: ImageView = view.findViewById(R.id.change_password_show_passwords) - val oldPasswordTil: TextInputLayout = view.findViewById(R.id.change_password_old_pwd_til) - val oldPasswordText: TextInputEditText = view.findViewById(R.id.change_password_old_pwd_text) - val newPasswordText: TextInputEditText = view.findViewById(R.id.change_password_new_pwd_text) - val confirmNewPasswordTil: TextInputLayout = view.findViewById(R.id.change_password_confirm_new_pwd_til) - val confirmNewPasswordText: TextInputEditText = view.findViewById(R.id.change_password_confirm_new_pwd_text) - val changePasswordLoader: View = view.findViewById(R.id.change_password_loader) - - var passwordShown = false - - showPassword.setOnClickListener(object : View.OnClickListener { - override fun onClick(v: View?) { - passwordShown = !passwordShown - - oldPasswordText.showPassword(passwordShown) - newPasswordText.showPassword(passwordShown) - confirmNewPasswordText.showPassword(passwordShown) - - showPassword.setImageResource(if (passwordShown) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) - } - }) - - val dialog = AlertDialog.Builder(activity) - .setView(view) - .setPositiveButton(R.string.settings_change_password_submit, null) - .setNegativeButton(R.string.cancel, null) - .setOnDismissListener { - val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(view.applicationWindowToken, 0) - } - .create() - - dialog.setOnShowListener { - val updateButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) - updateButton.isEnabled = false - - fun updateUi() { - val oldPwd = oldPasswordText.text.toString().trim() - val newPwd = newPasswordText.text.toString().trim() - val newConfirmPwd = confirmNewPasswordText.text.toString().trim() - - updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && TextUtils.equals(newPwd, newConfirmPwd) - - if (newPwd.isNotEmpty() && newConfirmPwd.isNotEmpty() && !TextUtils.equals(newPwd, newConfirmPwd)) { - confirmNewPasswordTil.error = getString(R.string.passwords_do_not_match) - } - } - - oldPasswordText.addTextChangedListener(object : SimpleTextWatcher() { - override fun afterTextChanged(s: Editable) { - oldPasswordTil.error = null - updateUi() - } - }) - - newPasswordText.addTextChangedListener(object : SimpleTextWatcher() { - override fun afterTextChanged(s: Editable) { - confirmNewPasswordTil.error = null - updateUi() - } - }) - - confirmNewPasswordText.addTextChangedListener(object : SimpleTextWatcher() { - override fun afterTextChanged(s: Editable) { - confirmNewPasswordTil.error = null - updateUi() - } - }) - - fun showPasswordLoadingView(toShow: Boolean) { - if (toShow) { - showPassword.isEnabled = false - oldPasswordText.isEnabled = false - newPasswordText.isEnabled = false - confirmNewPasswordText.isEnabled = false - changePasswordLoader.isVisible = true - updateButton.isEnabled = false - } else { - showPassword.isEnabled = true - oldPasswordText.isEnabled = true - newPasswordText.isEnabled = true - confirmNewPasswordText.isEnabled = true - changePasswordLoader.isVisible = false - updateButton.isEnabled = true - } - } - - updateButton.setOnClickListener { - if (passwordShown) { - // Hide passwords during processing - showPassword.performClick() - } - - val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(view.applicationWindowToken, 0) - - val oldPwd = oldPasswordText.text.toString().trim() - val newPwd = newPasswordText.text.toString().trim() - - notImplemented() - /* TODO - showPasswordLoadingView(true) - - session.updatePassword(oldPwd, newPwd, object : MatrixCallback { - private fun onDone(@StringRes textResId: Int) { - showPasswordLoadingView(false) - - if (textResId == R.string.settings_fail_to_update_password_invalid_current_password) { - oldPasswordTil.error = getString(textResId) - } else { - dialog.dismiss() - activity.toast(textResId, Toast.LENGTH_LONG) - } - } - - override fun onSuccess(info: Void?) { - onDone(R.string.settings_password_updated) - } - - override fun onNetworkError(e: Exception) { - onDone(R.string.settings_fail_to_update_password) - } - - override fun onMatrixError(e: MatrixError) { - if (e.error == "Invalid password") { - onDone(R.string.settings_fail_to_update_password_invalid_current_password) - } else { - dialog.dismiss() - onDone(R.string.settings_fail_to_update_password) - } - } - - override fun onUnexpectedError(e: Exception) { - onDone(R.string.settings_fail_to_update_password) - } - }) - */ - } - } - dialog.show() - } - } - - /** - * Update a push rule. - */ - - private fun onPushRuleClick(preferenceKey: String, newValue: Boolean) { - notImplemented() - /* TODO - val matrixInstance = Matrix.getInstance(context) - val pushManager = matrixInstance.pushManager - - Timber.v("onPushRuleClick $preferenceKey : set to $newValue") - - when (preferenceKey) { - - PreferencesManager.SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY -> { - if (pushManager.isScreenTurnedOn != newValue) { - pushManager.isScreenTurnedOn = newValue - } - } - - PreferencesManager.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY -> { - val isConnected = matrixInstance.isConnected - val isAllowed = pushManager.areDeviceNotificationsAllowed() - - // avoid useless update - if (isAllowed == newValue) { - return - } - - pushManager.setDeviceNotificationsAllowed(!isAllowed) - - // when using FCM - // need to register on servers - if (isConnected && pushManager.useFcm() && (pushManager.isServerRegistered || pushManager.isServerUnRegistered)) { - val listener = object : MatrixCallback { - - private fun onDone() { - activity?.runOnUiThread { - hideLoadingView(true) - refreshPushersList() - } - } - - override fun onSuccess(info: Void?) { - onDone() - } - - override fun onMatrixError(e: MatrixError?) { - // Set again the previous state - pushManager.setDeviceNotificationsAllowed(isAllowed) - onDone() - } - - override fun onNetworkError(e: java.lang.Exception?) { - // Set again the previous state - pushManager.setDeviceNotificationsAllowed(isAllowed) - onDone() - } - - override fun onUnexpectedError(e: java.lang.Exception?) { - // Set again the previous state - pushManager.setDeviceNotificationsAllowed(isAllowed) - onDone() - } - } - - displayLoadingView() - if (pushManager.isServerRegistered) { - pushManager.unregister(listener) - } else { - pushManager.register(listener) - } - } - } - - // check if there is an update - - // on some old android APIs, - // the callback is called even if there is no user interaction - // so the value will be checked to ensure there is really no update. - else -> { - - val ruleId = mPrefKeyToBingRuleId[preferenceKey] - val rule = session.dataHandler.pushRules()?.findDefaultRule(ruleId) - - // check if there is an update - var curValue = null != rule && rule.isEnabled - - if (TextUtils.equals(ruleId, BingRule.RULE_ID_DISABLE_ALL) || TextUtils.equals(ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) { - curValue = !curValue - } - - // on some old android APIs, - // the callback is called even if there is no user interaction - // so the value will be checked to ensure there is really no update. - if (newValue == curValue) { - return - } - - if (null != rule) { - displayLoadingView() - session.dataHandler.bingRulesManager.updateEnableRuleStatus(rule, !rule.isEnabled, object : BingRulesManager.onBingRuleUpdateListener { - private fun onDone() { - refreshDisplay() - hideLoadingView() - } - - override fun onBingRuleUpdateSuccess() { - onDone() - } - - override fun onBingRuleUpdateFailure(errorMessage: String) { - activity?.toast(errorMessage) - onDone() - } - }) - } - } - } - */ - } - - /** - * Update the displayname. - */ - private fun onDisplayNameClick(value: String?) { - notImplemented() - /* TODO - if (!TextUtils.equals(session.myUser.displayname, value)) { - displayLoadingView() - - session.myUser.updateDisplayName(value, object : MatrixCallback { - override fun onSuccess(info: Void?) { - // refresh the settings value - PreferenceManager.getDefaultSharedPreferences(activity).edit { - putString(PreferencesManager.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY, value) - } - - onCommonDone(null) - - refreshDisplay() - } - - override fun onNetworkError(e: Exception) { - onCommonDone(e.localizedMessage) - } - - override fun onMatrixError(e: MatrixError) { - if (MatrixError.M_CONSENT_NOT_GIVEN == e.errcode) { - activity?.runOnUiThread { - hideLoadingView() - (activity as VectorAppCompatActivity).consentNotGivenHelper.displayDialog(e) - } - } else { - onCommonDone(e.localizedMessage) - } - } - - override fun onUnexpectedError(e: Exception) { - onCommonDone(e.localizedMessage) - } - }) - } - */ - } - - private fun displayRingtonePicker() { - val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { - putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, getString(R.string.settings_call_ringtone_dialog_title)) - putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false) - putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true) - putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE) - activity?.let { putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, getCallRingtoneUri(it)) } - } - startActivityForResult(intent, REQUEST_CALL_RINGTONE) - } - - /** - * Update the avatar. - */ - private fun onUpdateAvatarClick() { - notImplemented() - - /* TODO - if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { - changeAvatar() - } - */ - } - - private fun changeAvatar() { - /* TODO - val intent = Intent(activity, VectorMediaPickerActivity::class.java) - intent.putExtra(VectorMediaPickerActivity.EXTRA_AVATAR_MODE, true) - startActivityForResult(intent, VectorUtils.TAKE_IMAGE) - */ - } - - /** - * Refresh the notification privacy setting - */ - private fun refreshNotificationPrivacy() { - /* TODO - val pushManager = Matrix.getInstance(activity).pushManager - - // this setting apply only with FCM for the moment - if (pushManager.useFcm()) { - val notificationPrivacyString = NotificationPrivacyActivity.getNotificationPrivacyString(activity, - pushManager.notificationPrivacy) - mNotificationPrivacyPreference.summary = notificationPrivacyString - } else { - notificationsSettingsCategory.removePreference(mNotificationPrivacyPreference) - } - */ } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -1428,635 +138,16 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref if (resultCode == Activity.RESULT_OK) { when (requestCode) { - REQUEST_CALL_RINGTONE -> { - val callRingtoneUri: Uri? = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) - val thisActivity = activity - if (callRingtoneUri != null && thisActivity != null) { - setCallRingtoneUri(thisActivity, callRingtoneUri) - mCallRingtonePreference.summary = getCallRingtoneName(thisActivity) - } - } - REQUEST_E2E_FILE_REQUEST_CODE -> importKeys(data) - REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList() - REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data) - REQUEST_LOCALE -> { + REQUEST_LOCALE -> { activity?.let { startActivity(it.intent) it.finish() } } - /* TODO - VectorUtils.TAKE_IMAGE -> { - val thumbnailUri = VectorUtils.getThumbnailUriFromIntent(activity, data, session.mediaCache) - - if (null != thumbnailUri) { - displayLoadingView() - - val resource = ResourceUtils.openResource(activity, thumbnailUri, null) - - if (null != resource) { - session.mediaCache.uploadContent(resource.mContentStream, null, resource.mMimeType, null, object : MXMediaUploadListener() { - - override fun onUploadError(uploadId: String?, serverResponseCode: Int, serverErrorMessage: String?) { - activity?.runOnUiThread { onCommonDone(serverResponseCode.toString() + " : " + serverErrorMessage) } - } - - override fun onUploadComplete(uploadId: String?, contentUri: String?) { - activity?.runOnUiThread { - session.myUser.updateAvatarUrl(contentUri, object : MatrixCallback { - override fun onSuccess(info: Void?) { - onCommonDone(null) - refreshDisplay() - } - - override fun onNetworkError(e: Exception) { - onCommonDone(e.localizedMessage) - } - - override fun onMatrixError(e: MatrixError) { - if (MatrixError.M_CONSENT_NOT_GIVEN == e.errcode) { - activity?.runOnUiThread { - hideLoadingView() - (activity as VectorAppCompatActivity).consentNotGivenHelper.displayDialog(e) - } - } else { - onCommonDone(e.localizedMessage) - } - } - - override fun onUnexpectedError(e: Exception) { - onCommonDone(e.localizedMessage) - } - }) - } - } - }) - } - } - } - */ } } } - /** - * Refresh the known information about the account - */ - private fun refreshPreferences() { - PreferenceManager.getDefaultSharedPreferences(activity).edit { - putString(PreferencesManager.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY, "TODO") //session.myUser.displayname) - - /* TODO - session.dataHandler.pushRules()?.let { - for (preferenceKey in mPrefKeyToBingRuleId.keys) { - val preference = findPreference(preferenceKey) - - if (null != preference && preference is SwitchPreference) { - val ruleId = mPrefKeyToBingRuleId[preferenceKey] - - val rule = it.findDefaultRule(ruleId) - var isEnabled = null != rule && rule.isEnabled - - if (TextUtils.equals(ruleId, BingRule.RULE_ID_DISABLE_ALL) || TextUtils.equals(ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) { - isEnabled = !isEnabled - } else if (isEnabled) { - val domainActions = rule?.domainActions - - // no action -> noting will be done - if (null == domainActions || domainActions.isEmpty()) { - isEnabled = false - } else if (1 == domainActions.size) { - try { - isEnabled = !TextUtils.equals(domainActions[0] as String, BingRule.ACTION_DONT_NOTIFY) - } catch (e: Exception) { - Timber.e(e, "## refreshPreferences failed " + e.message) - } - - } - }// check if the rule is only defined by don't notify - - putBoolean(preferenceKey, isEnabled) - } - } - } - */ - } - } - - /** - * Display a dialog which asks confirmation for the deletion of a 3pid - * - * @param pid the 3pid to delete - * @param preferenceSummary the displayed 3pid - */ - private fun displayDelete3PIDConfirmationDialog(/* TODO pid: ThirdPartyIdentifier,*/ preferenceSummary: CharSequence) { - val mediumFriendlyName = "TODO" // ThreePid.getMediumFriendlyName(pid.medium, activity).toLowerCase(VectorLocale.applicationLocale) - val dialogMessage = getString(R.string.settings_delete_threepid_confirmation, mediumFriendlyName, preferenceSummary) - - activity?.let { - AlertDialog.Builder(it) - .setTitle(R.string.dialog_title_confirmation) - .setMessage(dialogMessage) - .setPositiveButton(R.string.remove) { _, _ -> - notImplemented() - /* TODO - displayLoadingView() - - session.myUser.delete3Pid(pid, object : MatrixCallback { - override fun onSuccess(info: Void?) { - when (pid.medium) { - ThreePid.MEDIUM_EMAIL -> refreshEmailsList() - ThreePid.MEDIUM_MSISDN -> refreshPhoneNumbersList() - } - onCommonDone(null) - } - - override fun onNetworkError(e: Exception) { - onCommonDone(e.localizedMessage) - } - - override fun onMatrixError(e: MatrixError) { - onCommonDone(e.localizedMessage) - } - - override fun onUnexpectedError(e: Exception) { - onCommonDone(e.localizedMessage) - } - }) - */ - } - .setNegativeButton(R.string.cancel, null) - .show() - } - } - - //============================================================================================================== - // ignored users list management - //============================================================================================================== - - /** - * Refresh the ignored users list - */ - private fun refreshIgnoredUsersList() { - val ignoredUsersList = mutableListOf() // TODO session.dataHandler.ignoredUserIds - - ignoredUsersList.sortWith(Comparator { u1, u2 -> - u1.toLowerCase(VectorLocale.applicationLocale).compareTo(u2.toLowerCase(VectorLocale.applicationLocale)) - }) - - val preferenceScreen = preferenceScreen - - preferenceScreen.removePreference(mIgnoredUserSettingsCategory) - preferenceScreen.removePreference(mIgnoredUserSettingsCategoryDivider) - mIgnoredUserSettingsCategory.removeAll() - - if (ignoredUsersList.size > 0) { - preferenceScreen.addPreference(mIgnoredUserSettingsCategoryDivider) - preferenceScreen.addPreference(mIgnoredUserSettingsCategory) - - for (userId in ignoredUsersList) { - val preference = Preference(activity) - - preference.title = userId - preference.key = IGNORED_USER_KEY_BASE + userId - - preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.let { - AlertDialog.Builder(it) - .setMessage(getString(R.string.settings_unignore_user, userId)) - .setPositiveButton(R.string.yes) { _, _ -> - displayLoadingView() - - val idsList = ArrayList() - idsList.add(userId) - - notImplemented() - /* TODO - session.unIgnoreUsers(idsList, object : MatrixCallback { - override fun onSuccess(info: Void?) { - onCommonDone(null) - } - - override fun onNetworkError(e: Exception) { - onCommonDone(e.localizedMessage) - } - - override fun onMatrixError(e: MatrixError) { - onCommonDone(e.localizedMessage) - } - - override fun onUnexpectedError(e: Exception) { - onCommonDone(e.localizedMessage) - } - }) - */ - } - .setNegativeButton(R.string.no, null) - .show() - } - - false - } - - mIgnoredUserSettingsCategory.addPreference(preference) - } - } - } - - //============================================================================================================== - // pushers list management - //============================================================================================================== - - /** - * Refresh the pushers list - */ - private fun refreshPushersList() { - activity?.let { activity -> - /* TODO - val pushManager = Matrix.getInstance(activity).pushManager - val pushersList = ArrayList(pushManager.mPushersList) - - if (pushersList.isEmpty()) { - preferenceScreen.removePreference(mPushersSettingsCategory) - preferenceScreen.removePreference(mPushersSettingsDivider) - return - } - - // check first if there is an update - var isNewList = true - if (pushersList.size == mDisplayedPushers.size) { - isNewList = !mDisplayedPushers.containsAll(pushersList) - } - - if (isNewList) { - // remove the displayed one - mPushersSettingsCategory.removeAll() - - // add new emails list - mDisplayedPushers = pushersList - - var index = 0 - - for (pushRule in mDisplayedPushers) { - if (null != pushRule.lang) { - val isThisDeviceTarget = TextUtils.equals(pushManager.currentRegistrationToken, pushRule.pushkey) - - val preference = VectorPreference(activity).apply { - mTypeface = if (isThisDeviceTarget) Typeface.BOLD else Typeface.NORMAL - } - preference.title = pushRule.deviceDisplayName - preference.summary = pushRule.appDisplayName - preference.key = PUSHER_PREFERENCE_KEY_BASE + index - index++ - mPushersSettingsCategory.addPreference(preference) - - // the user cannot remove the self device target - if (!isThisDeviceTarget) { - preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener { - override fun onPreferenceLongClick(preference: Preference): Boolean { - AlertDialog.Builder(activity) - .setTitle(R.string.dialog_title_confirmation) - .setMessage(R.string.settings_delete_notification_targets_confirmation) - .setPositiveButton(R.string.remove) - { _, _ -> - displayLoadingView() - pushManager.unregister(session, pushRule, object : MatrixCallback { - override fun onSuccess(info: Void?) { - refreshPushersList() - onCommonDone(null) - } - - override fun onNetworkError(e: Exception) { - onCommonDone(e.localizedMessage) - } - - override fun onMatrixError(e: MatrixError) { - onCommonDone(e.localizedMessage) - } - - override fun onUnexpectedError(e: Exception) { - onCommonDone(e.localizedMessage) - } - }) - } - .setNegativeButton(R.string.cancel, null) - .show() - return true - } - } - } - } - } - } - */ - } - } - - //============================================================================================================== - // Email management - //============================================================================================================== - - /** - * Refresh the emails list - */ - private fun refreshEmailsList() { - val currentEmail3PID = emptyList() // TODO ArrayList(session.myUser.getlinkedEmails()) - - val newEmailsList = ArrayList() - for (identifier in currentEmail3PID) { - // TODO newEmailsList.add(identifier.address) - } - - // check first if there is an update - var isNewList = true - if (newEmailsList.size == mDisplayedEmails.size) { - isNewList = !mDisplayedEmails.containsAll(newEmailsList) - } - - if (isNewList) { - // remove the displayed one - run { - var index = 0 - while (true) { - val preference = mUserSettingsCategory.findPreference(EMAIL_PREFERENCE_KEY_BASE + index) - - if (null != preference) { - mUserSettingsCategory.removePreference(preference) - } else { - break - } - index++ - } - } - - // add new emails list - mDisplayedEmails = newEmailsList - - val addEmailBtn = mUserSettingsCategory.findPreference(ADD_EMAIL_PREFERENCE_KEY) - ?: return - - var order = addEmailBtn.order - - for ((index, email3PID) in currentEmail3PID.withIndex()) { - val preference = VectorPreference(activity!!) - - preference.title = getString(R.string.settings_email_address) - preference.summary = "TODO" // email3PID.address - preference.key = EMAIL_PREFERENCE_KEY_BASE + index - preference.order = order - - preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { pref -> - displayDelete3PIDConfirmationDialog(/* TODO email3PID, */ pref.summary) - true - } - - preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener { - override fun onPreferenceLongClick(preference: Preference): Boolean { - activity?.let { copyToClipboard(it, "TODO") } //email3PID.address) } - return true - } - } - - mUserSettingsCategory.addPreference(preference) - - order++ - } - - addEmailBtn.order = order - } - } - - /** - * A request has been processed. - * Display a toast if there is a an error message - * - * @param errorMessage the error message - */ - private fun onCommonDone(errorMessage: String?) { - if (!isAdded) { - return - } - activity?.runOnUiThread { - if (!TextUtils.isEmpty(errorMessage) && errorMessage != null) { - activity?.toast(errorMessage!!) - } - hideLoadingView() - } - } - - /** - * Attempt to add a new email to the account - * - * @param email the email to add. - */ - private fun addEmail(email: String) { - // check first if the email syntax is valid - // if email is null , then also its invalid email - if (TextUtils.isEmpty(email) || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) { - activity?.toast(R.string.auth_invalid_email) - return - } - - // check first if the email syntax is valid - if (mDisplayedEmails.indexOf(email) >= 0) { - activity?.toast(R.string.auth_email_already_defined) - return - } - - notImplemented() - /* TODO - val pid = ThreePid(email, ThreePid.MEDIUM_EMAIL) - - displayLoadingView() - - session.myUser.requestEmailValidationToken(pid, object : MatrixCallback { - override fun onSuccess(info: Void?) { - activity?.runOnUiThread { showEmailValidationDialog(pid) } - } - - override fun onNetworkError(e: Exception) { - onCommonDone(e.localizedMessage) - } - - override fun onMatrixError(e: MatrixError) { - if (TextUtils.equals(MatrixError.THREEPID_IN_USE, e.errcode)) { - onCommonDone(getString(R.string.account_email_already_used_error)) - } else { - onCommonDone(e.localizedMessage) - } - } - - override fun onUnexpectedError(e: Exception) { - onCommonDone(e.localizedMessage) - } - }) - */ - } - - /** - * Show an email validation dialog to warn the user tho valid his email link. - * - * @param pid the used pid. - */ - /* TODO - private fun showEmailValidationDialog(pid: ThreePid) { - activity?.let { - AlertDialog.Builder(it) - .setTitle(R.string.account_email_validation_title) - .setMessage(R.string.account_email_validation_message) - .setPositiveButton(R.string._continue) { _, _ -> - session.myUser.add3Pid(pid, true, object : MatrixCallback { - override fun onSuccess(info: Void?) { - it.runOnUiThread { - hideLoadingView() - refreshEmailsList() - } - } - - override fun onNetworkError(e: Exception) { - onCommonDone(e.localizedMessage) - } - - override fun onMatrixError(e: MatrixError) { - if (TextUtils.equals(e.errcode, MatrixError.THREEPID_AUTH_FAILED)) { - it.runOnUiThread { - hideLoadingView() - it.toast(R.string.account_email_validation_error) - } - } else { - onCommonDone(e.localizedMessage) - } - } - - override fun onUnexpectedError(e: Exception) { - onCommonDone(e.localizedMessage) - } - }) - } - .setNegativeButton(R.string.cancel) { _, _ -> - hideLoadingView() - } - .show() - } - } */ - - //============================================================================================================== - // Phone number management - //============================================================================================================== - - /** - * Refresh phone number list - */ - private fun refreshPhoneNumbersList() { - /* TODO - val currentPhoneNumber3PID = ArrayList(session.myUser.getlinkedPhoneNumbers()) - - val phoneNumberList = ArrayList() - for (identifier in currentPhoneNumber3PID) { - phoneNumberList.add(identifier.address) - } - - // check first if there is an update - var isNewList = true - if (phoneNumberList.size == mDisplayedPhoneNumber.size) { - isNewList = !mDisplayedPhoneNumber.containsAll(phoneNumberList) - } - - if (isNewList) { - // remove the displayed one - run { - var index = 0 - while (true) { - val preference = mUserSettingsCategory.findPreference(PHONE_NUMBER_PREFERENCE_KEY_BASE + index) - - if (null != preference) { - mUserSettingsCategory.removePreference(preference) - } else { - break - } - index++ - } - } - - // add new phone number list - mDisplayedPhoneNumber = phoneNumberList - - val addPhoneBtn = mUserSettingsCategory.findPreference(ADD_PHONE_NUMBER_PREFERENCE_KEY) - ?: return - - var order = addPhoneBtn.order - - for ((index, phoneNumber3PID) in currentPhoneNumber3PID.withIndex()) { - val preference = VectorPreference(activity!!) - - preference.title = getString(R.string.settings_phone_number) - var phoneNumberFormatted = phoneNumber3PID.address - try { - // Attempt to format phone number - val phoneNumber = PhoneNumberUtil.getInstance().parse("+$phoneNumberFormatted", null) - phoneNumberFormatted = PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL) - } catch (e: NumberParseException) { - // Do nothing, we will display raw version - } - - preference.summary = phoneNumberFormatted - preference.key = PHONE_NUMBER_PREFERENCE_KEY_BASE + index - preference.order = order - - preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - displayDelete3PIDConfirmationDialog(phoneNumber3PID, preference.summary) - true - } - - preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener { - override fun onPreferenceLongClick(preference: Preference): Boolean { - activity?.let { copyToClipboard(it, phoneNumber3PID.address) } - return true - } - } - - order++ - mUserSettingsCategory.addPreference(preference) - } - - addPhoneBtn.order = order - } */ - } - - //============================================================================================================== - // contacts management - //============================================================================================================== - - private fun setContactsPreferences() { - /* TODO - // Permission - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // on Android >= 23, use the system one - mContactSettingsCategory.removePreference(findPreference(ContactsManager.CONTACTS_BOOK_ACCESS_KEY)) - } - // Phonebook country - mContactPhonebookCountryPreference.summary = PhoneNumberUtils.getHumanCountryCode(PhoneNumberUtils.getCountryCode(activity)) - - mContactPhonebookCountryPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - val intent = CountryPickerActivity.getIntent(activity, true) - startActivityForResult(intent, REQUEST_PHONEBOOK_COUNTRY) - true - } - */ - } - - private fun onPhonebookCountryUpdate(data: Intent?) { - /* TODO - if (data != null && data.hasExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_NAME) - && data.hasExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_CODE)) { - val countryCode = data.getStringExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_CODE) - if (!TextUtils.equals(countryCode, PhoneNumberUtils.getCountryCode(activity))) { - PhoneNumberUtils.setCountryCode(activity, countryCode) - mContactPhonebookCountryPreference.summary = data.getStringExtra(CountryPickerActivity.EXTRA_OUT_COUNTRY_NAME) - } - } - */ - } //============================================================================================================== // user interface management @@ -2114,864 +205,8 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref } } - //============================================================================================================== - // background sync management - //============================================================================================================== - - /** - * Convert a delay in seconds to string - * - * @param seconds the delay in seconds - * @return the text - */ - private fun secondsToText(seconds: Int): String { - return if (seconds > 1) { - seconds.toString() + " " + getString(R.string.settings_seconds) - } else { - seconds.toString() + " " + getString(R.string.settings_second) - } - } - - /** - * Refresh the background sync preference - */ - private fun refreshBackgroundSyncPrefs() { - /* TODO - activity?.let { activity -> - val pushManager = Matrix.getInstance(activity).pushManager - - val timeout = pushManager.backgroundSyncTimeOut / 1000 - val delay = pushManager.backgroundSyncDelay / 1000 - - // update the settings - PreferenceManager.getDefaultSharedPreferences(activity).edit { - putString(PreferencesManager.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, timeout.toString() + "") - putString(PreferencesManager.SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, delay.toString() + "") - } - - mSyncRequestTimeoutPreference?.let { - it.summary = secondsToText(timeout) - it.text = timeout.toString() + "" - - it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - var newTimeOut = timeout - - try { - newTimeOut = Integer.parseInt(newValue as String) - } catch (e: Exception) { - Timber.e(e, "## refreshBackgroundSyncPrefs : parseInt failed " + e.message) - } - - if (newTimeOut != timeout) { - pushManager.backgroundSyncTimeOut = newTimeOut * 1000 - - activity.runOnUiThread { refreshBackgroundSyncPrefs() } - } - - false - } - } - - mSyncRequestDelayPreference?.let { - it.summary = secondsToText(delay) - it.text = delay.toString() + "" - - it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - var newDelay = delay - - try { - newDelay = Integer.parseInt(newValue as String) - } catch (e: Exception) { - Timber.e(e, "## refreshBackgroundSyncPrefs : parseInt failed " + e.message) - } - - if (newDelay != delay) { - pushManager.backgroundSyncDelay = newDelay * 1000 - - activity.runOnUiThread { refreshBackgroundSyncPrefs() } - } - - false - } - } - } - */ - } - - //============================================================================================================== - // Cryptography - //============================================================================================================== - - private fun removeCryptographyPreference() { - preferenceScreen.let { - it.removePreference(mCryptographyCategory) - it.removePreference(mCryptographyCategoryDivider) - - // Also remove keys management section - it.removePreference(mCryptographyManageCategory) - it.removePreference(mCryptographyManageCategoryDivider) - } - } - - /** - * Build the cryptography preference section. - * - * @param aMyDeviceInfo the device info - */ - private fun refreshCryptographyPreference(aMyDeviceInfo: DeviceInfo?) { - val userId = session.sessionParams.credentials.userId - val deviceId = session.sessionParams.credentials.deviceId - - // device name - if (null != aMyDeviceInfo) { - cryptoInfoDeviceNamePreference.summary = aMyDeviceInfo.displayName - - cryptoInfoDeviceNamePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - displayDeviceRenameDialog(aMyDeviceInfo) - true - } - - cryptoInfoDeviceNamePreference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener { - override fun onPreferenceLongClick(preference: Preference): Boolean { - activity?.let { copyToClipboard(it, aMyDeviceInfo.displayName!!) } - return true - } - } - } - - // crypto section: device ID - if (!TextUtils.isEmpty(deviceId)) { - cryptoInfoDeviceIdPreference.summary = deviceId - - cryptoInfoDeviceIdPreference.setOnPreferenceClickListener { - activity?.let { copyToClipboard(it, deviceId!!) } - true - } - } - - // crypto section: device key (fingerprint) - if (!TextUtils.isEmpty(deviceId) && !TextUtils.isEmpty(userId)) { - val deviceInfo = session.getDeviceInfo(userId, deviceId) - - if (null != deviceInfo && !TextUtils.isEmpty(deviceInfo.fingerprint())) { - cryptoInfoTextPreference.summary = deviceInfo.getFingerprintHumanReadable() - - cryptoInfoTextPreference.setOnPreferenceClickListener { - deviceInfo.fingerprint()?.let { - copyToClipboard(requireActivity(), it) - } - true - } - } - } - - sendToUnverifiedDevicesPref.isChecked = false - - sendToUnverifiedDevicesPref.isChecked = session.getGlobalBlacklistUnverifiedDevices() - - sendToUnverifiedDevicesPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { - session.setGlobalBlacklistUnverifiedDevices(sendToUnverifiedDevicesPref.isChecked) - - true - } - } - - private fun refreshKeysManagementSection() { - //If crypto is not enabled parent section will be removed - //TODO notice that this will not work when no network - manageBackupPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { - context?.let { - startActivity(KeysBackupManageActivity.intent(it)) - } - false - } - - exportPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { - exportKeys() - true - } - - importPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { - importKeys() - true - } - } - - //============================================================================================================== - // devices list - //============================================================================================================== - - private fun removeDevicesPreference() { - preferenceScreen.let { - it.removePreference(mDevicesListSettingsCategory) - it.removePreference(mDevicesListSettingsCategoryDivider) - } - } - - /** - * Force the refresh of the devices list.

- * The devices list is the list of the devices where the user as looged in. - * It can be any mobile device, as any browser. - */ - private fun refreshDevicesList() { - if (session.isCryptoEnabled() && !TextUtils.isEmpty(session.sessionParams.credentials.deviceId)) { - // display a spinner while loading the devices list - if (0 == mDevicesListSettingsCategory.preferenceCount) { - activity?.let { - val preference = ProgressBarPreference(it) - mDevicesListSettingsCategory.addPreference(preference) - } - } - - session.getDevicesList(object : MatrixCallback { - override fun onSuccess(data: DevicesListResponse) { - if (!isAdded) { - return - } - - if (data.devices?.isEmpty() == true) { - removeDevicesPreference() - } else { - buildDevicesSettings(data.devices!!) - } - } - - override fun onFailure(failure: Throwable) { - if (!isAdded) { - return - } - - removeDevicesPreference() - onCommonDone(failure.message) - } - }) - } else { - removeDevicesPreference() - removeCryptographyPreference() - } - } - - /** - * Build the devices portion of the settings.

- * Each row correspond to a device ID and its corresponding device name. Clicking on the row - * display a dialog containing: the device ID, the device name and the "last seen" information. - * - * @param aDeviceInfoList the list of the devices - */ - private fun buildDevicesSettings(aDeviceInfoList: List) { - var preference: VectorPreference - var typeFaceHighlight: Int - var isNewList = true - val myDeviceId = session.sessionParams.credentials.deviceId - - if (aDeviceInfoList.size == mDevicesNameList.size) { - isNewList = !mDevicesNameList.containsAll(aDeviceInfoList) - } - - if (isNewList) { - var prefIndex = 0 - mDevicesNameList = aDeviceInfoList - - // sort before display: most recent first - mDevicesNameList.sortByLastSeen() - - // start from scratch: remove the displayed ones - mDevicesListSettingsCategory.removeAll() - - for (deviceInfo in mDevicesNameList) { - // set bold to distinguish current device ID - if (null != myDeviceId && myDeviceId == deviceInfo.deviceId) { - mMyDeviceInfo = deviceInfo - typeFaceHighlight = Typeface.BOLD - } else { - typeFaceHighlight = Typeface.NORMAL - } - - // add the edit text preference - preference = VectorPreference(requireActivity()).apply { - mTypeface = typeFaceHighlight - } - - if (null == deviceInfo.deviceId && null == deviceInfo.displayName) { - continue - } else { - if (null != deviceInfo.deviceId) { - preference.title = deviceInfo.deviceId - } - - // display name parameter can be null (new JSON API) - if (null != deviceInfo.displayName) { - preference.summary = deviceInfo.displayName - } - } - - preference.key = DEVICES_PREFERENCE_KEY_BASE + prefIndex - prefIndex++ - - // onClick handler: display device details dialog - preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - displayDeviceDetailsDialog(deviceInfo) - true - } - - mDevicesListSettingsCategory.addPreference(preference) - } - - refreshCryptographyPreference(mMyDeviceInfo) - } - } - - /** - * Display a dialog containing the device ID, the device name and the "last seen" information.<> - * This dialog allow to delete the corresponding device (see [.displayDeviceDeletionDialog]) - * - * @param aDeviceInfo the device information - */ - private fun displayDeviceDetailsDialog(aDeviceInfo: DeviceInfo) { - - activity?.let { - - val builder = AlertDialog.Builder(it) - val inflater = it.layoutInflater - val layout = inflater.inflate(R.layout.dialog_device_details, null) - var textView = layout.findViewById(R.id.device_id) - - textView.text = aDeviceInfo.deviceId - - // device name - textView = layout.findViewById(R.id.device_name) - val displayName = if (TextUtils.isEmpty(aDeviceInfo.displayName)) LABEL_UNAVAILABLE_DATA else aDeviceInfo.displayName - textView.text = displayName - - // last seen info - textView = layout.findViewById(R.id.device_last_seen) - - val lastSeenIp = aDeviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-" - - val lastSeenTime = aDeviceInfo.lastSeenTs?.let { ts -> - val dateFormatTime = SimpleDateFormat("HH:mm:ss") - val date = Date(ts) - - val time = dateFormatTime.format(date) - val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()) - - dateFormat.format(date) + ", " + time - } ?: "-" - - val lastSeenInfo = getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime) - textView.text = lastSeenInfo - - // title & icon - builder.setTitle(R.string.devices_details_dialog_title) - .setIcon(android.R.drawable.ic_dialog_info) - .setView(layout) - .setPositiveButton(R.string.rename) { _, _ -> displayDeviceRenameDialog(aDeviceInfo) } - - // disable the deletion for our own device - if (!TextUtils.equals(session.getMyDevice()?.deviceId, aDeviceInfo.deviceId)) { - builder.setNegativeButton(R.string.delete) { _, _ -> - //displayDeviceDeletionDialog(aDeviceInfo) - } - } - builder.setNeutralButton(R.string.cancel, null) - .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - dialog.cancel() - return@OnKeyListener true - } - false - }) - .show() - } - } - - /** - * Display an alert dialog to rename a device - * - * @param aDeviceInfoToRename device info - */ - private fun displayDeviceRenameDialog(aDeviceInfoToRename: DeviceInfo) { - activity?.let { - val inflater = it.layoutInflater - val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) - - val input = layout.findViewById(R.id.edit_text) - input.setText(aDeviceInfoToRename.displayName) - - AlertDialog.Builder(it) - .setTitle(R.string.devices_details_device_name) - .setView(layout) - .setPositiveButton(R.string.ok) { _, _ -> - displayLoadingView() - - val newName = input.text.toString() - - session.setDeviceName(aDeviceInfoToRename.deviceId!!, newName, object : MatrixCallback { - override fun onSuccess(data: Unit) { - hideLoadingView() - - // search which preference is updated - val count = mDevicesListSettingsCategory.preferenceCount - - for (i in 0 until count) { - val pref = mDevicesListSettingsCategory.getPreference(i) - - if (TextUtils.equals(aDeviceInfoToRename.deviceId, pref.title)) { - pref.summary = newName - } - } - - // detect if the updated device is the current account one - if (TextUtils.equals(cryptoInfoDeviceIdPreference.summary, aDeviceInfoToRename.deviceId)) { - cryptoInfoDeviceNamePreference.summary = newName - } - - // Also change the display name in aDeviceInfoToRename, in case of multiple renaming - aDeviceInfoToRename.displayName = newName - } - - override fun onFailure(failure: Throwable) { - onCommonDone(failure.localizedMessage) - } - }) - } - .setNegativeButton(R.string.cancel, null) - .show() - } - } - - /** - * Try to delete a device. - * - * @param deviceInfo the device to delete - */ - private fun deleteDevice(deviceInfo: DeviceInfo) { - val deviceId = deviceInfo.deviceId - if (deviceId == null) { - Timber.e("## displayDeviceDeletionDialog(): sanity check failure") - return - } - - displayLoadingView() - session.deleteDevice(deviceId, object : MatrixCallback { - override fun onSuccess(data: Unit) { - hideLoadingView() - // force settings update - refreshDevicesList() - } - - override fun onFailure(failure: Throwable) { - var isPasswordRequestFound = false - - if (failure is Failure.RegistrationFlowError) { - // We only support LoginFlowTypes.PASSWORD - // Check if we can provide the user password - failure.registrationFlowResponse.flows?.forEach { interactiveAuthenticationFlow -> - isPasswordRequestFound = isPasswordRequestFound || interactiveAuthenticationFlow.stages?.any { it == LoginFlowTypes.PASSWORD } == true - } - - if (isPasswordRequestFound) { - maybeShowDeleteDeviceWithPasswordDialog(deviceId, failure.registrationFlowResponse.session) - } - - } - - if (!isPasswordRequestFound) { - // LoginFlowTypes.PASSWORD not supported, and this is the only one RiotX supports so far... - onCommonDone(failure.localizedMessage) - } - } - }) - } - - /** - * Show a dialog to ask for user password, or use a previously entered password. - */ - private fun maybeShowDeleteDeviceWithPasswordDialog(deviceId: String, authSession: String?) { - if (!TextUtils.isEmpty(mAccountPassword)) { - deleteDeviceWithPassword(deviceId, authSession, mAccountPassword) - } else { - activity?.let { - val inflater = it.layoutInflater - val layout = inflater.inflate(R.layout.dialog_device_delete, null) - val passwordEditText = layout.findViewById(R.id.delete_password) - - AlertDialog.Builder(it) - .setIcon(android.R.drawable.ic_dialog_alert) - .setTitle(R.string.devices_delete_dialog_title) - .setView(layout) - .setPositiveButton(R.string.devices_delete_submit_button_label, DialogInterface.OnClickListener { _, _ -> - if (TextUtils.isEmpty(passwordEditText.toString())) { - it.toast(R.string.error_empty_field_your_password) - return@OnClickListener - } - mAccountPassword = passwordEditText.text.toString() - deleteDeviceWithPassword(deviceId, authSession, mAccountPassword) - }) - .setNegativeButton(R.string.cancel) { _, _ -> - hideLoadingView() - } - .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - dialog.cancel() - hideLoadingView() - return@OnKeyListener true - } - false - }) - .show() - } - } - } - - private fun deleteDeviceWithPassword(deviceId: String, authSession: String?, accountPassword: String) { - session.deleteDeviceWithUserPassword(deviceId, authSession, accountPassword, object : MatrixCallback { - override fun onSuccess(data: Unit) { - hideLoadingView() - // force settings update - refreshDevicesList() - } - - override fun onFailure(failure: Throwable) { - // Password is maybe not good - onCommonDone(failure.localizedMessage) - mAccountPassword = "" - } - }) - } - - /** - * Manage the e2e keys export. - */ - private fun exportKeys() { - // We need WRITE_EXTERNAL permission - if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_EXPORT_KEYS)) { - activity?.let { activity -> - ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener { - override fun onPassphrase(passphrase: String) { - displayLoadingView() - - KeysExporter(session) - .export(requireContext(), - passphrase, - object : MatrixCallback { - override fun onSuccess(data: String) { - if (isAdded) { - hideLoadingView() - - AlertDialog.Builder(activity) - .setMessage(getString(R.string.encryption_export_saved_as, data)) - .setCancelable(false) - .setPositiveButton(R.string.ok, null) - .show() - } - } - - override fun onFailure(failure: Throwable) { - onCommonDone(failure.localizedMessage) - } - - }) - } - }) - } - } - } - - /** - * Manage the e2e keys import. - */ - @SuppressLint("NewApi") - private fun importKeys() { - activity?.let { openFileSelection(it, this, false, REQUEST_E2E_FILE_REQUEST_CODE) } - } - - /** - * Manage the e2e keys import. - * - * @param intent the intent result - */ - private fun importKeys(intent: Intent?) { - // sanity check - if (null == intent) { - return - } - - val sharedDataItems = analyseIntent(intent) - val thisActivity = activity - - if (sharedDataItems.isNotEmpty() && thisActivity != null) { - val sharedDataItem = sharedDataItems[0] - - val uri = when (sharedDataItem) { - is ExternalIntentData.IntentDataUri -> sharedDataItem.uri - is ExternalIntentData.IntentDataClipData -> sharedDataItem.clipDataItem.uri - else -> null - } - - val mimetype = when (sharedDataItem) { - is ExternalIntentData.IntentDataClipData -> sharedDataItem.mimeType - else -> null - } - - if (uri == null) { - return - } - - val appContext = thisActivity.applicationContext - - val filename = getFilenameFromUri(appContext, uri) - - val dialogLayout = thisActivity.layoutInflater.inflate(R.layout.dialog_import_e2e_keys, null) - - val textView = dialogLayout.findViewById(R.id.dialog_e2e_keys_passphrase_filename) - - if (filename.isNullOrBlank()) { - textView.isVisible = false - } else { - textView.isVisible = true - textView.text = getString(R.string.import_e2e_keys_from_file, filename) - } - - val builder = AlertDialog.Builder(thisActivity) - .setTitle(R.string.encryption_import_room_keys) - .setView(dialogLayout) - - val passPhraseEditText = dialogLayout.findViewById(R.id.dialog_e2e_keys_passphrase_edit_text) - val importButton = dialogLayout.findViewById