From f0165f69d377df31ae8278c63a784eb43e27f052 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 13:21:00 +0000 Subject: [PATCH 001/114] Bump realm-gradle-plugin from 10.11.0 to 10.11.1 Bumps [realm-gradle-plugin](https://github.com/realm/realm-java) from 10.11.0 to 10.11.1. - [Release notes](https://github.com/realm/realm-java/releases) - [Changelog](https://github.com/realm/realm-java/blob/v10.11.1/CHANGELOG.md) - [Commits](https://github.com/realm/realm-java/compare/v10.11.0...v10.11.1) --- updated-dependencies: - dependency-name: io.realm:realm-gradle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index ee58db748c..7260d30f5d 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -17,7 +17,7 @@ buildscript { } } dependencies { - classpath "io.realm:realm-gradle-plugin:10.11.0" + classpath "io.realm:realm-gradle-plugin:10.11.1" } } From 91b0465caaaeb1ef0cfaf46f35707fb2a98b0c6b Mon Sep 17 00:00:00 2001 From: Fork Liang Date: Mon, 1 Aug 2022 03:41:02 +0000 Subject: [PATCH 002/114] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 0fae38bf6e..ab88307fc0 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -6,7 +6,7 @@ %1$s 加入了房间 %1$s 离开了房间 %1$s 拒绝了邀请 - %1$s 移除了 %2$s + %1$s 踢了 %2$s %1$s 解封了 %2$s %1$s 封禁了 %2$s %1$s 更换了他们的头像 @@ -33,7 +33,7 @@ 电子邮箱地址 手机号码 %1$s 撤回了对 %2$s 的邀请 - %1$s 让未来的房间历史记录对 %2$s 可见 + %1$s 让未来的房间聊天记录对 %2$s 可见 %1$s 向 %2$s 发送了加入房间的邀请 %1$s 接受了 %2$s 的邀请 空房间 @@ -90,7 +90,7 @@ 你加入了房间 你离开了房间 你拒绝了邀请 - 你移除了 %1$s + 你踢了 %1$s 你解封了 %1$s 你封禁了 %1$s 你撤回了对 %1$s 的邀请 @@ -163,7 +163,7 @@ 你邀请了 %1$s %1$s 邀请了 %2$s 你在此处升级。 - %s 在此处升级。 + %s 是升级后的房间。 你使未来的消息对 %1$s 可见 %1$s 使未来的消息对 %2$s 可见 你离开了房间 @@ -1775,7 +1775,7 @@ • 匹配 %s 的服务器现已被屏蔽。 • 已封禁匹配 IP 地址的服务器。 • 已允许匹配 IP 地址的服务器。 - • 匹配 %s 的服务器被屏蔽。 + • 匹配 %s 的服务器已被屏蔽。 • 已允许匹配 %s 的服务器。 已勾选 已选中 @@ -2232,7 +2232,7 @@ 您确定要删除此投票吗?一旦移除,就无法恢复。 删除投票 投票已结束 - 已投票 + 投票 结束投票 这将使人们无法再投票,并将显示投票的最终结果。 结束此投票? @@ -2539,4 +2539,7 @@ 小时 - 一些用户已被取消忽略 初始同步请求 + + %1$s 与 %2$d 其他人 + \ No newline at end of file From 6671a100adb214ab160edd45139a5b5449599f7a Mon Sep 17 00:00:00 2001 From: phardyle Date: Sun, 31 Jul 2022 18:30:51 +0000 Subject: [PATCH 003/114] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 89 +++++++++++-------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index ab88307fc0..ef4209a590 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -193,7 +193,7 @@ 登出 搜索 发送文件 - 通话结束 + 通话已结束 继续 加入 拒绝 @@ -420,12 +420,12 @@ 黑色主题 通知声音 使用12小时制显示时间戳 - 你确定要删除这个挂件吗? + 确定要从此房间删除此挂件吗? 无法创建挂件。 发送请求失败。 权力级别必须是正整数。 你不在这个房间。 - 你没有在当前房间中执行此操作的权限。 + 你没权限在当前房间执行此操作。 请求中缺失 room_id。 请求中缺失 user_id。 房间 %s 不可见。 @@ -470,14 +470,14 @@ %d个成员 - %d 条未读消息 + %d条未读的已通知消息 成员 %d 个房间 - 已启用 %d 个挂件 + %d个启用的挂件 主页 房间 @@ -504,7 +504,7 @@ 你目前没有启用任何贴纸包。 \n \n要添加一些吗? - 缺少所需的参数。 + 缺少一个必需参数。 要想继续使用主服务器 %1$s 你必须阅读并同意其服务条款。 现在阅读 下载 @@ -587,7 +587,7 @@ FCM令牌已成功注册至主服务器。 未能将FCM令牌注册到主服务器: \n %1$s - 调用系统相机应用而非使用 ${app_name} 内置的相机界面。 + 启动系统相机而非自定义的相机屏幕。 开机时启动 启用开机时启动 检查后台限制 @@ -760,12 +760,12 @@ 播放快门声 标记为已读 - %1$s: %2$d 条消息 + %1$s:%2$d条消息 %d 条通知 - 新活动 + 新事件 房间 新消息 新邀请 @@ -812,11 +812,11 @@ 无法预览此房间 房间 创建 - 房间名称 + 名称 Matrix SDK 版本 通用 - 选项 - 隐私安全 + 偏好 + 安全与隐私 推送规则 尚未定义任何推送规则 没有已注册的推送通道 @@ -905,16 +905,16 @@ %d 个封禁用户 成功导出密钥 - %1$s: %2$s - %1$s: %2$s %3$s + %1$s:%2$s + %1$s:%2$s %3$s 查看 活动挂件 挂件 载入挂件 此挂件添加者: - 使用它会设置 cookie 并与 %s 分享数据: - 使用它会与 %s 分享数据: - 无法载入挂件。 + 使用它可能会设置cookie并与%s分享数据: + 使用它可能会与%s分享数据: + 载入挂件失败。 \n%s 重载挂件 在浏览器中打开 @@ -956,7 +956,7 @@ 任何人都可以加入此房间 获取信任信息时发生错误 获取密钥备份数据时发生错误 - 从文件 \"%1$s\" 导入端对端密钥。 + 从文件“%1$s”导入端到端密钥。 其他第三方通知 你已经在查看此房间! 注册令牌 @@ -1319,7 +1319,7 @@ %d 个活动会话 - 验证此登录 + 验证此设备 使用现有会话来验证此会话,并授予其访问加密消息的权限。 验证 已验证 @@ -1335,7 +1335,7 @@ 重置密钥 二维码 快要完成了!%s 显示对勾了吗? - 使 + 到服务器的连接已丢失 飞行模式已打开 @@ -1901,7 +1901,7 @@ 邀请至 %s 分享链接 通过电子邮件进行邀请 - 此刻仅有你自己。%s 与他人一道会更好。 + 此刻只有你。%s与他人一道会更好。 邀请至 %s 邀请人们 邀请人们加入你的空间 @@ -1925,7 +1925,7 @@ 我和伙伴 用于整理你房间的私有空间 仅我 - 确保对的人可以访问 %s。 + 确保正确的人可以访问%s。 你与谁一同工作? 要加入现有空间,你需要获得邀请。 你可以稍后更改 @@ -1974,7 +1974,7 @@ 你正在使用空间的测试版。你的反馈将有助于改善下一版本。我们将会记录你的平台和用户名以帮助我们尽我们所能多发挥你的反馈的作用。 反馈 空间反馈 - 离开当前的回忆并切换至其他会议? + 离开当前会议并切换至另一个? 抱歉,在尝试加入会议时发生了错误 所有在此空间中的人都可以找到并加入它。仅有此房间的管理员可以将其添加到空间中。 仅空间成员 @@ -2127,7 +2127,7 @@ 视频通话被拒绝 语音通话被拒绝 视频通话已结束 • %1$s - 语音通话结束了 • %1$s + 语音通话已结束 • %1$s 视频来电 语音来电 你拒绝了此通话 @@ -2274,7 +2274,7 @@ 修改服务器 %d 的 ACLs - Thread帮助你的对话不离题且易于跟踪。 + 消息列帮助你的对话不离题且易于跟踪。 显示当前房间的所有子区 所有子区 筛选器 @@ -2310,7 +2310,7 @@ 缩放到当前位置 地图上选定位置的图钉 无投票 - 检查你的电子邮件以验证。 + 验证你的电子邮件 %d条消息已移除 @@ -2374,9 +2374,9 @@ 位置 分享位置 结果仅在你结束投票后展示 - 已关闭的投票 + 封闭式投票 投票者一投票就能看到结果 - 开启投票 + 开放式投票 投票类型 编辑投票 结果将在投票结束时可见 @@ -2386,7 +2386,7 @@ 添加用户资料图片 重发电子邮件 没有收到电子邮件? - 要确认你的电子邮件地址,点击我们刚刚寄到%s的电子邮件里的按钮 + 跟随发送到%s的说明 重新发送验证码 代码已发送到%s 确认你的电话号码 @@ -2430,7 +2430,7 @@ 安全传送消息。 向主服务器注册端点token失败: \n%1$s - Threads帮助保持你的对话不离题且易于跟踪。%s启用thread会刷新应用。这对一些账户可能需要更长时间。 + Threads帮助保持你的对话不离题且易于跟踪。%s启用消息列会刷新应用。这对一些账户可能需要更长时间。 重启应用以使更改生效。 启用LaTeX数学 (%1$s) @@ -2442,7 +2442,7 @@ %1$s、%2$s、%3$s 显示最新用户信息 注意:应用将重启 - 启用thread消息 + 启用消息列消息 当无法解密的错误出现时,你的系统会自动发送日志 自动报告解密错误。 一些结果可能被隐藏,因为它们是私有的,你需要它们的邀请。 @@ -2495,11 +2495,11 @@ BETA 重设通知方式 资料标签: - 你已经在查看这个thread了! + 你已经在查看这个消息列了! 出发 - 在thread中回复 + 在消息列中回复 备份具有来自该用户的有效签名。 - 命令“%s”可被识别但在threads中不被支持。 + 命令“%s”可被识别但在消息列中不被支持。 使用系统默认值 手动选择 自动设置 @@ -2516,13 +2516,13 @@ 自动播放动画图片 端点成功注册到主服务器。 端点注册 - 你的主服务器当前不支持threads,所以此功能可能不可靠。Some threaded messages may not be reliably available. %s你仍要启用threads吗? + 你的主服务器当前不支持消息列,所以此功能可能不可靠。Some threaded messages may not be reliably available. %s你仍要启用消息列吗? Threads接近Beta了 🎉 - 来自thread + 来自消息列 实用提示:长按消息并使用“%s”。 - 使用threads来保持讨论的条理性 - 显示你参与的所有threads - 我的threads + 使用消息列来保持讨论的条理性 + 显示你参与的所有消息列 + 我的消息列s 加密被错误地配置了,所以你无法发送消息。点击以打开设置。 加密被错误地配置了,所以你无法发送消息。请联系管理员将加密还原到有效的状态。 %1$s、%2$s与其他人 @@ -2542,4 +2542,15 @@ %1$s 与 %2$d 其他人 + 正在更新你的数据…… + 自动批准Element通话组件,授予相机/麦克风权限 + 启用Element通话权限捷径 + 实时位置 + 这个QR码看起来不正常。请尝试用另一个方法验证。 + 你无法访问加密消息历史。重置你的安全消息备份和验证密钥以重新开始。 + 无法验证此设备 + 你的服务器地址是什么? + 你的对话发生的地方 + %1$s和%2$s + 电子邮件未确认,检查你的收件箱 \ No newline at end of file From 927b51413cfc216b537d90446c65d526fb38a36c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Aug 2022 11:57:41 +0000 Subject: [PATCH 004/114] Bump com.autonomousapps.dependency-analysis from 1.11.2 to 1.12.0 Bumps com.autonomousapps.dependency-analysis from 1.11.2 to 1.12.0. --- updated-dependencies: - dependency-name: com.autonomousapps.dependency-analysis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index afe51cc734..115b21a23d 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ plugins { id "io.gitlab.arturbosch.detekt" version "1.21.0" // Dependency Analysis - id 'com.autonomousapps.dependency-analysis' version "1.11.2" + id 'com.autonomousapps.dependency-analysis' version "1.12.0" } // https://github.com/jeremylong/DependencyCheck From b73c097f3c8733deff57a89d71e9e2ec04a74037 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 23:08:10 +0000 Subject: [PATCH 005/114] Bump dagger from 2.42 to 2.43.2 Bumps `dagger` from 2.42 to 2.43.2. Updates `hilt-android-gradle-plugin` from 2.42 to 2.43.2 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.43.2) Updates `dagger` from 2.42 to 2.43.2 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.43.2) Updates `dagger-compiler` from 2.42 to 2.43.2 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.43.2) Updates `hilt-android` from 2.42 to 2.43.2 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.43.2) Updates `hilt-android-testing` from 2.42 to 2.43.2 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.43.2) Updates `hilt-compiler` from 2.42 to 2.43.2 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.43.2) --- updated-dependencies: - dependency-name: com.google.dagger:hilt-android-gradle-plugin dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:dagger dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:dagger-compiler dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android-testing dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-compiler dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 93a62a548e..e319fd2629 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -14,7 +14,7 @@ def gradle = "7.1.3" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.6.21" def kotlinCoroutines = "1.6.4" -def dagger = "2.42" +def dagger = "2.43.2" def appDistribution = "16.0.0-beta03" def retrofit = "2.9.0" def arrow = "0.8.2" From 7bf8a419599b4a6851692e6a84536d5f244340a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 23:09:11 +0000 Subject: [PATCH 006/114] Bump flipper from 0.156.0 to 0.157.0 Bumps `flipper` from 0.156.0 to 0.157.0. Updates `flipper` from 0.156.0 to 0.157.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.156.0...v0.157.0) Updates `flipper-network-plugin` from 0.156.0 to 0.157.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.156.0...v0.157.0) --- updated-dependencies: - dependency-name: com.facebook.flipper:flipper dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.facebook.flipper:flipper-network-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 93a62a548e..8bef583874 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -22,7 +22,7 @@ def markwon = "4.6.2" def moshi = "1.13.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.156.0" +def flipper = "0.157.0" def epoxy = "4.6.2" def mavericks = "2.7.0" def glide = "4.13.2" From 825ba77bb2bf6bccd2f1ac0066e180e0bfdac83b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 4 Aug 2022 11:58:01 +0100 Subject: [PATCH 007/114] taking into account non ascii characters as invalid username error --- .../java/org/matrix/android/sdk/api/failure/Extensions.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index 6e198fb98c..68b931b33c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -62,7 +62,10 @@ fun Throwable.isUsernameInUse() = this is Failure.ServerError && error.code == MatrixError.M_USER_IN_USE fun Throwable.isInvalidUsername() = this is Failure.ServerError && - error.code == MatrixError.M_INVALID_USERNAME + (error.code == MatrixError.M_INVALID_USERNAME || usernameContainsNonAsciiCharacters()) + +private fun Failure.ServerError.usernameContainsNonAsciiCharacters() = error.code == MatrixError.M_UNKNOWN && + error.message == "Query parameter \'username\' must be ascii" fun Throwable.isInvalidPassword() = this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && From a4ea47e7409ba6e8eff1fe4a276fae8350f39fe6 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 4 Aug 2022 11:58:31 +0100 Subject: [PATCH 008/114] catching username availabilty exceptions and handling as user facing error --- .../onboarding/OnboardingViewModel.kt | 43 +++++++++++-------- .../onboarding/OnboardingViewModelTest.kt | 14 ++++++ .../app/test/fakes/FakeRegistrationWizard.kt | 4 ++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 8136dc379b..73288bd6d5 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -193,25 +193,32 @@ class OnboardingViewModel @AssistedInject constructor( } private suspend fun checkUserNameAvailability(userName: String) { - when (val result = registrationWizard.registrationAvailable(userName)) { - RegistrationAvailability.Available -> { - setState { - copy( - registrationState = RegistrationState( - isUserNameAvailable = true, - selectedMatrixId = when { - userName.isMatrixId() -> userName - else -> "@$userName:${selectedHomeserver.userFacingUrl.toReducedUrl()}" - }, - ) - ) - } - } + runCatching { registrationWizard.registrationAvailable(userName) }.fold( + onSuccess = { result -> + when (result) { + RegistrationAvailability.Available -> { + setState { + copy( + registrationState = RegistrationState( + isUserNameAvailable = true, + selectedMatrixId = when { + userName.isMatrixId() -> userName + else -> "@$userName:${selectedHomeserver.userFacingUrl.toReducedUrl()}" + }, + ) + ) + } + } - is RegistrationAvailability.NotAvailable -> { - _viewEvents.post(OnboardingViewEvents.Failure(result.failure)) - } - } + is RegistrationAvailability.NotAvailable -> { + _viewEvents.post(OnboardingViewEvents.Failure(result.failure)) + } + } + }, + onFailure = { + _viewEvents.post(OnboardingViewEvents.Failure(it)) + } + ) } private fun withAction(action: OnboardingAction, block: (OnboardingAction) -> Unit) { diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 61d3101b64..f734a62cf8 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -390,6 +390,20 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given available username throws, when a register username is entered, then emits error`() = runTest { + viewModelWith(initialRegistrationState(A_HOMESERVER_URL)) + fakeAuthenticationService.givenRegistrationWizard(FakeRegistrationWizard().also { it.givenUserNameIsAvailableThrows(A_USERNAME, AN_ERROR) }) + val test = viewModel.test() + + viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(A_USERNAME)) + + test + .assertStates(initialState) + .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) + .finish() + } + @Test fun `given available username, when a register username is entered, then emits available registration state`() = runTest { viewModelWith(initialRegistrationState(A_HOMESERVER_URL)) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt index 4f0b1fe083..6cacd6c1e5 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt @@ -57,6 +57,10 @@ class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) { coEvery { registrationAvailable(userName) } returns RegistrationAvailability.Available } + fun givenUserNameIsAvailableThrows(userName: String, cause: Throwable) { + coEvery { registrationAvailable(userName) } throws cause + } + fun givenUserNameIsUnavailable(userName: String, failure: Failure.ServerError) { coEvery { registrationAvailable(userName) } returns RegistrationAvailability.NotAvailable(failure) } From 9a97e0bf6143f3249453d40adf1c195e26aa301d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 4 Aug 2022 12:04:51 +0100 Subject: [PATCH 009/114] adding changelog entry --- changelog.d/6735.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6735.bugfix diff --git a/changelog.d/6735.bugfix b/changelog.d/6735.bugfix new file mode 100644 index 0000000000..814bf3f47c --- /dev/null +++ b/changelog.d/6735.bugfix @@ -0,0 +1 @@ +Fixes crash when entering non ascii characters during account creation From 68e55c01f9a1b7751a517bef204d459ae60cc7c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Aug 2022 23:09:28 +0000 Subject: [PATCH 010/114] Bump firebase-appdistribution-gradle from 3.0.2 to 3.0.3 Bumps firebase-appdistribution-gradle from 3.0.2 to 3.0.3. --- updated-dependencies: - dependency-name: com.google.firebase:firebase-appdistribution-gradle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index afe51cc734..b91af784d9 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ buildscript { classpath libs.gradle.gradlePlugin classpath libs.gradle.kotlinPlugin classpath libs.gradle.hiltPlugin - classpath 'com.google.firebase:firebase-appdistribution-gradle:3.0.2' + classpath 'com.google.firebase:firebase-appdistribution-gradle:3.0.3' classpath 'com.google.gms:google-services:4.3.13' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' From c2fbb74e4b1d88c74ce706f8d3048296370fc9e5 Mon Sep 17 00:00:00 2001 From: "Mr.Narsus" Date: Fri, 5 Aug 2022 10:20:32 +0000 Subject: [PATCH 011/114] Translated using Weblate (Arabic) Currently translated at 42.0% (979 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ar/ --- vector/src/main/res/values-ar/strings.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index aa28d9f481..1df5563686 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -97,14 +97,14 @@ • الخوادِم المُطابقة لـ %s أُزيلت مِن قائمة الحظر. • الخوادِم المُطابقة لـ %s محظورة الآن. • خوادِم مُطابقة IP الحرفية مسموحة الآن. - لقد قمت بتغيير قائمة الوصول لهذه الغُرفة. - قام %s بتغيير قائمة الوصول (ACL) لهذه الغُرفة. + لقد غَيَّرت قائمة الوصول لهذه الغُرفة. + غَيَّرَ %s قائمة التحكم بالوصول (ACL) لهذه الغُرفة. • الخوادِم المتطابقة من حيث بروتوكول الإنترنت (IP) المستخدم محظورة. • الخوادِم المُطابقة لـِ %s مسموحة. • الخوادِم المُطابقة لـ %s محظورة. الخوادِم المتطابقة من حيث بروتوكول الإنترنت (IP) المستخدم مسموحة. - لقد قمت بتعيين قائمة التحكم بالوصول لهذه الغُرفة. - %s قام بتعيين قائمة التحكم بالوصول لهذه الغرفة. + لقد عيَّنت قائمة التحكم بالوصول لهذه الغُرفة. + عيَّنَ %s قائمة التحكم بالوصول لهذه الغرفة. رقيتَ هُنا. رقّى %s هُنا. جعلتَ الرسائل المُستقبلية مرئية لـ %1$s @@ -895,11 +895,11 @@ %d ثانية العنوان - امسح التأريخ + امسح التاريخ لِج سجّل لج عبر %1$s - اتص بخادم مخصص + اتصل بخادم مخصص اتصل بخدمات مايتركس لـ Element اتصل بـ %1$s تابع @@ -1168,4 +1168,9 @@ طَلَبُ مُزامَنة أوَّلِيّ غُرفة عامة تسجيل مرئيّ + هذا البريد الإلكتروني غير مربوط بأي حساب + تابع + البريد الإلكتروني + كلمة السر الجديدة + التالي \ No newline at end of file From f33e2fd656a8a3cee90fcc2aecf0fb8fc72d4733 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Tue, 2 Aug 2022 17:54:39 +0000 Subject: [PATCH 012/114] Translated using Weblate (Catalan) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ca/ --- vector/src/main/res/values-ca/strings.xml | 25 ++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index f224c80aab..f7366b7dd3 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -1277,7 +1277,7 @@ Consulta els termes de %s Accepta els termes per a continuar Estàs compartint les adreces de correu electrònic o números de telèfon amb el servidor d\'identitat %1$s. Per parar de compartir-les t\'has de tornar a connectar a %2$s. - Visualitza Edita Històric + Visualitza l\'històric d\'edició Indica els missatges eliminats Clau de recuperació de la còpia de seguretat de claus utilitzar la clau de recuperació de còpia de seguretat de claus @@ -1777,7 +1777,7 @@ Verifica el nou inici de sessió que està accedint al teu compte: %1$s Utilitza aquesta sessió per a verificar-ne una de nova i poder-li donar accés als missatges xifrats. Fins que aquest usuari no confiï en aquesta sessió, els missatges enviats i rebuts es marcaran amb una alerta. Com a alternativa, pots verificar-lo manualment. - Verifica aquest inici de sessió + Verifica aquest dispositiu Verifica aquesta sessió per fer-la de confiança i permetre que accedeixi als missatges xifrats. Si no has estat tu el que ha iniciat sessió aquí, pot ser que el teu compte estigui compromès: Verifica aquesta sessió Alguna de les següents pot haver estat compromesa: @@ -2228,7 +2228,7 @@ El fitxer és massa gran per carregar-lo. Ubicació Xifrat d\'extrem a extrem i sense haver de donar cap número de telèfon. Sense anuncis ni extracció de dades. - Tria on es desen les teves converses, donant-te control i independència. Connectat a través de Matrix. + Tria on es desen les teves converses, et dona control i independència. Connectat a través de Matrix. Comunicació segura i independent que t\'ofereix el mateix nivell de privadesa que una conversa cara a cara a casa teva. Missatgeria pel teu equip. Missatgeria segura. @@ -2535,7 +2535,7 @@ Envia un primer missatge per convidar %s a parlar Els missatges d\'aquest xat seran xifrats d\'extrem a extrem. Crea - Confirma el teu correu electrònic, prem el botó del correu que t\'hem enviat a %s + Segueix les instruccions enviades a %s Si us plau, llegeix les condicions i polítiques de %s Contacta Et podran trobar com a %s @@ -2547,7 +2547,7 @@ Usuari / Correu / Telèfon No es pot obrir l\'enllaç: les comunitats han estat substituïdes pels espais No tens permís per compartir la ubicació en directe - Comprova el teu correu per verificar. + Verifica el teu correu Torna a enviar el codi S\'ha enviat un codi al %s Confirma el teu número de telèfon @@ -2588,4 +2588,19 @@ Restabliment de contrasenya Contrasenya oblidada Element Matrix Services (EMS) és un servei robust i fiable d\'allotjament (servidors) per comunicacions ràpides, segures i en temps real. Descobreix-ho a <a href=\"${ftue_ems_url}\">element.io/ems</a> + No podràs accedir a l\'històric de missatges xifrats. Restableix la còpia de seguretat de missatges i les claus de verificació per començar de nou. + Aprova automàticament els ginys d\'Element Call i permet l\'accés a la càmera i el micròfon + Activa els permisos d\'accés directe d\'Element Call + Ubicació en directe + Aquest codi QR sembla incorrecte. Prova de fer la verificació amb un altre mètode. + No s\'ha pogut verificar aquest dispositiu + Quina és l\'adreça del teu servidor\? + On viuen les teves converses + Actualitzant dades… + + %1$s i %2$d altre + %1$s i %2$d altres + + %1$s i %2$s + Correu no verificat, mira la teva safata d\'entrada \ No newline at end of file From a56d8a23f58d91d44242f6a49002ae9c2f1455b7 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Fri, 29 Jul 2022 04:50:39 +0000 Subject: [PATCH 013/114] Translated using Weblate (Czech) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index a9f177128f..5733fde468 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -1321,7 +1321,7 @@ %d aktivní relace %d aktivních relací - Ověřit tuto relaci + Ověřit toto zařízení Pro ověření této relace použijte existující relaci, a tím ji udělíte přístup k zašifrovaným zprávám. Ověřit Ověřeno @@ -2599,8 +2599,8 @@ Zapomenuté heslo Znovu poslat e-mail Nepřišel vám e-mail\? - Pro potvrzení e-mailu klepněte na tlačítko ve zprávě, kterou jsme právě odeslali na adresu %s - Pro ověření si zkontrolujte svůj e-mail. + Postupujte podle pokynů zaslaných na adresu %s + Ověřte svůj e-mail Znovu odeslat kód Kód byl odeslán na %s Potvrďte své telefonní číslo @@ -2637,4 +2637,20 @@ Zvolit ručně Nastavit automaticky Volba velikosti písma + Automaticky schvalovat widgety Element hovorů a udělit přístup ke kameře/mikrofonu + Zapnout zkratky pro povolení Element hovorů + Poloha živě + Tento kód QR vypadá chybně. Zkuste provést ověření jinou metodou. + Nebudete mít přístup k historii zašifrovaných zpráv. Obnovte zálohování zabezpečených zpráv a ověřovací klíče a začněte znovu. + Nepodařilo se ověřit toto zařízení + Jaká je adresa vašeho serveru\? + Kde žijí vaše konverzace + Aktualizace vašich dat… + + %1$s a %2$d další + %1$s a %2$d další + %1$s a %2$d dalších + + %1$s a %2$s + E-mail nebyl ověřen, zkontrolujte si schránku \ No newline at end of file From 4af4f3f88ca01c7e8fbf8199ed40ef904ac3754b Mon Sep 17 00:00:00 2001 From: Vri Date: Fri, 5 Aug 2022 12:03:45 +0000 Subject: [PATCH 014/114] Translated using Weblate (German) Currently translated at 98.6% (2296 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 72 +++++++++++++++++++---- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index fcb82889f5..fa42cc44a1 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -1092,7 +1092,7 @@ E-Mail Neues Passwort Achtung! - Eine Änderung deines Passworts wird alle Ende-zu-Ende-Schlüssek zurücksetzen. Dein verschlüsselter Chatverlauf wird dadurch unlesbar. Richte die Schlüsselsicherung ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzt. + Eine Änderung deines Passworts wird alle Ende-zu-Ende-Schlüssel zurücksetzen. Dein verschlüsselter Chatverlauf wird dadurch unlesbar. Richte die Schlüsselsicherung ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzt. Fortfahren Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft Prüfe deinen Posteingang @@ -1297,7 +1297,7 @@ Eine aktive Sitzung %d aktive Sitzungen - Verifiziere diese Sitzung + Verifiziere dieses Gerät Nutze eine vorhandene Sitzung um diese Sitzung zu verifizieren und ihr Zugriff auf verschlüsselte Nachrichten zu gewähren. Verifizieren Verifiziert @@ -1520,7 +1520,7 @@ Backup Absicherung gegen den Verlust verschlüsselter Nachrichten Richte Backup ein - Nachricht gelöscht + Nachricht entfernt Gelöschte Nachrichten zeigen Zeigt einen Platzhalter für gelöschte Nachrichten an Dedizierten Tab für ungelesene Nachrichten zur Hauptansicht hinzufügen @@ -2367,7 +2367,7 @@ Möchtest du einem existierenden Server beitreten\? Communities Teams - Wir helfen dir, in Verbindung zu kommen. + Wir helfen dir, in Verbindung zu kommen Mit wem wirst du am meisten chatten\? Link zu Thread kopieren Threads anzeigen @@ -2375,8 +2375,8 @@ Laden der Karte fehlgeschlagen Karte Hinweis: App wird neugestartet - diese Frage überspringen - Noch nicht sicher\? Du kannst %s + Diese Frage überspringen + Noch nicht sicher\? %s Freunde und Familie In Thread antworten Aus einem Thread @@ -2471,7 +2471,7 @@ %1$s, %2$s, %3$s Die neuesten Profilinformationen (Avatar und Anzeigename) für alle Nachrichten anzeigen. Aktuelle Benutzerinformationen anzeigen - Du kannst loslegen! + Sieht gut aus! einen Anzeigenamen wählen Zurück zum Home-Screen Threads Beta-Feedback @@ -2516,17 +2516,67 @@ teilten ihren Live-Standort Schritt überspringen Speichern und fortfahren - Deine Einstellungen wurden gespeichert. + Öffne die Einstellungen jederzeit um dein Profil zu aktualisieren Los geht\'s - Du kannst das jederzeit ändern. + Zeit, dem Namen ein Gesicht zu geben Profilbild hinzufügen Du kannst dies später ändern Anzeigename Dies wird angezeigt, wenn Du Nachrichten sendest. - Dein Konto %s wurde erstellt. + Dein Konto %s wurde erstellt Herzlichen Glückwunsch! Profil personalisieren ${app_name} ist auch für den Arbeitsplatz geeignet. Die sichersten Organisationen der Welt vertrauen darauf. Threads sind noch in Arbeit, und es stehen neue, aufregende Funktionen an, wie z. B. verbesserte Benachrichtigungen. Wir würden uns sehr über Dein Feedback freuen! - Nachrichten in diesem Chat werden End-zu-End-Verschlüsselt + Nachrichten in diesem Chat werden Ende-zu-Ende-verschlüsselt. + Bist du ein Mensch\? + Bitte lies dir %ss Bedingungen und Richtlinien durch + Server-Richtlinien + Folge den Anweisungen, die an %s gesendet wurden + E-Mail bestätigen + Ergebnisse sind nach Beenden der Abstimmung sichtbar + Prüfe deine E-Mails. + Passwort zurücksetzen + Gib mindestens 8 Zeichen ein. + E-Mail-Adresse + Telefonnummer + Erneut senden + %s wird dir einen Bestätigungslink schicken + Deine E-Mail-Adresse + Neues Passwort + Wähle ein neues Passwort + Alle Geräte abmelden + Bestätige deine Telefonnummer + Keine E-Mail erhalten\? + Folge den Anweisungen, die an %s gesendet wurden + Kann Link nicht öffnen: Communities wurden durch Spaces ersetzt + MSC3061: Raumschlüssel für vorherige Nachrichten teilen + Beim Einladen in einen Raum mit sichtbarem Verlauf wird der verschlüsselte Verlauf sichtbar sein. + Live-Standort + + %d Nachricht gelöscht + %d Nachrichten gelöscht + + Keine Element Call-Berechtigungsabfragen + Bestätige automatisch Element Call-Widgets und erlaube Kamera- und Mikrofonzugriff + Los + ändern + oder + Das Zuhause deiner Gespräche + Das zukünftige Zuhause für deine Gespräche + Systemstandard nutzen + Automatisch festlegen + Schriftgröße wählen + Manuell wählen + %1$s und %2$s + E-Mail nicht bestätigt, prüfe deinen Posteingang + Willkommen zurück! + Passwort vergessen + Benutzername / E-Mail / Telefon + Erstelle dein Konto + Serveradresse + Wie lautet die Adresse deines Servers\? Das wird eine Art Zuhause für deine Daten + Wie lautet die Adresse deines Servers\? + Muss 8 oder mehr Zeichen umfassen + Wähle deinen Server \ No newline at end of file From 7b96597d569f7d48e14b194fa22100d4de8cbe45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 30 Jul 2022 19:59:00 +0000 Subject: [PATCH 015/114] Translated using Weblate (Estonian) Currently translated at 99.6% (2319 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 45d8a1f96e..124ca1eae7 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -720,7 +720,7 @@ %d aktiivne sessioon %d aktiivset sessiooni - Verifitseeri see sisselogimissessioon + Verifitseeri see seade Kasuta olemasolevat sessiooni selle sessiooni verifitseerimiseks, andes sellega ligipääsu krüptitud sõnumitele. Verifitseeri Verifitseeritud @@ -2541,8 +2541,8 @@ Kas unustasid oma salasõna Saada e-kiri uuesti Sa ei saanud e-kirja kätte\? - Oma e-posti aadressi kinnitamiseks klõpsi selles kirjas leiduvat nuppu, mille just saatsime %s aadressile - Kinnitamiseks vaata oma e-kirju. + Palun järgi juhtnööre, mille just saatsime %s aadressile + Verifitseeri oma e-posti aadress Saada kinnituskood uuesti Kinnituskoodi saatsime telefoninumbrile %s Kinnita oma telefoninumber @@ -2579,4 +2579,19 @@ Vali ise Määra automaatselt Vali kirjatüübi suurus + Automaatsel luba kasutada Element\'i põhiste kõnede vidinaid ning luba ligipääs kaamerale ja mikrofonile + Võta kasutsele Element\'i põhiste kõnede õiguste kiirnupud + Asukoht reaalajas + See QR-kood tundub olema vigane. Palun proovi verifitseerimist mõne muu meetodiga. + Sa ei saa lugeda varasemaid krüptitud sõnumeid. Uuesti alustamiseks seadista varundusvõtmed ja verifitseerimisvõtmed. + Selle seadme verifitseerimine ei õnnestunud + Mis on sinu koduserveri aadress\? + Kuidas sinu vestlusi hallatakse + Sinu andmed on uuendamisel… + + %1$s ja veel %2$d + %1$s ja veel %2$d muud + + %1$s ja %2$s + E-posti aadress on kinnitamata, palun vaata oma saabunud e-kirju \ No newline at end of file From dbbdc1791a859e5b14d9fb199201ef29fad65292 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Thu, 28 Jul 2022 13:46:05 +0000 Subject: [PATCH 016/114] Translated using Weblate (Persian) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 26a006f498..591ae21198 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -982,7 +982,7 @@ ورود چندگانه به کار نیفتاده مدیر کارسازتان رمزنگاری سرتاسری پیش‌گزیده را در اتاق‌های خصوصی و پیام‌های مستقیم از کار انداخته است. خروج از این نشست - تأیید این ورود + تأیید این افزاره استفاده از نشستی موجود برای تأییدش که به پیام‌های رمزشده دسترسی می‌دهد. تأیید تأیید‌شده @@ -2550,8 +2550,8 @@ فراموشی گذرواژه فرستادن دوبارهٔ رایانامه رایانامه‌ای نگرفتید؟ - برای تأیید رایانامه‌تان، روی دکمه در رایانامه‌ای‌که اکنون برایتان به %s فرستادیم بزنید - برای تأیید، رایانامه‌تان را بررسی کنید. + دستورالعمل‌های فرستاده شده به %s را دنبال کنید + رایانامه‌تان را تأیید کنید فرستادن دوبارهٔ رمز رمزی به %s فرستاده خواهد شد شمارهٔ تلفنتان را تأیید کنید @@ -2588,4 +2588,19 @@ گزینش دستی تنظیم خودکار گزینش اندازهٔ قلم + تأیید خودکار ابزارک‌های تماس و اعطای دسترسی دوربین و میکروفون + به کار انداخت میان‌برهای اجازهٔ تماس المنت + مکان زنده + این رمز QR بدریخت به نظر می‌رسد. لطفاً تأیید با روشی دیگر را بیازمایید. + نخواهید توانست به تاریخچهٔ پیام رمزشده دست یابید. برای آغاز تازه، کلیدهای تأیید و پشتیبان پیام امنتان را بازنشانی کنید. + ناتوان در تأیید این افزاره + نشانی کارسازتان چیست؟ + زیستگاه گفت‌وگوهایتان + به‌روز رساندن داده‌هایتان… + + %1$s و ۱ والد دیگر + %1$s و %2$d والد دیگر + + %1$s و %2$s + رایانامه تأیید نشده. صندوق ورودیتان را بررسی کنید \ No newline at end of file From 4a3b6baaf71a23e5f670739dc4643cc76add06a2 Mon Sep 17 00:00:00 2001 From: Glandos Date: Fri, 29 Jul 2022 10:47:20 +0000 Subject: [PATCH 017/114] Translated using Weblate (French) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 1039a66f32..07849141b6 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1273,7 +1273,7 @@ %d session active %d sessions actives - Vérifier cette session + Vérifier cet appareil Utilisez une session existante pour vérifier celle-ci, ce qui lui permettra d’avoir accès aux messages chiffrés. Vérifier Vérifié @@ -2550,8 +2550,8 @@ Mot de passe oublié Renvoyer le courriel Pas reçu de courriel \? - Pour confirmer votre courriel, appuyez sur le bouton dans le courriel que nous venons d’envoyer à %s - Relevez vos courriels pour la vérification. + Suivez les instructions envoyées à %s + Vérifiez votre courriel Renvoyer le code Un code a été envoyé au %s Confirmer votre numéro de téléphone @@ -2588,4 +2588,19 @@ Choisir manuellement Automatique Choisir la taille de la police + Approuve automatiquement les widgets de Element Call et leur donner l’accès au micro et à la caméra + Activer les raccourcis de permission de Element Call + Position en direct + Ce QR code semble incorrect. Veuillez réessayer la vérification avec une autre méthode. + Vous ne pourrez plus accéder à l’historique des messages chiffrés. Réinitialiser votre sauvegarde sécurisée des messages et vos clés de récupération pour un nouveau départ. + Impossible de vérifier cet appareil + Quelle est l’adresse de votre serveur \? + Où seront vos conversations + Mise-à-jour de vos données… + + %1$s et %2$d autre + %1$s et %2$d autres + + %1$s et %2$s + Courriel non vérifié, relevez votre boîte de réception \ No newline at end of file From 8cbdd6e13095af3703e8af979dce325a04c6928c Mon Sep 17 00:00:00 2001 From: notramo Date: Wed, 3 Aug 2022 20:28:14 +0000 Subject: [PATCH 018/114] Translated using Weblate (Hungarian) Currently translated at 98.8% (2301 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- vector/src/main/res/values-hu/strings.xml | 104 +++++++++++++++------- 1 file changed, 72 insertions(+), 32 deletions(-) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index dc9e17fc75..33c0044843 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -1064,9 +1064,9 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Az alkalmazás nem tud bejelentkezni a Matrix szerverbe. A Matrix szerver ezeket a bejelentkezési módokat támogatja: %1$s. \n \nWeb klienssel szeretnél bejelentkezni\? - Az alkalmazás nem tud fiókot készíteni ezen a Matrix szerveren. + Ez az alkalmazás nem tudott fiókot készíteni ezen a Matrix szerveren. \n -\nWeb klienssel szeretnél bejelentkezni\? +\nSzeretnél a webes kliens segítségével létrehozni egy fiókot\? Koppints a linkre az új jelszó megerősítéséhez. Miután követted a linket, kattints alább. Állíts be egy e-mail címet a fiókod visszaállításához. Később esetleg engedélyezheted, hogy ismerősök e-mail címmel megtalálhassanak. Nemzetközi telefonszámnak „+” jellel kell kezdődnie @@ -1077,7 +1077,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Következő A felhasználónév már használatban van Figyelmeztetés - A felhasználói fiókod még nincs kész. Megállítód a regisztrációt\? + A felhasználói fiókod még nincs kész. Félbehagyod a regisztrációt\? matrix.org kiválasztása Element Matrix Services kiválasztása Egyedi matrix szerver kiválasztása @@ -2318,7 +2318,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze A változások életbelépéséhez indítsd újra az alkalmazást. LaTeX matematikai szintaxis engedélyezése Nem léphetsz be ebbe a szobába - Birtokold a beszélgetéseid. + Vedd birtokba a beszélgetéseid. Titkosítás visszafejtési hiba esemény alkalmával a rendszer automatikusan elküldi a logokat Titkosítás visszafejtési hibák automatikus jelentése. Szavazás létrehozása @@ -2330,7 +2330,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Megjelenítendő név színének megváltoztatása Már van fiókom Biztonságos üzenetküldés. - Te irányítasz. + Tiéd az irányítás. Tartózkodási hely megosztása Megnyitás ezzel Az ${app_name} nem fér hozzá a tartózkodási helyedhez. Próbáld újra később. @@ -2351,9 +2351,9 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze A földrajzi helyzetüket megosztották Fiók létrehozása Üzenetküldés a csoportodnak. - Telefonszám nélkül végpontok között titkosított. Reklámok és adatbányászat nélkül. - Válaszd meg hol legyenek a beszélgetéseid tárolva, visszaadja az irányítást és függetlenné tesz. Csatlakozva a Matrixhoz. - Biztonságos és független kommunikáció ami olyan biztonságos mintha valakivel négyszemközt beszélgetnél a házadban. + Végponti tikosítással és telefonszám nélküli regisztrációval. Reklámok és adatbányászat nélkül. + Te választhatod ki, hogy hol legyenek a beszélgetéseid tárolva, ezáltal visszadva az irányítást és a függetlenséget neked. A Matrix hálózathoz csatlakozva. + Biztonságos és független kommunikáció, ami olyan biztonságos mint ha valakivel négyszemközt beszélgetnél a házadban. Földrajzi helyzet A titkosítás beállítása hibás így nem lehet üzenetet küldeni. Kattints a beállításokért. A titkosítás beállítása hibás így nem lehet üzenetet küldeni. Kérjük vedd fel a kapcsolatot az adminisztrátorral a titkosítás helyreállításához. @@ -2367,9 +2367,9 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Kihagyhatod ezt a kérdést. Még nem tudod\? %s Közösségek - Csoportok + Munkahelyi csoportok Barátok és család - Segítünk a kapcsolatteremtésben. + Segítünk a kapcsolatteremtésben Kikkel fogsz legtöbbet beszélgetni\? Jelenleg ezt az üzenetszálat olvasod! Megjelenítés szobában @@ -2424,17 +2424,17 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze ${app_name} Folyamatos helymeghatározás A matrix szerver nem fogad el olyan felhasználói nevet ami csak számokból áll. Lépés kihagyása - Mentés és tovább - A beállítások elmentve. - Minden kész! + Mentés és folytatás + A beállításokban bármikor megváltoztathatod a profilod adatait + Jónak tűnik! Gyerünk - Bármikor megváltoztatható. + Itt az ideje egy profilképet adni a fiókhoz Profilkép hozzáadása Ezt később meg lehet változtatni Megjelenítendő név Ez fog megjelenni amikor üzenetet küldesz. - Válassz egy megjelenítési nevet - A fiókod elkészült: %s. + Válassz egy megjelenítendő nevet + A fiókod elkészült. A Matrix címed: %s Gratulálunk! A kezdőlapra Profil személyre szabása @@ -2499,35 +2499,35 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze mperc perc ó - Földrajzi hely megosztás engedélyezése - Figyelem: ez a labor lehetőség egy átmeneti megvalósítás. Ez azt jelenti, hogy a szobába már elküldött helyadatok az élő hely megosztás leállítása után is hozzáférhetők maradnak a szobában. - Élő földrajzi hely megosztása + Földrajzi hely megosztásának engedélyezése + Figyelem: ez a labs lehetőség egy átmeneti megvalósítás. Ez azt jelenti, hogy nem lehet utólag törölni a megosztott helyadatokat. A technikailag hozzáértő szoba tagok a helyzetmegosztás leállítása után is meg tudják nézni az útvonalat. + Folyamatos helyzetmegosztás Jelenlegi átjáró: %s Átjáró (gateway) Nem található végpont. Jelenlegi végpont: %s Végpont Jelenleg használatban: %s. - Metódus + Mód - %d beállítás található. - %d beállítás található. + %d mód található. + %d mód található. - A háttér szinkronizációs szolgáltatástól eltérő beállítási lehetőség nem érhető el. - A Google Play szolgáltatástól eltérő beállítási lehetőség nem érhető el. - Elérhető beállítások - Értesítési beállítások + A háttér szinkronizációs szolgáltatáson kívül nem található más mód. + A Google Play szolgáltatásokon kívül nem található más mód. + Elérhető módok + Értesítési módok Szinkronizálás a háttérben Google szolgáltatások Válaszd ki, hogyan szeretnél értesítéseket kapni - Az eredmény a szavazás végeztével válik láthatóvá - Ha olyan titkosított szobába hívsz meg valakit ahol a régi üzenetek megosztása engedélyezett a régi titkosított üzenetek is láthatóak lesznek a meghívott számára. + Az eredmény a szavazás lezárása után válik láthatóvá + Ha olyan titkosított szobába hívsz meg valakit ahol a régi üzenetek megosztása be van kapcsolva, akkor a meghívott felhasználó el fogja tudni olvasni a meghívása előtt küldött üzeneteket is. MSC3061: Szoba kulcsok megosztása a régi üzenetekhez - A biometrikus azonosítást nem lehet engedélyezni. - A biometrikus azonosítás kikapcsolásra került mivel egy új biometrikus azonosítási metódus került hozzáadásra. Újra engedélyezheted a Beállításokban. - Az első üzeneteddel hívd meg ide őt: %s + Nem sikerült bekapcsolni a biometrikus azonosítást. + A biometrikus azonosítás kikapcsolásra került mivel egy új biometrikus azonosítási mód került hozzáadásra. Újra engedélyezheted a Beállításokban. + A meghívó elküldéséhez küldj %s felhasználónak egy üzenetet Az üzenetek ebben a beszélgetésben végpontok közötti titkosítással lesznek védve. - Értesítési metódus visszaállítása + Értesítési mód visszaállítása Profil címke: Menj Végpont token regisztrációja sikertelen a Matrix-kiszolgálón: @@ -2535,4 +2535,44 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Végpont sikeresen regisztrálva lett a matrix szerveren. Végpont regisztráció Következő + Felhasználónév / e-mail / telefonszám + Valódi személy vagy\? + Kövesd az útmutatást, amit ide küldtünk: %s + Jelszó visszaállítás + Elfelejtett jelszó + E-mail újraküldése + Nem kaptad meg az e-mailt\? + Kövesd az útmutatást, amit ide küldtünk: %s + E-mail ellenőrzése + Kód újraküldése + Egy kódot küldtünk ide: %s + Telefonszám megerősítése + Összes eszközöm kijelentkeztetése + Jelszó visszaállítása + Bizonyosodj meg róla, hogy minimum 8 karakter hosszú. + Megerősítő kód + Telefonszám + A %s szervernek ellenőriznie kell a fiókod + Add meg a telefonszámod + E-mail + A %s szervernek ellenőriznie kell a fiókod + Add meg az e-mail címed + Kérünk, olvasd el a %s szerver felhasználási feltételeit + Szerver szabályok + Kapcsolatfelvétel + Az Element Matrix Services (EMS) egy robosztus és megbízható szerver üzemeltetési szolgáltatás a gyors és biztonságos kommunikáció céljaira. Tudj meg többet itt: element.io/ems + Szeretnél egy saját szervert\? + Szerver URL + Mi a szervered címe\? + Válassz szervert + Üdv újra! + Szerkesztés + Ahol a beszélgetéseid tárolva vannak + vagy + Minimum 8 karakterből kell álljon + Mások ezen a címen tudnak írni neked: %s + Fiók létrehozása + Itt lesznek tárolva a beszélgetéseid + Mi a szervered címe\? Itt lesz tárolva az összes üzeneted + Az email cím nem lett ellenőrizve, kérlek nézd meg a beérkező email-jeidet \ No newline at end of file From 804880cc9c0fd42c272f406812fe5993173d2184 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 1 Aug 2022 13:05:27 +0000 Subject: [PATCH 019/114] Translated using Weblate (Italian) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 6dbdffd27e..46bb5453a8 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -1289,7 +1289,7 @@ %d sessione attiva %d sessioni attive - Verifica questo accesso + Verifica questo dispositivo Usa una sessione esistente per verificare questa, dandole l\'accesso ai messaggi criptati. Verifica Verificato @@ -2541,8 +2541,8 @@ Password dimenticata Reinvia email Non hai ricevuto un\'email\? - Per confermare l\'email, tocca il pulsante nell\'email che abbiamo appena inviato a %s - Controlla l\'email per verificare. + Segui le istruzioni inviate a %s + Verifica l\'email Reinvia codice È stato inviato un codice a %s Conferma il tuo numero di telefono @@ -2579,4 +2579,19 @@ Scegli manualmente Imposta automaticamente Scegli dimensione caratteri + Auto-approva i widget di Element Call e consenti accesso a fotocamera / microfono + Attiva scorciatoie di autorizzazione di Element Call + Posizione in tempo reale + Questo codice QR sembra sbagliato. Prova la verifica con un altro metodo. + Non potrai accedere alla cronologia dei messaggi cifrati. Reimposta il backup dei messaggi sicuri e le chiavi di verifica per ricominciare. + Impossibile verificare questo dispositivo + Qual è l\'indirizzo del tuo server\? + Dove vivono le tue conversazioni + Aggiornamento dei tuoi dati… + + %1$s e %2$d altro + %1$s e altri %2$d + + %1$s e %2$s + Email non verificata, controlla la posta in arrivo \ No newline at end of file From 721c4c0d9c7bc64ccc2a5f732cc75f671f89ad19 Mon Sep 17 00:00:00 2001 From: Johan Smits Date: Thu, 4 Aug 2022 06:15:58 +0000 Subject: [PATCH 020/114] Translated using Weblate (Dutch) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nl/ --- vector/src/main/res/values-nl/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index 2c66e51960..a79f5e7aee 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -2000,7 +2000,7 @@ Deze sessie wordt vertrouwd voor veilig berichtenverkeer omdat %1$s (%2$s) deze heeft geverifieerd: Kan geen sessies ophalen Gebruik een bestaande sessie om deze te verifiëren en deze toegang te verlenen tot versleutelde berichten. - Verifieer deze login + Verifieer dit apparaat %d actieve sessie %d actieve sessies @@ -2550,8 +2550,8 @@ Wachtwoord vergeten Email opnieuw verzenden Geen e-mail ontvangen\? - Om uw e-mailadres te bevestigen, tikt u op de knop in de e-mail die we zojuist naar %s hebben gestuurd - Controleer uw e-mail om te verifiëren. + Volg de instructies die naar %s zijn gestuurd + Verifieer uw e-mailadres Code nogmaals versturen Er is een code verzonden naar %s Bevestig uw telefoonnummer @@ -2588,4 +2588,19 @@ Handmatig kiezen Automatisch instellen Kies lettergrootte + Keur automatisch Element Oproep widgets goed en verleen camera-/microfoontoegang + Snelkoppelingen voor Element Oproep machtigingen inschakelen + Live locatie + Deze QR-code lijkt misvormd. Probeer te verifiëren met een andere methode. + U hebt geen toegang tot de gecodeerde berichtgeschiedenis. Reset uw Veilige Berichten Back-up en verificatiesleutels om opnieuw te beginnen. + Kan dit apparaat niet verifiëren + Wat is het adres van uw server\? + Waar uw conversaties leven + Uw gegevens bijwerken… + + %1$s en %2$d andere + %1$s en %2$d andere + + %1$s en %2$s + E-mailadres niet geverifieerd, controleer je inbox \ No newline at end of file From a41ad6625e0d7bb93c6b098a311e0aaef63274a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Romanik?= Date: Fri, 29 Jul 2022 12:50:25 +0000 Subject: [PATCH 021/114] Translated using Weblate (Polish) Currently translated at 99.6% (2319 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/ --- vector/src/main/res/values-pl/strings.xml | 45 +++++++++++++++++------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 421de65bce..71669740db 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -457,7 +457,7 @@ Markdown został włączony. Markdown został wyłączony. %1$s: %2$s - %d+ + +%d Użyj domyślnego dzwonka ${app_name} dla przychodzących połączeń Połączenia Wyrzuć @@ -1132,7 +1132,7 @@ %d aktywnych sesji %d aktywnych sesji - Zweryfikuj tą sesję + Zweryfikuj te urządzenie Otwórz obecną sesję i użyj jej do zweryfikowania obecnej, przyznając jej dostęp do zaszyfrowanych wiadomości. Zweryfkuj Zweryfikowano @@ -1303,7 +1303,7 @@ %s stworzył(a) i skonfigurował(a) ten pokój. Szyfrowanie wykorzystywane przez ten pokój nie jest obsługiwane Szyfrowanie wyłączone - Wiadomości w tym pokoju są szyfrowane w trybie punkt-punkt (e2e). + Wiadomości w tym czacie są szyfrowane end-to-end. Wiadomości w tym pokoju są szyfrowane punkt-punkt (e2e). Możesz dowiedzieć się więcej i zweryfikować użytkowników w ich profilach. Szyfrowanie włączone Jeżeli teraz przerwiesz, możesz utracić zaszyfrowane wiadomości oraz dane jeżeli utracisz dostęp do zalogowanych sesji. @@ -2377,28 +2377,28 @@ Utwórz konto Pomiń ten krok Zapisz i kontynuuj - Twoje ustawienia zostały zapisane. - Wszystko ustawiłeś! + Zawsze możesz zmienić swój profil w ustawieniach + Dobrze wyglądasz! Chodźmy - Możesz go zmienić w każdym momencie. + Pora nadać tej nazwie jakąś twarz Dodaj obraz profilu Możesz zmienić ją później Powiadomienie pokoju Wyświetlana nazwa Będzie ona widoczna podczas wysyłania wiadomości. Wybierz wyświetlaną nazwę - Twoje konto %s zostało utworzone. + Twoje konto %s zostało utworzone Gratulacje! Zabierz mnie do domu Spersonalizuj profil Połącz się z serwerem Chcesz dołączyć do istniejącego serwera\? - pominąć to pytanie - Nie wiesz jeszcze\? Możesz %s + Pomiń to pytanie + Nie wiesz jeszcze\? %s Społeczności Zespoły Przyjaciele i rodzina - Pomożemy Ci się połączyć. + Pomożemy Ci się połączyć Z kim będziesz najczęściej rozmawiać\? Szyfrowane od-końca-do-końca i nie wymaga numeru telefonu. Brak reklam i dataminingu. Wybierz, gdzie prowadzone są Twoje rozmowy, dając Ci kontrolę i niezależność. Połączenie przez sieć Matrix. @@ -2636,8 +2636,8 @@ Zapomniane hasło Wyślij wiadomość ponownie Email nie dotarł\? - Aby potwierdzić swój email, dotknij przycisk we wiadomości wysłanej na %s - Sprawdź swoją skrzynkę email aby dokończyć weryfikację. + Wykonaj instrukcje podane w wiadomości wysłanej na %s + Potwierdź swój email Wyślij kod ponownie Kod został wysłany do %s Potwierdź swój numer telefonu @@ -2672,4 +2672,25 @@ Ustaw ręcznie Ustaw automatycznie Wybierz rozmiar czcionki + Automatycznie akceptuj widżety Element Call i przyznaj dostęp do kamery i mikrofonu + Włącz skróty uprawnień dla Element Call + Uwaga: to eksperymentalna funkcja wykorzystująca tymczasową implementację. Oznacza to, że nie będzie możliwości usunięcia historii lokalizacji, a zaawansowani użytkownicy będą mogli ją zobaczyć nawet gdy przestaniesz dzielić się lokalizacją na żywo z tym pokojem. + Lokalizacja na żywo + Zapraszając kogoś do zaszyfrowanego pokoju który współdzieli historię, zaszyfrowana historia będzie dla tej osoby widoczna. + MSC3061: Współdzielenie kluczy pokoju dla wcześniejszych wiadomości + Ten kod QR wygląda na niepoprawny. Spróbuj zweryfikować przy użyciu innej metody. + Dostęp do wcześniejszych zaszyfrowanych wiadomości nie będzie możliwy. Zresetuj bezpieczną kopię zapasową wiadomości oraz klucze weryfikacyjne by zacząć od nowa. + Nie udało się zweryfikować tego urządzenia + Zapoznaj się z warunkami i zasadami serwera %s + Jaki jest adres twojego serwera\? + Miejsce na twoje konwersacje + Aktualizowanie twoich danych… + + %1$s i %2$d inny + %1$s i %2$d inne + %1$s i %2$d innych + %1$s i %2$d innych + + %1$s i %2$s + Email nie został zweryfikowany, sprawdź swoją skrzynkę \ No newline at end of file From ee1f29432750163ffe6eaf99a7b33ffc565c95c3 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 4 Aug 2022 03:30:49 +0000 Subject: [PATCH 022/114] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 26218a509a..8e4e1942da 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1390,7 +1390,7 @@ %d sessão ativa %d sessões ativas - Verificar este login + Verificar este dispositivo Use uma sessão existente para verificar esta aqui, concedendo-lhe acesso a mensagens encriptadas. Verificar Verificada(o) @@ -2549,8 +2549,8 @@ Esqueceu senha Reenviar email Não recebeu um email\? - Para confirmar seu email, toque no botão no email que nós acabamos de enviar para %s - Checar seu email para verificar. + Siga as instruções enviadas para %s + Verifique seu email Reenviar código Um código foi enviado para %s Confirmar seu número de telefone @@ -2588,4 +2588,19 @@ Escolher manualmente Definir automaticamente Encolher tamanho de fonte + Auto-aprovar widgets de Element Call e conceder acesso de câmera / mic + Habilitar atalhos de permissão de Element Call + Localização ao vivo + Este QR code parece malformado. Por favor tente verificar com um outro método. + Você não vai ser capaz de acessar histórico de mensagem encriptada. Resette seu Backup de Mensagem Seguro e chaves de verificação para começar do zero. + Incapaz de verificar este dispositivo + Qual é o endereço de seu servidor\? + Onde suas conversas vivem + Atualizando seus dados… + + %1$s e %2$d outro + %1$s e %2$d outros + + %1$s e %2$s + Email não verificado, cheque sua inbox \ No newline at end of file From 05816d00ae52d643a4f5033f326ca6eea2b3a237 Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Fri, 5 Aug 2022 15:15:48 +0000 Subject: [PATCH 023/114] Translated using Weblate (Russian) Currently translated at 99.0% (2306 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 82 +++++++++++++++++++---- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 55c5ab0127..281262d02d 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -194,7 +194,7 @@ %1$s, %2$s и %3$s 🎉 Всем серверам запрещено участвовать! Эта комната больше не может быть использована. Без изменений. - Пустая комната (была %s) + Пустая комната (был(а) %s) • Соответствующие серверы %s заблокированы. • Серверы, соответствующие буквальным IP-адресам, теперь запрещены. • Серверы, соответствующие буквальным IP-адресам, теперь разрешены. @@ -302,7 +302,7 @@ Камера Вход Подать - Неправильный логин и/или пароль + Неправильное имя пользователя и/или пароль Это не похоже на действительный адрес электронной почты Этот адрес электронной почты уже используется. Забыли пароль? @@ -962,7 +962,7 @@ Отфильтровать беседы… Не можете найти то, что ищете\? Создать новую комнату - Отправить новое сообщение в диалог + Отправить новое личное сообщение Просмотр каталога комнат Имя или ID (#example:matrix.org) Включить жест смахивания для ответа в ленте сообщений @@ -1455,7 +1455,7 @@ %d сессии активны %d сессий активно - Подтвердите эту сессию + Подтвердите это устройство Используйте существующую сессию для подтверждения этой, предоставив ей доступ к зашифрованным сообщениям. Инструменты для разработчиков Данные учётной записи @@ -1749,7 +1749,7 @@ Забыли или потеряли все варианты восстановления\? Сбросить все Вы вошли. %s вошёл(ла). - Сообщения в этой комнате зашифрованы сквозным шифрованием. + Сообщения в этой переписке защищены сквозным шифрованием. Покинуть Настройки Сообщения здесь защищены сквозным шифрованием. @@ -2035,7 +2035,7 @@ Ищете кого-то не в %s\? %s приглашает вас Вы приглашены - Пространства - это новый способ группировки комнат и людей. + Пространства — это новый способ организации комнат и людей. Добавить существующие комнаты и пространство Вы единственный администратор этого пространства. Если вы его покинете, то никто не сможет управлять им. Вы сможете присоединиться только после повторного приглашения. @@ -2387,7 +2387,7 @@ Восстановить шифрование Обратитесь к администратору, чтобы восстановить шифрование до рабочего состояния. Шифрование настроено неправильно. - Поделились своим местоположением + Поделился(лась) местоположением У меня уже есть учётная запись Создать учётную запись Обмен сообщениями для вашей команды. @@ -2443,12 +2443,12 @@ Включить обсуждения сообщений Подключиться к серверу Хотите присоединиться к существующему серверу\? - пропустить вопрос - Пока не уверенны\? Вы можете %s + Пропустить вопрос + Пока не уверены\? %s Сообщества Команды Друзья и семья - Мы поможем вам подключится. + Мы поможем вам подключиться С кем вы будете общаться больше всего\? Вы уже просматриваете это обсуждение! Просмотр в Комнате @@ -2489,7 +2489,7 @@ Отображаемое имя Показывается, когда вы отправляете сообщения. Выберите отображаемое имя - Ваш аккаунт «%s» создан. + Ваша учётная запись %s создана Поздравляем! Домой Персонализировать @@ -2523,7 +2523,7 @@ Загрузка трансляции местоположения… Поделиться трансляцией местоположения на Трансляцией местоположения - Поделились трансляцией местоположения + Поделился(лась) трансляцией местоположения Трансляцией местоположения Некоторые результаты могут быть скрыты, поскольку они являются приватными и для их просмотра необходимо приглашение. мин @@ -2550,7 +2550,7 @@ Пропустить этот шаг Сохранить и продолжить Ваши предпочтения были сохранены. - Всё готово! + Выглядит хорошо! ${app_name} также отлично подходит для работы. Ему доверяют самые надёжные организации в мире. Резервная копия имеет действительную подпись для данного пользователя. Воспроизводить анимированные изображения, как только они попадают в поле зрения @@ -2609,4 +2609,58 @@ Текущая конечная точка: %s Конечная точка Вещи в этом пространстве - + Поделиться местоположением + MSC3061: Предоставление ключей от прошлых сообщений + Вперёд + Убедитесь, что он состоит из 8 или более символов. + Выбрать вручную + Выбор размера шрифта + + Удалено %d сообщение + Удалено %d сообщения + Удалено %d сообщений + Удалено %d сообщений + + Выйти из учётной записи на всех устройствах + Трансляция местоположения + Сообщения в этой переписке будут защищены сквозным шифрованием. + Обновление данных… + Повторно отправить письмо + Следуйте инструкциям, отправленным на %s + Подтвердите свою электронную почту + Проверьте электронную почту. + Пожалуйста, ознакомьтесь с условиями и правилами %s + Хотите разместить собственный сервер\? + Какой адрес у вашего сервера\? + Какой адрес у вашего сервера\? Это что-то вроде дома для всех ваших данных + Выберите свой сервер + Установить автоматически + Почта не подтверждена, проверьте почтовый ящик + Изменить + Где хранятся ваши переписки + Где будут храниться ваши переписки + Должно быть 8 или более символов + Не удалось подтвердить это устройство + Невозможно открыть эту ссылку: сообщества были заменены пространствами + Имя пользователя / Почта / Телефон + Следуйте инструкциям, отправленным на %s + Забыли пароль + Не получили письмо\? + Вы человек\? + Повторно отправить код + Код отправлен на %s + Подтвердите номер телефона + Сбросить пароль + Новый пароль + Код подтверждения + Номер телефона + Введите номер телефона + Электронная почта + Введите адрес электронной почты + Правила сервера + URL-адрес сервера + С возвращением! + Или + Создать учётную запись + %1$s и %2$s + \ No newline at end of file From 2aac0c9d649fc20425b00e95c9d62841c86bf485 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Thu, 28 Jul 2022 20:59:51 +0000 Subject: [PATCH 024/114] Translated using Weblate (Slovak) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- vector/src/main/res/values-sk/strings.xml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index f0cf54953e..cac6187d8a 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -1239,7 +1239,7 @@ Šifrovanie nie je zapnuté Udržujte ho v bezpečí Toto som nebol ja - Overte toto prihlásenie + Overiť toto zariadenie %d aktívna relácia %d aktívne relácie @@ -2600,8 +2600,8 @@ Zabudnuté heslo Opätovne odoslať e-mail Nedostali ste e-mail\? - Ak chcete potvrdiť svoj e-mail, ťuknite na tlačidlo v e-maile, ktorý sme práve poslali na adresu %s - Pre overenie skontrolujte svoj e-mail. + Postupujte podľa pokynov zaslaných na adresu %s + Overte svoj e-mail Znovu odoslať kód Kód bol odoslaný na %s Potvrďte svoje telefónne číslo @@ -2637,4 +2637,20 @@ Vybrať manuálne Nastaviť automaticky Vyberte veľkosť písma + Automaticky schvaľovať miniaplikácie Element Call a udeliť prístup ku kamere/mikrofónu + Zapnúť skratky na povolenie Element hovorov + Poloha v reálnom čase + Tento QR kód vyzerá chybne. Skúste vykonať overenie inou metódou. + K zašifrovanej histórii správ nebudete mať prístup. Obnovte zálohu zabezpečených správ a overovacie kľúče a začnite odznova. + Nie je možné overiť toto zariadenie + Aká je adresa vášho servera\? + Kde vaše rozhovory žijú + Aktualizácia vašich údajov… + + %1$s a %2$d ďalší + %1$s a %2$d ďalší + %1$s a %2$d ďalších + + %1$s a %2$s + E-mail nie je overený, skontrolujte si schránku \ No newline at end of file From fde0d81c1e70a3818ecba7857ca472bba193a447 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 2 Aug 2022 19:59:30 +0000 Subject: [PATCH 025/114] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 33 +++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 45a057dd5f..8300199e8a 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1207,7 +1207,7 @@ Використайте чинний сеанс, щоб звірити цей сеанс, таким чином надавши йому доступ до зашифрованих повідомлень. Підтвердьте вашу тотожність, звіривши цей вхід та надавши йому доступ до зашифрованих повідомлень. Звірте новий вхід, що доступається до вашого облікового запису: %1$s - Звірте цей вхід + Звірте цей пристрій Очікування… Новий вхід. Це були ви\? Оновити @@ -1227,7 +1227,7 @@ Ви прийняли запрошення для %1$s. Причина: %2$s %1$s приймає запрошення для %2$s. Причина: %3$s Ви заблокували %1$s. Причина: %2$s - %1$s заблоковано %2$s. Причина: %3$s + %1$s блокує %2$s. Причина: %3$s Ви розблокували %1$s. Причина: %2$s %1$s розблоковує %2$s. Причина: %3$s Ви вилучили %1$s. Причина: %2$s @@ -1436,11 +1436,11 @@ Резервну копію відновлено %s! Не вдалося розшифрувати резервну копію за допомогою цього ключа відновлення: переконайтеся, що ви ввели правильний ключ відновлення. - • Сервери з відповідними літералам IP тепер заблоковано. + • Сервери з відповідними літералам IP тепер заблоковані. • Відповідні сервери %s було вилучено зі списку блокування. - • Відповідні сервери %s тепер заблоковано. - • Сервери, що з відповідними літералам IP заблоковано. - • Відповідні сервери %s заблоковано. + • Відповідні сервери %s відтепер заблоковані. + • Сервери з відповідними літералам IP заблоковані. + • Відповідні сервери %s заблоковані. Неможливо відкрити кімнату, у якій вас заблоковано. Переглянути каталог кімнат Створення кімнати… @@ -2648,8 +2648,8 @@ Забули пароль Не отримали лист\? Повторно надіслати електронний лист - Щоб підтвердити свою електронну адресу, торкніться кнопки в електронному листі, який ми щойно надіслали на адресу %s - Перейдіть до своєї електронної пошти для підтвердження. + Виконайте вказівки надіслані на %s + Підтвердьте свою електронну адресу Повторно надіслати код Код було надіслано на %s Підтвердьте свій номер телефону @@ -2686,4 +2686,21 @@ Вибрати вручну Установити автоматично Виберіть розмір шрифту + Віджети автосхвалення викликів Element і надання доступу до камери/мікрофона + Увімкнути ярлики дозволів на виклик елемента + Місце перебування наживо + Цей QR-код має неправильний вигляд. Спробуйте звірити іншим методом. + Ви не зможете отримати доступ до історії зашифрованих повідомлень. Скиньте захищену резервну копію повідомлень і ключі підтвердження, щоб почати заново. + Не вдалося звірити цей пристрій + Яка адреса вашого сервера\? + Де відбуватимуться ваші розмови + Оновлення ваших даних… + + %1$s і %2$d інший + %1$s і %2$d інші + %1$s і %2$d інших + %1$s і %2$d інших + + %1$s і %2$s + Електронна пошта не підтверджена, перевірте свою поштову скриньку \ No newline at end of file From e15e941e49588f328a1c2eecf5e9918defc524e6 Mon Sep 17 00:00:00 2001 From: phardyle Date: Wed, 3 Aug 2022 16:36:30 +0000 Subject: [PATCH 026/114] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index ef4209a590..0549bd84a9 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -6,7 +6,7 @@ %1$s 加入了房间 %1$s 离开了房间 %1$s 拒绝了邀请 - %1$s 踢了 %2$s + %1$s 移除了 %2$s %1$s 解封了 %2$s %1$s 封禁了 %2$s %1$s 更换了他们的头像 @@ -33,7 +33,7 @@ 电子邮箱地址 手机号码 %1$s 撤回了对 %2$s 的邀请 - %1$s 让未来的房间聊天记录对 %2$s 可见 + %1$s 让未来的房间历史记录对 %2$s 可见 %1$s 向 %2$s 发送了加入房间的邀请 %1$s 接受了 %2$s 的邀请 空房间 @@ -108,7 +108,7 @@ 你发送了数据以建立通话。 你接听了通话。 你结束了通话。 - 你已让未来的房间记录对%1$s可见 + 你已让未来的房间历史对%1$s可见 你升级了此房间。 你移除了房间名称 你移除了房间主题 @@ -893,7 +893,7 @@ 重置安全备份 在此设备上设置 通过在你的服务器上备份加密密钥,防止失去对加密信息和数据的访问。 - 为你已有的备份生成新的安全密钥或设置新的安全口令。 + 为你已有的备份生成新的安全密钥或设置新的安全短语。 这将替换你的当前密钥或短语。 发现 管理你的发现设置。 @@ -1350,7 +1350,7 @@ 发送原始尺寸图片 确认移除 - 你确实想要移除(删除)此事件吗?注意如果你删除房间名或话题更改,可以撤销更改。 + 你确定要移除(删除)此事件吗?注意,如果删除房间名称或话题的更改,更改会被撤销。 附加理由 编辑理由 事件被用户删除,理由:%1$s @@ -1379,7 +1379,7 @@ 消息密钥 输入你的 %s 以继续。 不要使用你的账户密码。 - 输入只有你知道的安全口令,用于保护服务器上的秘密。 + 输入只有你知道的安全短语,用于保护服务器上的秘密。 这可能会花费数秒,请耐心等待。 设置恢复。 完成了! @@ -1517,20 +1517,20 @@ 设置 使用安全密钥 生成安全密钥存储在安全的地方如密码管理器或保险箱。 - 使用安全口令 - 输入仅有你知道的安全口令,生成备份用的密钥。 + 使用安全短语 + 输入仅有你知道的秘密短语,生成备份用的密钥。 保存你的安全密钥 将你的安全密钥存储在安全的地方,例如密码管理器或保险箱。 - 设置安全口令 - 输入只有你知道的安全口令,用于保护你的服务器上的秘密。 - 安全口令 - 再次输入你的安全口令以确认。 + 设置安全短语 + 输入只有你知道的安全短语,用于保护你的服务器上的秘密。 + 安全短语 + 再次输入你的安全短语以确认。 房间名称 主题 你已成功更改房间设置 你无法访问此消息 正在等待此消息,可能会花费一些时间 - 由于端对端加密,你可能需要等待某人的消息到达因为加密密钥未正确发送给你。 + 由于端到端加密,你可能需要等待某人的消息到达,因为加密密钥未正确发送给你。 你无法访问此消息因为你已屏蔽此发送者 你无法访问此消息,因为你的会话不被发送者信任 你无法访问此消息因为发送者有意不发送密钥 @@ -1540,7 +1540,7 @@ 明白了 了解更多 将恢复密钥保存到 - 正在获取你的联系人… + 正在获取你的联系人…… 你的通讯录是空的 通讯录 撤销邀请 @@ -1548,7 +1548,7 @@ 被 %1$s 封禁 解封用户失败 推送通知已禁用 - 查看你的设置以启用推送通知 + 检查你的设置以启用推送通知 选择 PIN 以确保安全 确认 PIN 验证 PIN 失败,请输入新的。 @@ -1583,7 +1583,7 @@ 错误代码,剩余 %d 次尝试 - 注意!登出前最后一次尝试! + 警告!登出前最后一次尝试! 错误次数过多,你已被登出 此电话号码已定义。 你的账户尚未添加电话号码 @@ -1613,8 +1613,8 @@ 每次打开 ${app_name} 都要求 PIN 码。 在 2 分钟未使用 ${app_name} 后要求 PIN 码。 2 分钟后要求 PIN - 仅在一个简单通知中显示未读消息内容数量。 - 显示细节如房间名称和消息内容。 + 仅在一个简单的通知中显示未读消息的数量。 + 显示详情,如房间名称和消息内容。 在通知中显示内容 PIN 码是解锁 ${app_name} 的唯一方式。 启用设备特定的生物特征识别,如指纹和面部识别。 @@ -2143,7 +2143,7 @@ 添加新关键词 你的关键词 - 仅提及 & 关键词 + 仅提及和关键词 正结束通话… 无应答 你呼叫的用户正忙。 @@ -2224,7 +2224,7 @@ 投票 向 %s 发送电子邮件和电话号码 您的联系人是私密的。 要从您的联系人中发现用户,我们需要您的许可才能将联系信息发送到您的身份服务器。 - 已退出此会话! + 已登出此会话! 已离开此房间! 你同意发送此信息吗? 要发现现有的联系人,您需要将联系人信息(电子邮件和电话号码)发送到您的身份服务器。出乎隐私考量,我们会在发送前对您的数据进行散列处理。 @@ -2540,7 +2540,7 @@ - 一些用户已被取消忽略 初始同步请求 - %1$s 与 %2$d 其他人 + %1$s与其他%2$d人 正在更新你的数据…… 自动批准Element通话组件,授予相机/麦克风权限 From ec9199cb18d4d6ed60c423d39a7a2e76b033bbaa Mon Sep 17 00:00:00 2001 From: phardyle Date: Wed, 3 Aug 2022 16:39:09 +0000 Subject: [PATCH 027/114] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index ee96ee6063..c4d5627bce 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1304,7 +1304,7 @@ 使用原始大小傳送圖片 確認移除 - 您確定您想要移除(刪除)此活動嗎?注意,如果您刪除聊天室名稱或變更主題,則可能會撤銷變更。 + 您確定您想要移除(刪除)此活動嗎?注意,如果您刪除聊天室名稱或主題的變更,變更會被撤銷。 包含理由 修改原因 被使用者刪除的活動,理由:%1$s @@ -2293,9 +2293,9 @@ 位置 分享位置 結果僅在您結束投票後顯示 - 已關閉投票 + 封闭式投票 投票者在投票後可以立刻看到投票結果 - 開啟投票 + 開放式投票 投票類型 編輯投票 沒有投票 @@ -2486,7 +2486,7 @@ MSC3061:為過去的訊息分享聊天室金鑰 傳送您的第一則訊息以邀請 %s 來聊天 此聊天中的訊息將會是端到端加密。 - + 出發 已移除 %d 則訊息 From 77ae291c1576fe378cbb0e6fb7ebc8c7433a1403 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 29 Jul 2022 02:10:33 +0000 Subject: [PATCH 028/114] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index c4d5627bce..effb6e7410 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1273,7 +1273,7 @@ %d 活躍的工作階段 - 驗證此登入 + 驗證此裝置 使用既有的工作階段來驗證這個,讓它可以存取已加密的訊息。 驗證 已驗證 @@ -2501,8 +2501,8 @@ 忘記密碼 重新傳送電子郵件 沒有收到電子郵件? - 要確認您的電子郵件,請點擊我們剛剛寄給 %s 的電子郵件中的按鈕 - 檢查您的電子郵件以驗證。 + 請遵循傳送至 %s 中的步驟 + 驗證您的電子郵件 重新傳送驗證碼 驗證碼已傳送至 %s 確認您的電話號碼 @@ -2539,4 +2539,18 @@ 手動選擇 自動設定 選擇字型大小 + 自動批准 Element Call 小工具並授予相機/麥克風存取權限 + 啟用 Element Call 權限捷徑 + 即時位置 + 這個 QR code 的格式似乎不正確。請嘗試使用其他方法進行驗證。 + 您將無法存取已加密的訊息歷史紀錄。重設您的安全訊息備份與驗證金鑰以重新開始。 + 無法驗證此裝置 + 您的伺服器地址是? + 您的對話所在位置 + 正在更新您的資料…… + + %1$s 與 %2$d 個其他人 + + %1$s 與 %2$s + 電子郵件未驗證,請檢查您的收件匣 \ No newline at end of file From ac047afd278b3bf59914e1d73976cdb993fec3d3 Mon Sep 17 00:00:00 2001 From: Dinh Quang Tuyen Date: Thu, 4 Aug 2022 08:17:21 +0000 Subject: [PATCH 029/114] Translated using Weblate (Vietnamese) Currently translated at 88.6% (2062 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/ --- vector/src/main/res/values-vi/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index dca5645f1d..61b8728ca0 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -975,7 +975,7 @@ \nVui lòng kiểm tra thiết lập. Thông báo được bật trong thiết lập hệ thống. Thiết lập hệ thống. - Xử lý lỗi thông báo + Thông báo khắc phục lỗi Từ khóa không được chứa \'%s\' Từ khóa không được bắt đầu với \'.\' Thêm mới từ khóa @@ -2274,4 +2274,7 @@ Chế độ ngoại tuyến Sự hiện diện Hiển thị bong bóng tin nhắn + Chọn cách nhận thông báo + Phương thức thông báo + Đặt lại phương thức thông báo \ No newline at end of file From e02cf61f2ff410b4accd1481ea1e0b564fb05ee9 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 8 Aug 2022 16:57:51 +0100 Subject: [PATCH 030/114] decoupling debug receiver from the variants by introducing vector layer interface --- .../app/features/debug/di/DebugModule.kt | 32 ++++++++++++++++ ...ebugReceiver.kt => VectorDebugReceiver.kt} | 12 +++++- .../vector/app/core/platform/DebugReceiver.kt | 24 ++++++++++++ .../app/core/platform/VectorBaseActivity.kt | 21 +++------- .../java/im/vector/app/core/di/DebugModule.kt | 38 +++++++++++++++++++ 5 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt rename vector/src/debug/java/im/vector/app/receivers/{DebugReceiver.kt => VectorDebugReceiver.kt} (88%) create mode 100644 vector/src/main/java/im/vector/app/core/platform/DebugReceiver.kt create mode 100644 vector/src/release/java/im/vector/app/core/di/DebugModule.kt diff --git a/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt b/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt new file mode 100644 index 0000000000..d68d6c743f --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import im.vector.app.core.platform.DebugReceiver +import im.vector.app.receivers.VectorDebugReceiver + +@InstallIn(SingletonComponent::class) +@Module +abstract class DebugModule { + + @Binds + abstract fun bindsDebugReceiver(receiver: VectorDebugReceiver): DebugReceiver +} diff --git a/vector/src/debug/java/im/vector/app/receivers/DebugReceiver.kt b/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt similarity index 88% rename from vector/src/debug/java/im/vector/app/receivers/DebugReceiver.kt rename to vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt index 9ec475d6d3..9b547a433e 100644 --- a/vector/src/debug/java/im/vector/app/receivers/DebugReceiver.kt +++ b/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt @@ -23,13 +23,23 @@ import android.content.IntentFilter import android.content.SharedPreferences import androidx.core.content.edit import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.platform.DebugReceiver import im.vector.app.core.utils.lsFiles import timber.log.Timber +import javax.inject.Inject /** * Receiver to handle some command from ADB */ -class DebugReceiver : BroadcastReceiver() { +class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugReceiver { + + override fun register(context: Context) { + context.registerReceiver(this, getIntentFilter(context)) + } + + override fun unregister(context: Context) { + context.unregisterReceiver(this) + } override fun onReceive(context: Context, intent: Intent) { Timber.v("Received debug action: ${intent.action}") diff --git a/vector/src/main/java/im/vector/app/core/platform/DebugReceiver.kt b/vector/src/main/java/im/vector/app/core/platform/DebugReceiver.kt new file mode 100644 index 0000000000..aeb99f312d --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/platform/DebugReceiver.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.platform + +import android.content.Context + +interface DebugReceiver { + fun register(context: Context) + fun unregister(context: Context) +} diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 8a09b6bd46..e5c7ab9228 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -91,7 +91,7 @@ import im.vector.app.features.settings.FontScalePreferencesImpl import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils -import im.vector.app.receivers.DebugReceiver +import im.vector.app.receivers.VectorDebugReceiver import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.orFalse @@ -160,6 +160,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver @Inject lateinit var rageShake: RageShake @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var fontScalePreferences: FontScalePreferences + // For debug only + @Inject lateinit var debugReceiver: VectorDebugReceiver @Inject lateinit var vectorFeatures: VectorFeatures @@ -176,9 +178,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver private var savedInstanceState: Bundle? = null - // For debug only - private var debugReceiver: DebugReceiver? = null - private val restorables = ArrayList() override fun attachBaseContext(base: Context) { @@ -418,13 +417,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver if (this !is BugReportActivity && vectorPreferences.useRageshake()) { rageShake.start() } - DebugReceiver - .getIntentFilter(this) - .takeIf { buildMeta.isDebug } - ?.let { - debugReceiver = DebugReceiver() - registerReceiver(debugReceiver, it) - } + debugReceiver.register(this) } private val postResumeScheduledActions = mutableListOf<() -> Unit>() @@ -454,11 +447,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver Timber.i("onPause Activity ${javaClass.simpleName}") rageShake.stop() - - debugReceiver?.let { - unregisterReceiver(debugReceiver) - debugReceiver = null - } + debugReceiver.unregister(this) } override fun onWindowFocusChanged(hasFocus: Boolean) { diff --git a/vector/src/release/java/im/vector/app/core/di/DebugModule.kt b/vector/src/release/java/im/vector/app/core/di/DebugModule.kt new file mode 100644 index 0000000000..2899b14b7b --- /dev/null +++ b/vector/src/release/java/im/vector/app/core/di/DebugModule.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import im.vector.app.core.platform.DebugReceiver + +@InstallIn(SingletonComponent::class) +@Module +object DebugModule { + @Provides + fun providesDebugReceiver() = object: DebugReceiver { + override fun register(context: Context) { + // no op + } + + override fun unregister(context: Context) { + // no op + } + } +} From 19c8b2a630e0e15d1f828bec0e2a3a69e9b9b2d9 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 8 Aug 2022 17:02:08 +0100 Subject: [PATCH 031/114] moving debug menu access to only the debug sourceset and providing an injectable interface to decouple from the direct access --- vector/src/debug/AndroidManifest.xml | 1 + .../vector/app/features/debug/di/DebugModule.kt | 15 +++++++++++++++ vector/src/main/AndroidManifest.xml | 1 - .../app/features/navigation/DebugNavigator.kt} | 11 ++++++----- .../app/features/navigation/DefaultNavigator.kt | 7 ++++--- .../java/im/vector/app/core/di/DebugModule.kt | 11 ++++++++++- 6 files changed, 36 insertions(+), 10 deletions(-) rename vector/src/{release/java/im/vector/app/features/debug/DebugMenuActivity.kt => main/java/im/vector/app/features/navigation/DebugNavigator.kt} (74%) diff --git a/vector/src/debug/AndroidManifest.xml b/vector/src/debug/AndroidManifest.xml index 84fa2584b9..4ec47d4920 100644 --- a/vector/src/debug/AndroidManifest.xml +++ b/vector/src/debug/AndroidManifest.xml @@ -9,6 +9,7 @@ + - diff --git a/vector/src/release/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/main/java/im/vector/app/features/navigation/DebugNavigator.kt similarity index 74% rename from vector/src/release/java/im/vector/app/features/debug/DebugMenuActivity.kt rename to vector/src/main/java/im/vector/app/features/navigation/DebugNavigator.kt index c5db033a18..26e8a3e5c6 100644 --- a/vector/src/release/java/im/vector/app/features/debug/DebugMenuActivity.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DebugNavigator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ -package im.vector.app.features.debug +package im.vector.app.features.navigation -import androidx.appcompat.app.AppCompatActivity +import android.content.Context -// This activity is not accessible -class DebugMenuActivity : AppCompatActivity() +interface DebugNavigator { + fun openDebugMenu(context: Context) +} diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 38db642287..e9f1619ed5 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -51,7 +51,6 @@ import im.vector.app.features.crypto.recover.BootstrapBottomSheet import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider import im.vector.app.features.crypto.verification.VerificationBottomSheet -import im.vector.app.features.debug.DebugMenuActivity import im.vector.app.features.devtools.RoomDevToolActivity import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.arguments.TimelineArgs @@ -123,7 +122,8 @@ class DefaultNavigator @Inject constructor( private val spaceStateHandler: SpaceStateHandler, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val features: VectorFeatures, - private val analyticsTracker: AnalyticsTracker + private val analyticsTracker: AnalyticsTracker, + private val debugNavigator: DebugNavigator, ) : Navigator { override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { @@ -367,7 +367,7 @@ class DefaultNavigator @Inject constructor( } override fun openDebug(context: Context) { - context.startActivity(Intent(context, DebugMenuActivity::class.java)) + debugNavigator.openDebugMenu(context) } override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) { @@ -615,3 +615,4 @@ class DefaultNavigator @Inject constructor( context.startActivity(this) } } + diff --git a/vector/src/release/java/im/vector/app/core/di/DebugModule.kt b/vector/src/release/java/im/vector/app/core/di/DebugModule.kt index 2899b14b7b..c5af1e06c8 100644 --- a/vector/src/release/java/im/vector/app/core/di/DebugModule.kt +++ b/vector/src/release/java/im/vector/app/core/di/DebugModule.kt @@ -21,12 +21,21 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.core.platform.DebugReceiver +import im.vector.app.features.navigation.DebugNavigator @InstallIn(SingletonComponent::class) @Module object DebugModule { + @Provides - fun providesDebugReceiver() = object: DebugReceiver { + fun providesDebugNavigator() = object : DebugNavigator { + override fun openDebugMenu(context: Context) { + // no op + } + } + + @Provides + fun providesDebugReceiver() = object : DebugReceiver { override fun register(context: Context) { // no op } From 8156a646a118d951af376286b1836f22ab36901b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 8 Aug 2022 17:06:57 +0100 Subject: [PATCH 032/114] moving debug interfaces to debug package --- .../debug/java/im/vector/app/features/debug/di/DebugModule.kt | 4 ++-- .../debug/java/im/vector/app/receivers/VectorDebugReceiver.kt | 2 +- .../app/{features/navigation => core/debug}/DebugNavigator.kt | 2 +- .../im/vector/app/core/{platform => debug}/DebugReceiver.kt | 2 +- .../im/vector/app/features/navigation/DefaultNavigator.kt | 1 + vector/src/release/java/im/vector/app/core/di/DebugModule.kt | 4 ++-- 6 files changed, 8 insertions(+), 7 deletions(-) rename vector/src/main/java/im/vector/app/{features/navigation => core/debug}/DebugNavigator.kt (94%) rename vector/src/main/java/im/vector/app/core/{platform => debug}/DebugReceiver.kt (95%) diff --git a/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt b/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt index f5f10bf9c9..fbac319ee9 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt @@ -23,9 +23,9 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import im.vector.app.core.platform.DebugReceiver +import im.vector.app.core.debug.DebugReceiver import im.vector.app.features.debug.DebugMenuActivity -import im.vector.app.features.navigation.DebugNavigator +import im.vector.app.core.debug.DebugNavigator import im.vector.app.receivers.VectorDebugReceiver @InstallIn(SingletonComponent::class) diff --git a/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt b/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt index 9b547a433e..827fc540f7 100644 --- a/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt +++ b/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt @@ -23,7 +23,7 @@ import android.content.IntentFilter import android.content.SharedPreferences import androidx.core.content.edit import im.vector.app.core.di.DefaultSharedPreferences -import im.vector.app.core.platform.DebugReceiver +import im.vector.app.core.debug.DebugReceiver import im.vector.app.core.utils.lsFiles import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/navigation/DebugNavigator.kt b/vector/src/main/java/im/vector/app/core/debug/DebugNavigator.kt similarity index 94% rename from vector/src/main/java/im/vector/app/features/navigation/DebugNavigator.kt rename to vector/src/main/java/im/vector/app/core/debug/DebugNavigator.kt index 26e8a3e5c6..16c8dac55c 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DebugNavigator.kt +++ b/vector/src/main/java/im/vector/app/core/debug/DebugNavigator.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.navigation +package im.vector.app.core.debug import android.content.Context diff --git a/vector/src/main/java/im/vector/app/core/platform/DebugReceiver.kt b/vector/src/main/java/im/vector/app/core/debug/DebugReceiver.kt similarity index 95% rename from vector/src/main/java/im/vector/app/core/platform/DebugReceiver.kt rename to vector/src/main/java/im/vector/app/core/debug/DebugReceiver.kt index aeb99f312d..7cccba3d37 100644 --- a/vector/src/main/java/im/vector/app/core/platform/DebugReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/debug/DebugReceiver.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.core.platform +package im.vector.app.core.debug import android.content.Context diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index e9f1619ed5..872f102a0d 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -51,6 +51,7 @@ import im.vector.app.features.crypto.recover.BootstrapBottomSheet import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider import im.vector.app.features.crypto.verification.VerificationBottomSheet +import im.vector.app.core.debug.DebugNavigator import im.vector.app.features.devtools.RoomDevToolActivity import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.arguments.TimelineArgs diff --git a/vector/src/release/java/im/vector/app/core/di/DebugModule.kt b/vector/src/release/java/im/vector/app/core/di/DebugModule.kt index c5af1e06c8..c340c36aed 100644 --- a/vector/src/release/java/im/vector/app/core/di/DebugModule.kt +++ b/vector/src/release/java/im/vector/app/core/di/DebugModule.kt @@ -20,8 +20,8 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import im.vector.app.core.platform.DebugReceiver -import im.vector.app.features.navigation.DebugNavigator +import im.vector.app.core.debug.DebugNavigator +import im.vector.app.core.debug.DebugReceiver @InstallIn(SingletonComponent::class) @Module From 02286b96b05b8d2529848035753b352355c5f762 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 8 Aug 2022 17:13:43 +0100 Subject: [PATCH 033/114] decoupling the flipper proxy from the vector module --- .../app/features/debug/di/DebugModule.kt | 6 +++++ ...{FlipperProxy.kt => VectorFlipperProxy.kt} | 13 ++++------ .../java/im/vector/app/di/FlavorModule.kt | 8 ++++++ .../java/im/vector/app/di/FlavorModule.kt | 16 +++++++++--- ...ightlyProxy.kt => FirebaseNightlyProxy.kt} | 8 +++--- .../java/im/vector/app/VectorApplication.kt | 2 +- .../im/vector/app/core/debug/FlipperProxy.kt | 25 +++++++++++++++++++ .../im/vector/app/core/di/SingletonModule.kt | 4 +-- .../app/core/platform/VectorBaseActivity.kt | 5 ++-- .../vector/app/features/home/HomeActivity.kt | 1 - .../vector/app/features/home}/NightlyProxy.kt | 8 +++--- .../java/im/vector/app/core/di/DebugModule.kt | 17 +++++++++++-- 12 files changed, 85 insertions(+), 28 deletions(-) rename vector/src/debug/java/im/vector/app/flipper/{FlipperProxy.kt => VectorFlipperProxy.kt} (91%) rename vector/src/gplay/java/im/vector/app/nightly/{NightlyProxy.kt => FirebaseNightlyProxy.kt} (95%) create mode 100644 vector/src/main/java/im/vector/app/core/debug/FlipperProxy.kt rename vector/src/{fdroid/java/im/vector/app/nightly => main/java/im/vector/app/features/home}/NightlyProxy.kt (82%) diff --git a/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt b/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt index fbac319ee9..56df9fa0bc 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt @@ -26,6 +26,8 @@ import dagger.hilt.components.SingletonComponent import im.vector.app.core.debug.DebugReceiver import im.vector.app.features.debug.DebugMenuActivity import im.vector.app.core.debug.DebugNavigator +import im.vector.app.core.debug.FlipperProxy +import im.vector.app.flipper.VectorFlipperProxy import im.vector.app.receivers.VectorDebugReceiver @InstallIn(SingletonComponent::class) @@ -44,4 +46,8 @@ abstract class DebugModule { @Binds abstract fun bindsDebugReceiver(receiver: VectorDebugReceiver): DebugReceiver + + @Binds + abstract fun bindsFlipperProxy(flipperProxy: VectorFlipperProxy): FlipperProxy + } diff --git a/vector/src/debug/java/im/vector/app/flipper/FlipperProxy.kt b/vector/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt similarity index 91% rename from vector/src/debug/java/im/vector/app/flipper/FlipperProxy.kt rename to vector/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt index 76be7e1b46..2e4336c942 100644 --- a/vector/src/debug/java/im/vector/app/flipper/FlipperProxy.kt +++ b/vector/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt @@ -29,19 +29,19 @@ import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPl import com.facebook.soloader.SoLoader import com.kgurgul.flipper.RealmDatabaseDriver import com.kgurgul.flipper.RealmDatabaseProvider +import im.vector.app.core.debug.FlipperProxy import io.realm.RealmConfiguration -import okhttp3.Interceptor import org.matrix.android.sdk.api.Matrix import javax.inject.Inject import javax.inject.Singleton @Singleton -class FlipperProxy @Inject constructor( +class VectorFlipperProxy @Inject constructor( private val context: Context, -) { +) : FlipperProxy { private val networkFlipperPlugin = NetworkFlipperPlugin() - fun init(matrix: Matrix) { + override fun init(matrix: Matrix) { SoLoader.init(context, false) if (FlipperUtils.shouldEnableFlipper(context)) { @@ -65,8 +65,5 @@ class FlipperProxy @Inject constructor( } } - @Suppress("RedundantNullableReturnType") - fun getNetworkInterceptor(): Interceptor? { - return FlipperOkhttpInterceptor(networkFlipperPlugin) - } + override fun networkInterceptor() = FlipperOkhttpInterceptor(networkFlipperPlugin) } diff --git a/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt b/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt index 1a3348f4c6..fcad54c3d2 100644 --- a/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt +++ b/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt @@ -23,6 +23,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.core.services.GuardServiceStarter import im.vector.app.fdroid.service.FDroidGuardServiceStarter +import im.vector.app.features.home.NightlyProxy import im.vector.app.features.settings.VectorPreferences @InstallIn(SingletonComponent::class) @@ -33,4 +34,11 @@ object FlavorModule { fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter { return FDroidGuardServiceStarter(preferences, appContext) } + + @Provides + fun provideNightlyProxy() = object : NightlyProxy { + override fun onHomeResumed() { + // no op + } + } } diff --git a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt index 901320be4c..256c908a02 100644 --- a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt +++ b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt @@ -16,18 +16,26 @@ package im.vector.app.di +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.core.services.GuardServiceStarter +import im.vector.app.features.home.NightlyProxy +import im.vector.app.nightly.FirebaseNightlyProxy @InstallIn(SingletonComponent::class) @Module -object FlavorModule { +abstract class FlavorModule { - @Provides - fun provideGuardServiceStarter(): GuardServiceStarter { - return object : GuardServiceStarter {} + companion object { + @Provides + fun provideGuardServiceStarter(): GuardServiceStarter { + return object : GuardServiceStarter {} + } } + + @Binds + abstract fun bindsNightlyProxy(nightlyProxy: FirebaseNightlyProxy): NightlyProxy } diff --git a/vector/src/gplay/java/im/vector/app/nightly/NightlyProxy.kt b/vector/src/gplay/java/im/vector/app/nightly/FirebaseNightlyProxy.kt similarity index 95% rename from vector/src/gplay/java/im/vector/app/nightly/NightlyProxy.kt rename to vector/src/gplay/java/im/vector/app/nightly/FirebaseNightlyProxy.kt index 7c6685f5ce..59a1e3eb17 100644 --- a/vector/src/gplay/java/im/vector/app/nightly/NightlyProxy.kt +++ b/vector/src/gplay/java/im/vector/app/nightly/FirebaseNightlyProxy.kt @@ -23,15 +23,17 @@ import com.google.firebase.appdistribution.FirebaseAppDistributionException import im.vector.app.BuildConfig import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.time.Clock +import im.vector.app.features.home.NightlyProxy import timber.log.Timber import javax.inject.Inject -class NightlyProxy @Inject constructor( +class FirebaseNightlyProxy @Inject constructor( private val clock: Clock, @DefaultPreferences private val sharedPreferences: SharedPreferences, -) { - fun onHomeResumed() { +) : NightlyProxy { + + override fun onHomeResumed() { if (!canDisplayPopup()) return val firebaseAppDistribution = FirebaseAppDistribution.getInstance() firebaseAppDistribution.updateIfNewReleaseAvailable() diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index b1bd0fc308..f31b5d915e 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -41,6 +41,7 @@ import com.vanniktech.emoji.EmojiManager import com.vanniktech.emoji.google.GoogleEmojiProvider import dagger.hilt.android.HiltAndroidApp import im.vector.app.config.Config +import im.vector.app.core.debug.FlipperProxy import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.BuildMeta import im.vector.app.features.analytics.VectorAnalytics @@ -59,7 +60,6 @@ import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.version.VersionProvider -import im.vector.app.flipper.FlipperProxy import im.vector.app.push.fcm.FcmHelper import org.jitsi.meet.sdk.log.JitsiMeetDefaultLogHandler import org.matrix.android.sdk.api.Matrix diff --git a/vector/src/main/java/im/vector/app/core/debug/FlipperProxy.kt b/vector/src/main/java/im/vector/app/core/debug/FlipperProxy.kt new file mode 100644 index 0000000000..a05da239f5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/debug/FlipperProxy.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.debug + +import okhttp3.Interceptor +import org.matrix.android.sdk.api.Matrix + +interface FlipperProxy { + fun init(matrix: Matrix) + fun networkInterceptor(): Interceptor? +} diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index 6959f17586..a060ebe731 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -34,6 +34,7 @@ import im.vector.app.EmojiSpanify import im.vector.app.SpaceStateHandler import im.vector.app.SpaceStateHandlerImpl import im.vector.app.config.Config +import im.vector.app.core.debug.FlipperProxy import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter @@ -57,7 +58,6 @@ import im.vector.app.features.settings.FontScalePreferencesImpl import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.UiStateRepository -import im.vector.app.flipper.FlipperProxy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -144,7 +144,7 @@ object VectorStaticModule { roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider, threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(), networkInterceptors = listOfNotNull( - flipperProxy.getNetworkInterceptor(), + flipperProxy.networkInterceptor(), ) ) } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index e5c7ab9228..24a65e1071 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -55,6 +55,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.EntryPointAccessors import im.vector.app.R +import im.vector.app.core.debug.DebugReceiver import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActivityEntryPoint import im.vector.app.core.dialogs.DialogLocker @@ -91,7 +92,6 @@ import im.vector.app.features.settings.FontScalePreferencesImpl import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils -import im.vector.app.receivers.VectorDebugReceiver import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.orFalse @@ -160,8 +160,9 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver @Inject lateinit var rageShake: RageShake @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var fontScalePreferences: FontScalePreferences + // For debug only - @Inject lateinit var debugReceiver: VectorDebugReceiver + @Inject lateinit var debugReceiver: DebugReceiver @Inject lateinit var vectorFeatures: VectorFeatures diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 12cdaecdf9..4dd6f8cfba 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -79,7 +79,6 @@ import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel -import im.vector.app.nightly.NightlyProxy import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach diff --git a/vector/src/fdroid/java/im/vector/app/nightly/NightlyProxy.kt b/vector/src/main/java/im/vector/app/features/home/NightlyProxy.kt similarity index 82% rename from vector/src/fdroid/java/im/vector/app/nightly/NightlyProxy.kt rename to vector/src/main/java/im/vector/app/features/home/NightlyProxy.kt index eecf3a24f2..b25add2ac9 100644 --- a/vector/src/fdroid/java/im/vector/app/nightly/NightlyProxy.kt +++ b/vector/src/main/java/im/vector/app/features/home/NightlyProxy.kt @@ -14,10 +14,8 @@ * limitations under the License. */ -package im.vector.app.nightly +package im.vector.app.features.home -import javax.inject.Inject - -class NightlyProxy @Inject constructor() { - fun onHomeResumed() = Unit +interface NightlyProxy { + fun onHomeResumed() } diff --git a/vector/src/release/java/im/vector/app/core/di/DebugModule.kt b/vector/src/release/java/im/vector/app/core/di/DebugModule.kt index c340c36aed..bedbe97864 100644 --- a/vector/src/release/java/im/vector/app/core/di/DebugModule.kt +++ b/vector/src/release/java/im/vector/app/core/di/DebugModule.kt @@ -14,14 +14,18 @@ * limitations under the License. */ -package im.vector.app.features.debug.di +package im.vector.app.core.di -import dagger.Binds +import android.content.Context import dagger.Module +import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.core.debug.DebugNavigator import im.vector.app.core.debug.DebugReceiver +import im.vector.app.core.debug.FlipperProxy +import okhttp3.Interceptor +import org.matrix.android.sdk.api.Matrix @InstallIn(SingletonComponent::class) @Module @@ -44,4 +48,13 @@ object DebugModule { // no op } } + + @Provides + fun providesFlipperProxy() = object : FlipperProxy { + override fun init(matrix: Matrix) { + // no op + } + + override fun networkInterceptor(): Interceptor? = null + } } From f605e0c4791318e3f8b103004b7cd4fa1fa4c223 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 8 Aug 2022 17:51:08 +0100 Subject: [PATCH 034/114] decoupling the notification test factory from the vector module --- .../vector/app/di/NotificationTestModule.kt | 31 +++++++++++++++++++ ...ficationTroubleshootTestManagerFactory.kt} | 7 +++-- .../vector/app/di/NotificationTestModule.kt | 31 +++++++++++++++++++ ...ficationTroubleshootTestManagerFactory.kt} | 7 +++-- ...ificationTroubleshootTestManagerFactory.kt | 24 ++++++++++++++ ...ttingsNotificationsTroubleshootFragment.kt | 2 +- 6 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 vector/src/fdroid/java/im/vector/app/di/NotificationTestModule.kt rename vector/src/fdroid/java/im/vector/app/push/fcm/{NotificationTroubleshootTestManagerFactory.kt => FdroidNotificationTroubleshootTestManagerFactory.kt} (93%) create mode 100644 vector/src/gplay/java/im/vector/app/di/NotificationTestModule.kt rename vector/src/gplay/java/im/vector/app/push/fcm/{NotificationTroubleshootTestManagerFactory.kt => GoogleNotificationTroubleshootTestManagerFactory.kt} (93%) create mode 100644 vector/src/main/java/im/vector/app/features/push/NotificationTroubleshootTestManagerFactory.kt diff --git a/vector/src/fdroid/java/im/vector/app/di/NotificationTestModule.kt b/vector/src/fdroid/java/im/vector/app/di/NotificationTestModule.kt new file mode 100644 index 0000000000..a171126168 --- /dev/null +++ b/vector/src/fdroid/java/im/vector/app/di/NotificationTestModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory +import im.vector.app.push.fcm.FdroidNotificationTroubleshootTestManagerFactory + +@InstallIn(ActivityComponent::class) +@Module +abstract class NotificationTestModule { + @Binds + abstract fun bindsNotificationTestFactory(factory: FdroidNotificationTroubleshootTestManagerFactory): NotificationTroubleshootTestManagerFactory +} diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidNotificationTroubleshootTestManagerFactory.kt similarity index 93% rename from vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt rename to vector/src/fdroid/java/im/vector/app/push/fcm/FdroidNotificationTroubleshootTestManagerFactory.kt index 5873b4308f..86843f0e6f 100644 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidNotificationTroubleshootTestManagerFactory.kt @@ -21,6 +21,7 @@ import im.vector.app.fdroid.features.settings.troubleshoot.TestAutoStartBoot import im.vector.app.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization import im.vector.app.features.VectorFeatures +import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors @@ -35,7 +36,7 @@ import im.vector.app.features.settings.troubleshoot.TestUnifiedPushEndpoint import im.vector.app.features.settings.troubleshoot.TestUnifiedPushGateway import javax.inject.Inject -class NotificationTroubleshootTestManagerFactory @Inject constructor( +class FdroidNotificationTroubleshootTestManagerFactory @Inject constructor( private val unifiedPushHelper: UnifiedPushHelper, private val testSystemSettings: TestSystemSettings, private val testAccountSettings: TestAccountSettings, @@ -52,9 +53,9 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor( private val testBatteryOptimization: TestBatteryOptimization, private val testNotification: TestNotification, private val vectorFeatures: VectorFeatures, -) { +): NotificationTroubleshootTestManagerFactory { - fun create(fragment: Fragment): NotificationTroubleshootTestManager { + override fun create(fragment: Fragment): NotificationTroubleshootTestManager { val mgr = NotificationTroubleshootTestManager(fragment) mgr.addTest(testSystemSettings) mgr.addTest(testAccountSettings) diff --git a/vector/src/gplay/java/im/vector/app/di/NotificationTestModule.kt b/vector/src/gplay/java/im/vector/app/di/NotificationTestModule.kt new file mode 100644 index 0000000000..f3dfbccfc1 --- /dev/null +++ b/vector/src/gplay/java/im/vector/app/di/NotificationTestModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory +import im.vector.app.push.fcm.GoogleNotificationTroubleshootTestManagerFactory + +@InstallIn(ActivityComponent::class) +@Module +abstract class NotificationTestModule { + @Binds + abstract fun bindsNotificationTestFactory(factory: GoogleNotificationTroubleshootTestManagerFactory): NotificationTroubleshootTestManagerFactory +} diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/gplay/java/im/vector/app/push/fcm/GoogleNotificationTroubleshootTestManagerFactory.kt similarity index 93% rename from vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt rename to vector/src/gplay/java/im/vector/app/push/fcm/GoogleNotificationTroubleshootTestManagerFactory.kt index b3425c778b..154e127fb6 100644 --- a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/GoogleNotificationTroubleshootTestManagerFactory.kt @@ -18,6 +18,7 @@ package im.vector.app.push.fcm import androidx.fragment.app.Fragment import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.features.VectorFeatures +import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors @@ -35,7 +36,7 @@ import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices import im.vector.app.gplay.features.settings.troubleshoot.TestTokenRegistration import javax.inject.Inject -class NotificationTroubleshootTestManagerFactory @Inject constructor( +class GoogleNotificationTroubleshootTestManagerFactory @Inject constructor( private val unifiedPushHelper: UnifiedPushHelper, private val testSystemSettings: TestSystemSettings, private val testAccountSettings: TestAccountSettings, @@ -52,9 +53,9 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor( private val testPushFromPushGateway: TestPushFromPushGateway, private val testNotification: TestNotification, private val vectorFeatures: VectorFeatures, -) { +) : NotificationTroubleshootTestManagerFactory { - fun create(fragment: Fragment): NotificationTroubleshootTestManager { + override fun create(fragment: Fragment): NotificationTroubleshootTestManager { val mgr = NotificationTroubleshootTestManager(fragment) mgr.addTest(testSystemSettings) mgr.addTest(testAccountSettings) diff --git a/vector/src/main/java/im/vector/app/features/push/NotificationTroubleshootTestManagerFactory.kt b/vector/src/main/java/im/vector/app/features/push/NotificationTroubleshootTestManagerFactory.kt new file mode 100644 index 0000000000..6a3ce04c1a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/push/NotificationTroubleshootTestManagerFactory.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.push + +import androidx.fragment.app.Fragment +import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager + +interface NotificationTroubleshootTestManagerFactory { + fun create(fragment: Fragment): NotificationTroubleshootTestManager +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt index 8a3407b428..e75824195e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt @@ -35,11 +35,11 @@ import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSettingsNotificationsTroubleshootBinding import im.vector.app.features.notifications.NotificationActionIds +import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TroubleshootTest -import im.vector.app.push.fcm.NotificationTroubleshootTestManagerFactory import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import javax.inject.Inject From 0c61595ace023f26d22aa85baf6a7fb8c3bdcd78 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 8 Aug 2022 18:05:04 +0100 Subject: [PATCH 035/114] decoupling the fcm helper from the vector module --- .../java/im/vector/app/di/FlavorModule.kt | 26 ++++++---- .../fcm/{FcmHelper.kt => FdroidFcmHelper.kt} | 17 ++++--- .../java/im/vector/app/di/FlavorModule.kt | 5 ++ .../troubleshoot/TestFirebaseToken.kt | 2 +- .../troubleshoot/TestTokenRegistration.kt | 2 +- .../fcm/{FcmHelper.kt => GoogleFcmHelper.kt} | 17 ++++--- .../java/im/vector/app/VectorApplication.kt | 2 +- .../im/vector/app/core/pushers/FcmHelper.kt | 50 +++++++++++++++++++ .../app/core/pushers/UnifiedPushHelper.kt | 1 - .../vector/app/features/home/HomeActivity.kt | 2 +- .../TestAvailableUnifiedPushDistributors.kt | 2 +- 11 files changed, 95 insertions(+), 31 deletions(-) rename vector/src/fdroid/java/im/vector/app/push/fcm/{FcmHelper.kt => FdroidFcmHelper.kt} (78%) rename vector/src/gplay/java/im/vector/app/push/fcm/{FcmHelper.kt => GoogleFcmHelper.kt} (87%) create mode 100644 vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt diff --git a/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt b/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt index fcad54c3d2..5a7a527c3f 100644 --- a/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt +++ b/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt @@ -17,28 +17,36 @@ package im.vector.app.di import android.content.Context +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.services.GuardServiceStarter import im.vector.app.fdroid.service.FDroidGuardServiceStarter import im.vector.app.features.home.NightlyProxy import im.vector.app.features.settings.VectorPreferences +import im.vector.app.push.fcm.FdroidFcmHelper @InstallIn(SingletonComponent::class) @Module -object FlavorModule { +abstract class FlavorModule { - @Provides - fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter { - return FDroidGuardServiceStarter(preferences, appContext) - } + companion object { + @Provides + fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter { + return FDroidGuardServiceStarter(preferences, appContext) + } - @Provides - fun provideNightlyProxy() = object : NightlyProxy { - override fun onHomeResumed() { - // no op + @Provides + fun provideNightlyProxy() = object : NightlyProxy { + override fun onHomeResumed() { + // no op + } } } + + @Binds + abstract fun bindsFcmHelper(fcmHelper: FdroidFcmHelper): FcmHelper } diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt similarity index 78% rename from vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt rename to vector/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt index 24ff00a353..8d41ce1e00 100755 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt @@ -20,6 +20,7 @@ package im.vector.app.push.fcm import android.app.Activity import android.content.Context import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import im.vector.app.fdroid.BackgroundSyncStarter import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver @@ -28,19 +29,19 @@ import javax.inject.Inject /** * This class has an alter ego in the gplay variant. */ -class FcmHelper @Inject constructor( +class FdroidFcmHelper @Inject constructor( private val context: Context, private val backgroundSyncStarter: BackgroundSyncStarter, -) { +) : FcmHelper { - fun isFirebaseAvailable(): Boolean = false + override fun isFirebaseAvailable(): Boolean = false /** * Retrieves the FCM registration token. * * @return the FCM token or null if not received from FCM */ - fun getFcmToken(): String? { + override fun getFcmToken(): String? { return null } @@ -49,7 +50,7 @@ class FcmHelper @Inject constructor( * * @param token the token to store */ - fun storeFcmToken(token: String?) { + override fun storeFcmToken(token: String?) { // No op } @@ -58,17 +59,17 @@ class FcmHelper @Inject constructor( * * @param activity the first launch Activity */ - fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { + override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { // No op } - fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) { + override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) { // try to stop all regardless of background mode activeSessionHolder.getSafeActiveSession()?.syncService()?.stopAnyBackgroundSync() AlarmSyncBroadcastReceiver.cancelAlarm(context) } - fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) { + override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) { backgroundSyncStarter.start(activeSessionHolder) } } diff --git a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt index 256c908a02..442f5f2eed 100644 --- a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt +++ b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt @@ -21,9 +21,11 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.services.GuardServiceStarter import im.vector.app.features.home.NightlyProxy import im.vector.app.nightly.FirebaseNightlyProxy +import im.vector.app.push.fcm.GoogleFcmHelper @InstallIn(SingletonComponent::class) @Module @@ -38,4 +40,7 @@ abstract class FlavorModule { @Binds abstract fun bindsNightlyProxy(nightlyProxy: FirebaseNightlyProxy): NightlyProxy + + @Binds + abstract fun bindsFcmHelper(fcmHelper: GoogleFcmHelper): FcmHelper } diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt index e7e3157f6b..d6180a9fe8 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt @@ -20,10 +20,10 @@ import androidx.activity.result.ActivityResultLauncher import androidx.fragment.app.FragmentActivity import com.google.firebase.messaging.FirebaseMessaging import im.vector.app.R +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.startAddGoogleAccountIntent import im.vector.app.features.settings.troubleshoot.TroubleshootTest -import im.vector.app.push.fcm.FcmHelper import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt index 8c21404d20..840bde77b1 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt @@ -23,10 +23,10 @@ import androidx.work.WorkInfo import androidx.work.WorkManager import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import im.vector.app.core.resources.StringProvider import im.vector.app.features.settings.troubleshoot.TroubleshootTest -import im.vector.app.push.fcm.FcmHelper import org.matrix.android.sdk.api.session.pushers.PusherState import javax.inject.Inject diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt similarity index 87% rename from vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt rename to vector/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt index a4eb9efc73..636f4f3189 100755 --- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt @@ -25,6 +25,7 @@ import com.google.firebase.messaging.FirebaseMessaging import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import timber.log.Timber import javax.inject.Inject @@ -33,23 +34,23 @@ import javax.inject.Inject * This class store the FCM token in SharedPrefs and ensure this token is retrieved. * It has an alter ego in the fdroid variant. */ -class FcmHelper @Inject constructor( +class GoogleFcmHelper @Inject constructor( context: Context, -) { +) : FcmHelper { companion object { private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" } private val sharedPrefs = DefaultSharedPreferences.getInstance(context) - fun isFirebaseAvailable(): Boolean = true + override fun isFirebaseAvailable(): Boolean = true /** * Retrieves the FCM registration token. * * @return the FCM token or null if not received from FCM */ - fun getFcmToken(): String? { + override fun getFcmToken(): String? { return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) } @@ -59,7 +60,7 @@ class FcmHelper @Inject constructor( * * @param token the token to store */ - fun storeFcmToken(token: String?) { + override fun storeFcmToken(token: String?) { sharedPrefs.edit { putString(PREFS_KEY_FCM_TOKEN, token) } @@ -70,7 +71,7 @@ class FcmHelper @Inject constructor( * * @param activity the first launch Activity */ - fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { + override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { // if (TextUtils.isEmpty(getFcmToken(activity))) { // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' if (checkPlayServices(activity)) { @@ -106,12 +107,12 @@ class FcmHelper @Inject constructor( } @Suppress("UNUSED_PARAMETER") - fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) { + override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) { // No op } @Suppress("UNUSED_PARAMETER") - fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) { + override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) { // No op } } diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index f31b5d915e..53222ab962 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -43,6 +43,7 @@ import dagger.hilt.android.HiltAndroidApp import im.vector.app.config.Config import im.vector.app.core.debug.FlipperProxy import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.resources.BuildMeta import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.call.webrtc.WebRtcCallManager @@ -60,7 +61,6 @@ import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.version.VersionProvider -import im.vector.app.push.fcm.FcmHelper import org.jitsi.meet.sdk.log.JitsiMeetDefaultLogHandler import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.auth.AuthenticationService diff --git a/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt new file mode 100644 index 0000000000..601722a036 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.pushers + +import android.app.Activity +import im.vector.app.core.di.ActiveSessionHolder + +interface FcmHelper { + fun isFirebaseAvailable(): Boolean + + /** + * Retrieves the FCM registration token. + * + * @return the FCM token or null if not received from FCM + */ + fun getFcmToken(): String? + + /** + * Store FCM token to the SharedPrefs + * TODO Store in realm + * + * @param token the token to store + */ + fun storeFcmToken(token: String?) + + /** + * onNewToken may not be called on application upgrade, so ensure my shared pref is set + * + * @param activity the first launch Activity + */ + fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) + + fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) + + fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) +} diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt index 724d3c7aa6..1f44ab3686 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt @@ -28,7 +28,6 @@ import im.vector.app.core.utils.getApplicationLabel import im.vector.app.features.VectorFeatures import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences -import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.launch import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.cache.CacheStrategy diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 4dd6f8cfba..fe57b9f735 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -44,6 +44,7 @@ import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.extensions.validateBackPressed import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorMenuProvider +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.utils.startSharePlainTextIntent @@ -79,7 +80,6 @@ import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel -import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAvailableUnifiedPushDistributors.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAvailableUnifiedPushDistributors.kt index acc0142924..89e7d8c204 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAvailableUnifiedPushDistributors.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAvailableUnifiedPushDistributors.kt @@ -19,9 +19,9 @@ package im.vector.app.features.settings.troubleshoot import android.content.Intent import androidx.activity.result.ActivityResultLauncher import im.vector.app.R +import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.resources.StringProvider -import im.vector.app.push.fcm.FcmHelper import javax.inject.Inject class TestAvailableUnifiedPushDistributors @Inject constructor( From b2d19152274463f8c552f62966f772fe64864e21 Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Sun, 7 Aug 2022 23:02:55 +0000 Subject: [PATCH 036/114] Translated using Weblate (Russian) Currently translated at 99.0% (2306 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 281262d02d..1fd522c7d5 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -1840,7 +1840,7 @@ Отправить историю запросов на обмен ключами Начать беседу %s чтобы люди знали, о чём эта комната. - Это начало вашей истории диалога с %s. + Это начало вашей переписки с %s. Экспорт аудита Личное сообщение Отправить электронную почту и номера телефонов From ed3b73a98990dbb6a80a147f90c5a157b0282ebe Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 9 Aug 2022 10:32:43 +0100 Subject: [PATCH 037/114] decouples the flavor code / OSS licenses activity launching --- .../fdroid/java/im/vector/app/di/FlavorModule.kt | 10 ++++++++++ .../app/{FlavorCode.kt => GoogleFlavorLegals.kt} | 14 ++++++++++++-- .../gplay/java/im/vector/app/di/FlavorModule.kt | 6 ++++++ .../app/features/settings/legals/FlavourLegals.kt} | 10 ++++++---- .../features/settings/legals/LegalsController.kt | 5 +++-- .../app/features/settings/legals/LegalsFragment.kt | 7 +++---- 6 files changed, 40 insertions(+), 12 deletions(-) rename vector/src/gplay/java/im/vector/app/{FlavorCode.kt => GoogleFlavorLegals.kt} (60%) rename vector/src/{fdroid/java/im/vector/app/FlavorCode.kt => main/java/im/vector/app/features/settings/legals/FlavourLegals.kt} (74%) diff --git a/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt b/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt index 5a7a527c3f..1936fbda8c 100644 --- a/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt +++ b/vector/src/fdroid/java/im/vector/app/di/FlavorModule.kt @@ -27,6 +27,7 @@ import im.vector.app.core.services.GuardServiceStarter import im.vector.app.fdroid.service.FDroidGuardServiceStarter import im.vector.app.features.home.NightlyProxy import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.settings.legals.FlavourLegals import im.vector.app.push.fcm.FdroidFcmHelper @InstallIn(SingletonComponent::class) @@ -45,6 +46,15 @@ abstract class FlavorModule { // no op } } + + @Provides + fun providesFlavorLegals() = object : FlavourLegals { + override fun hasThirdPartyNotices() = false + + override fun navigateToThirdPartyNotices(context: Context) { + // no op + } + } } @Binds diff --git a/vector/src/gplay/java/im/vector/app/FlavorCode.kt b/vector/src/gplay/java/im/vector/app/GoogleFlavorLegals.kt similarity index 60% rename from vector/src/gplay/java/im/vector/app/FlavorCode.kt rename to vector/src/gplay/java/im/vector/app/GoogleFlavorLegals.kt index 040296d755..32849b3741 100644 --- a/vector/src/gplay/java/im/vector/app/FlavorCode.kt +++ b/vector/src/gplay/java/im/vector/app/GoogleFlavorLegals.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Vector Ltd + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,5 +19,15 @@ package im.vector.app import android.content.Context import android.content.Intent import com.google.android.gms.oss.licenses.OssLicensesMenuActivity +import im.vector.app.features.settings.legals.FlavourLegals +import javax.inject.Inject -fun openOssLicensesMenuActivity(context: Context) = context.startActivity(Intent(context, OssLicensesMenuActivity::class.java)) +class GoogleFlavorLegals @Inject constructor() : FlavourLegals { + + override fun hasThirdPartyNotices() = true + + override fun navigateToThirdPartyNotices(context: Context) { + // See https://developers.google.com/android/guides/opensource + context.startActivity(Intent(context, OssLicensesMenuActivity::class.java)) + } +} diff --git a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt index 442f5f2eed..c97d1bff04 100644 --- a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt +++ b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt @@ -21,9 +21,11 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import im.vector.app.GoogleFlavorLegals import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.services.GuardServiceStarter import im.vector.app.features.home.NightlyProxy +import im.vector.app.features.settings.legals.FlavourLegals import im.vector.app.nightly.FirebaseNightlyProxy import im.vector.app.push.fcm.GoogleFcmHelper @@ -43,4 +45,8 @@ abstract class FlavorModule { @Binds abstract fun bindsFcmHelper(fcmHelper: GoogleFcmHelper): FcmHelper + + @Binds + abstract fun bindsFlavorLegals(legals: GoogleFlavorLegals): FlavourLegals } + diff --git a/vector/src/fdroid/java/im/vector/app/FlavorCode.kt b/vector/src/main/java/im/vector/app/features/settings/legals/FlavourLegals.kt similarity index 74% rename from vector/src/fdroid/java/im/vector/app/FlavorCode.kt rename to vector/src/main/java/im/vector/app/features/settings/legals/FlavourLegals.kt index 7d8ef22b44..a33ad23f7e 100644 --- a/vector/src/fdroid/java/im/vector/app/FlavorCode.kt +++ b/vector/src/main/java/im/vector/app/features/settings/legals/FlavourLegals.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Vector Ltd + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,11 @@ * limitations under the License. */ -package im.vector.app +package im.vector.app.features.settings.legals import android.content.Context -// No op -fun openOssLicensesMenuActivity(@Suppress("UNUSED_PARAMETER") context: Context) = Unit +interface FlavourLegals { + fun hasThirdPartyNotices(): Boolean + fun navigateToThirdPartyNotices(context: Context) +} diff --git a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsController.kt b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsController.kt index f88b73f36c..c64b2e51d3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsController.kt @@ -38,7 +38,8 @@ class LegalsController @Inject constructor( private val stringProvider: StringProvider, private val resources: Resources, private val elementLegals: ElementLegals, - private val errorFormatter: ErrorFormatter + private val errorFormatter: ErrorFormatter, + private val flavourLegals: FlavourLegals, ) : TypedEpoxyController() { var listener: Listener? = null @@ -134,7 +135,7 @@ class LegalsController @Inject constructor( clickListener { host.listener?.openThirdPartyNotice() } } // Only on Gplay - if (resources.getBoolean(R.bool.isGplay)) { + if (flavourLegals.hasThirdPartyNotices()) { discoveryPolicyItem { id("eltpn2") name(host.stringProvider.getString(R.string.settings_other_third_party_notices)) diff --git a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt index 9a4090ad1b..7bdcb9b5c9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt @@ -33,11 +33,11 @@ import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.discovery.ServerPolicy import im.vector.app.features.settings.VectorSettingsUrls -import im.vector.app.openOssLicensesMenuActivity import javax.inject.Inject class LegalsFragment @Inject constructor( - private val controller: LegalsController + private val controller: LegalsController, + private val flavourLegals: FlavourLegals, ) : VectorBaseFragment(), LegalsController.Listener { @@ -100,8 +100,7 @@ class LegalsFragment @Inject constructor( override fun openThirdPartyNoticeGplay() { if (firstThrottler.canHandle() is FirstThrottler.CanHandlerResult.Yes) { - // See https://developers.google.com/android/guides/opensource - openOssLicensesMenuActivity(requireActivity()) + flavourLegals.navigateToThirdPartyNotices(requireContext()) } } } From 045398d06feddff81be334cddf8c834a86383d68 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 9 Aug 2022 10:42:44 +0100 Subject: [PATCH 038/114] fixing import ordering and duplicated documentation --- .../app/features/debug/di/DebugModule.kt | 5 ++--- .../app/receivers/VectorDebugReceiver.kt | 2 +- .../im/vector/app/push/fcm/FdroidFcmHelper.kt | 15 --------------- ...ificationTroubleshootTestManagerFactory.kt | 2 +- .../java/im/vector/app/di/FlavorModule.kt | 1 - .../im/vector/app/push/fcm/GoogleFcmHelper.kt | 19 +------------------ .../im/vector/app/core/pushers/FcmHelper.kt | 13 +++++++------ .../features/navigation/DefaultNavigator.kt | 3 +-- 8 files changed, 13 insertions(+), 47 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt b/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt index 56df9fa0bc..b15f6dce62 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/di/DebugModule.kt @@ -23,10 +23,10 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import im.vector.app.core.debug.DebugReceiver -import im.vector.app.features.debug.DebugMenuActivity import im.vector.app.core.debug.DebugNavigator +import im.vector.app.core.debug.DebugReceiver import im.vector.app.core.debug.FlipperProxy +import im.vector.app.features.debug.DebugMenuActivity import im.vector.app.flipper.VectorFlipperProxy import im.vector.app.receivers.VectorDebugReceiver @@ -49,5 +49,4 @@ abstract class DebugModule { @Binds abstract fun bindsFlipperProxy(flipperProxy: VectorFlipperProxy): FlipperProxy - } diff --git a/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt b/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt index 827fc540f7..550dc055d9 100644 --- a/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt +++ b/vector/src/debug/java/im/vector/app/receivers/VectorDebugReceiver.kt @@ -22,8 +22,8 @@ import android.content.Intent import android.content.IntentFilter import android.content.SharedPreferences import androidx.core.content.edit -import im.vector.app.core.di.DefaultSharedPreferences import im.vector.app.core.debug.DebugReceiver +import im.vector.app.core.di.DefaultSharedPreferences import im.vector.app.core.utils.lsFiles import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt index 8d41ce1e00..5b83769116 100755 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt @@ -36,29 +36,14 @@ class FdroidFcmHelper @Inject constructor( override fun isFirebaseAvailable(): Boolean = false - /** - * Retrieves the FCM registration token. - * - * @return the FCM token or null if not received from FCM - */ override fun getFcmToken(): String? { return null } - /** - * Store FCM token to the SharedPrefs - * - * @param token the token to store - */ override fun storeFcmToken(token: String?) { // No op } - /** - * onNewToken may not be called on application upgrade, so ensure my shared pref is set - * - * @param activity the first launch Activity - */ override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { // No op } diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidNotificationTroubleshootTestManagerFactory.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidNotificationTroubleshootTestManagerFactory.kt index 86843f0e6f..d99afa59f7 100644 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidNotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FdroidNotificationTroubleshootTestManagerFactory.kt @@ -53,7 +53,7 @@ class FdroidNotificationTroubleshootTestManagerFactory @Inject constructor( private val testBatteryOptimization: TestBatteryOptimization, private val testNotification: TestNotification, private val vectorFeatures: VectorFeatures, -): NotificationTroubleshootTestManagerFactory { +) : NotificationTroubleshootTestManagerFactory { override fun create(fragment: Fragment): NotificationTroubleshootTestManager { val mgr = NotificationTroubleshootTestManager(fragment) diff --git a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt index c97d1bff04..393ea1e62a 100644 --- a/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt +++ b/vector/src/gplay/java/im/vector/app/di/FlavorModule.kt @@ -49,4 +49,3 @@ abstract class FlavorModule { @Binds abstract fun bindsFlavorLegals(legals: GoogleFlavorLegals): FlavourLegals } - diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt index 636f4f3189..d64847c124 100755 --- a/vector/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt @@ -45,32 +45,17 @@ class GoogleFcmHelper @Inject constructor( override fun isFirebaseAvailable(): Boolean = true - /** - * Retrieves the FCM registration token. - * - * @return the FCM token or null if not received from FCM - */ override fun getFcmToken(): String? { return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) } - /** - * Store FCM token to the SharedPrefs - * TODO Store in realm - * - * @param token the token to store - */ override fun storeFcmToken(token: String?) { + // TODO Store in realm sharedPrefs.edit { putString(PREFS_KEY_FCM_TOKEN, token) } } - /** - * onNewToken may not be called on application upgrade, so ensure my shared pref is set - * - * @param activity the first launch Activity - */ override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { // if (TextUtils.isEmpty(getFcmToken(activity))) { // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' @@ -106,12 +91,10 @@ class GoogleFcmHelper @Inject constructor( return resultCode == ConnectionResult.SUCCESS } - @Suppress("UNUSED_PARAMETER") override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) { // No op } - @Suppress("UNUSED_PARAMETER") override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) { // No op } diff --git a/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt index 601722a036..7b2c5e3959 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt @@ -25,22 +25,23 @@ interface FcmHelper { /** * Retrieves the FCM registration token. * - * @return the FCM token or null if not received from FCM + * @return the FCM token or null if not received from FCM. */ fun getFcmToken(): String? /** - * Store FCM token to the SharedPrefs - * TODO Store in realm + * Store FCM token to the SharedPrefs. * - * @param token the token to store + * @param token the token to store. */ fun storeFcmToken(token: String?) /** - * onNewToken may not be called on application upgrade, so ensure my shared pref is set + * onNewToken may not be called on application upgrade, so ensure my shared pref is set. * - * @param activity the first launch Activity + * @param activity the first launch Activity. + * @param pushersManager the instance to register the pusher on. + * @param registerPusher whether the pusher should be registered. */ fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 872f102a0d..e724084501 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -34,6 +34,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.SpaceStateHandler import im.vector.app.config.OnboardingVariant +import im.vector.app.core.debug.DebugNavigator import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.fatalError import im.vector.app.features.VectorFeatures @@ -51,7 +52,6 @@ import im.vector.app.features.crypto.recover.BootstrapBottomSheet import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider import im.vector.app.features.crypto.verification.VerificationBottomSheet -import im.vector.app.core.debug.DebugNavigator import im.vector.app.features.devtools.RoomDevToolActivity import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.arguments.TimelineArgs @@ -616,4 +616,3 @@ class DefaultNavigator @Inject constructor( context.startActivity(this) } } - From 319ec6fbf492fb47265170331445a98c20dc9b8d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 9 Aug 2022 11:25:55 +0100 Subject: [PATCH 039/114] removing now unused gplay resource --- vector/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index 0edaf5424e..1244a84bfd 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -307,7 +307,6 @@ android { isDefault = true versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}" - resValue "bool", "isGplay", "true" buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\"" buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\"" } @@ -317,7 +316,6 @@ android { versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}" - resValue "bool", "isGplay", "false" buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\"" buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\"" } From 6e1e31bac1e61aaec10e957d3c9340bb757c18e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 9 Aug 2022 09:53:08 +0200 Subject: [PATCH 040/114] Avoid crashes from unknown exceptions on lockscreen key migration. --- changelog.d/6769.bugfix | 1 + .../lockscreen/crypto/LockScreenKeysMigrator.kt | 2 +- .../migrations/MissingSystemKeyMigrator.kt | 17 ++++++++--------- .../crypto/migrations/SystemKeyV1Migrator.kt | 11 +++++++---- ...rTests.kt => LockScreenKeysMigratorTests.kt} | 6 +++--- .../migrations/MissingSystemKeyMigratorTests.kt | 10 +++++----- .../migrations/SystemKeyV1MigratorTests.kt | 4 +++- 7 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 changelog.d/6769.bugfix rename vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/{LockScreenTestMigratorTests.kt => LockScreenKeysMigratorTests.kt} (94%) diff --git a/changelog.d/6769.bugfix b/changelog.d/6769.bugfix new file mode 100644 index 0000000000..5d65bff449 --- /dev/null +++ b/changelog.d/6769.bugfix @@ -0,0 +1 @@ +Catch all exceptions on lockscreen system key migrations. diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt index 68acfcebf3..bb55ceb1b7 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt @@ -40,7 +40,7 @@ class LockScreenKeysMigrator @Inject constructor( suspend fun migrateIfNeeded() { if (legacyPinCodeMigrator.isMigrationNeeded()) { legacyPinCodeMigrator.migrate() - missingSystemKeyMigrator.migrate() + missingSystemKeyMigrator.migrateIfNeeded() } if (systemKeyV1Migrator.isMigrationNeeded() && versionProvider.get() >= Build.VERSION_CODES.M) { diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt index 75a68c66b7..4c33c14954 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt @@ -18,8 +18,6 @@ package im.vector.app.features.pin.lockscreen.crypto.migrations import android.annotation.SuppressLint import android.os.Build -import android.security.keystore.KeyPermanentlyInvalidatedException -import android.security.keystore.UserNotAuthenticatedException import im.vector.app.features.pin.lockscreen.crypto.KeyStoreCrypto import im.vector.app.features.pin.lockscreen.di.BiometricKeyAlias import im.vector.app.features.settings.VectorPreferences @@ -41,14 +39,15 @@ class MissingSystemKeyMigrator @Inject constructor( * If user had biometric auth enabled, ensure system key exists, creating one if needed. */ @SuppressLint("NewApi") - fun migrate() { + fun migrateIfNeeded() { if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && vectorPreferences.useBiometricsToUnlock()) { - try { - keystoreCryptoFactory.provide(systemKeyAlias, true).ensureKey() - } catch (e: KeyPermanentlyInvalidatedException) { - Timber.e("Could not automatically create biometric key because it was invalidated.", e) - } catch (e: UserNotAuthenticatedException) { - Timber.e("Could not automatically create biometric key because there are no enrolled biometric authenticators.", e) + val systemKeyStoreCrypto = keystoreCryptoFactory.provide(systemKeyAlias, true) + runCatching { + systemKeyStoreCrypto.ensureKey() + }.onFailure { e -> + Timber.e(e, "Could not automatically create biometric key. Biometric authentication will be disabled.") + systemKeyStoreCrypto.deleteKey() + vectorPreferences.setUseBiometricToUnlock(false) } } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1Migrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1Migrator.kt index 10c7505f27..748001af8b 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1Migrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1Migrator.kt @@ -17,10 +17,10 @@ package im.vector.app.features.pin.lockscreen.crypto.migrations import android.os.Build -import android.security.keystore.UserNotAuthenticatedException import androidx.annotation.RequiresApi import im.vector.app.features.pin.lockscreen.crypto.KeyStoreCrypto import im.vector.app.features.pin.lockscreen.di.BiometricKeyAlias +import im.vector.app.features.settings.VectorPreferences import timber.log.Timber import java.security.KeyStore import javax.inject.Inject @@ -32,6 +32,7 @@ class SystemKeyV1Migrator @Inject constructor( @BiometricKeyAlias private val systemKeyAlias: String, private val keyStore: KeyStore, private val keystoreCryptoFactory: KeyStoreCrypto.Factory, + private val vectorPreferences: VectorPreferences, ) { /** @@ -41,10 +42,12 @@ class SystemKeyV1Migrator @Inject constructor( fun migrate() { keyStore.deleteEntry(SYSTEM_KEY_ALIAS_V1) val systemKeyStoreCrypto = keystoreCryptoFactory.provide(systemKeyAlias, keyNeedsUserAuthentication = true) - try { + runCatching { systemKeyStoreCrypto.ensureKey() - } catch (e: UserNotAuthenticatedException) { - Timber.e("Could not migrate v1 biometric key because there are no enrolled biometric authenticators.", e) + }.onFailure { e -> + Timber.e(e, "Could not migrate v1 biometric key. Biometric authentication will be disabled.") + systemKeyStoreCrypto.deleteKey() + vectorPreferences.setUseBiometricToUnlock(false) } } diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenTestMigratorTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenKeysMigratorTests.kt similarity index 94% rename from vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenTestMigratorTests.kt rename to vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenKeysMigratorTests.kt index 73f71dbf2b..588fb12382 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenTestMigratorTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenKeysMigratorTests.kt @@ -26,7 +26,7 @@ import io.mockk.verify import kotlinx.coroutines.runBlocking import org.junit.Test -class LockScreenTestMigratorTests { +class LockScreenKeysMigratorTests { private val legacyPinCodeMigrator = mockk(relaxed = true) private val missingSystemKeyMigrator = mockk(relaxed = true) @@ -42,7 +42,7 @@ class LockScreenTestMigratorTests { runBlocking { migrator.migrateIfNeeded() } coVerify(exactly = 0) { legacyPinCodeMigrator.migrate() } - verify(exactly = 0) { missingSystemKeyMigrator.migrate() } + verify(exactly = 0) { missingSystemKeyMigrator.migrateIfNeeded() } // When migration is needed every { legacyPinCodeMigrator.isMigrationNeeded() } returns true @@ -50,7 +50,7 @@ class LockScreenTestMigratorTests { runBlocking { migrator.migrateIfNeeded() } coVerify { legacyPinCodeMigrator.migrate() } - verify { missingSystemKeyMigrator.migrate() } + verify { missingSystemKeyMigrator.migrateIfNeeded() } } @Test diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigratorTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigratorTests.kt index 3098187962..e211fc8a0e 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigratorTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigratorTests.kt @@ -44,7 +44,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns true - missingSystemKeyMigrator.migrate() + missingSystemKeyMigrator.migrateIfNeeded() verify { keyStoreCryptoMock.ensureKey() } } @@ -57,7 +57,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns true - invoking { missingSystemKeyMigrator.migrate() } shouldNotThrow KeyPermanentlyInvalidatedException::class + invoking { missingSystemKeyMigrator.migrateIfNeeded() } shouldNotThrow KeyPermanentlyInvalidatedException::class } @Test @@ -68,7 +68,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns true - invoking { missingSystemKeyMigrator.migrate() } shouldNotThrow UserNotAuthenticatedException::class + invoking { missingSystemKeyMigrator.migrateIfNeeded() } shouldNotThrow UserNotAuthenticatedException::class } @Test @@ -79,7 +79,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns false - missingSystemKeyMigrator.migrate() + missingSystemKeyMigrator.migrateIfNeeded() verify(exactly = 0) { keyStoreCryptoMock.ensureKey() } } @@ -93,7 +93,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns false - missingSystemKeyMigrator.migrate() + missingSystemKeyMigrator.migrateIfNeeded() verify(exactly = 0) { keyStoreCryptoMock.ensureKey() } } diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1MigratorTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1MigratorTests.kt index 5cbb828f71..825b251f3e 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1MigratorTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1MigratorTests.kt @@ -18,6 +18,7 @@ package im.vector.app.features.pin.lockscreen.crypto.migrations import android.security.keystore.UserNotAuthenticatedException import im.vector.app.features.pin.lockscreen.crypto.KeyStoreCrypto +import im.vector.app.features.settings.VectorPreferences import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -31,7 +32,8 @@ class SystemKeyV1MigratorTests { private val keyStoreCryptoFactory = mockk() private val keyStore = mockk(relaxed = true) - private val systemKeyV1Migrator = SystemKeyV1Migrator("vector.system_new", keyStore, keyStoreCryptoFactory) + private val vectorPreferences = mockk(relaxed = true) + private val systemKeyV1Migrator = SystemKeyV1Migrator("vector.system_new", keyStore, keyStoreCryptoFactory, vectorPreferences) @Test fun isMigrationNeededReturnsTrueIfV1KeyExists() { From 2f4725cfe96172827c8d62a5b4aaa76e74ae25f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 8 Aug 2022 12:22:05 +0200 Subject: [PATCH 041/114] Prevent crash while validating biometric key. --- changelog.d/6768.bugfix | 1 + .../features/pin/lockscreen/crypto/KeyStoreCrypto.kt | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) create mode 100644 changelog.d/6768.bugfix diff --git a/changelog.d/6768.bugfix b/changelog.d/6768.bugfix new file mode 100644 index 0000000000..764386132b --- /dev/null +++ b/changelog.d/6768.bugfix @@ -0,0 +1 @@ +Fix crash when biometric key is used when coming back to foreground and KeyStore reports that the device is still locked. diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt index d37c11ed69..a42ce3a9b7 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt @@ -20,13 +20,13 @@ import android.annotation.SuppressLint import android.content.Context import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException -import android.security.keystore.UserNotAuthenticatedException import android.util.Base64 import androidx.annotation.VisibleForTesting import androidx.biometric.BiometricPrompt import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.securestorage.SecretStoringUtils import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import java.security.Key @@ -113,14 +113,8 @@ class KeyStoreCrypto @AssistedInject constructor( fun hasValidKey(): Boolean { val keyExists = hasKey() return if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && keyExists) { - try { - ensureKey() - true - } catch (e: KeyPermanentlyInvalidatedException) { - false - } catch (e: UserNotAuthenticatedException) { - false - } + val initializedKey = tryOrNull("Error validating lockscreen system key.") { ensureKey() } + initializedKey != null } else { keyExists } From dfc8526b474c979fc8a2d5b128f9fe88551e7d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 9 Aug 2022 09:54:05 +0200 Subject: [PATCH 042/114] Refactor lockscreen implementation. Try to fix issues and simplify flow. --- .../biometrics/BiometricHelperTests.kt | 18 +-- .../crypto/LockScreenKeyRepositoryTests.kt | 4 - .../im/vector/app/features/pin/PinFragment.kt | 39 +++--- .../lockscreen/biometrics/BiometricHelper.kt | 44 +++---- .../configuration/LockScreenConfiguration.kt | 6 +- .../LockScreenConfiguratorProvider.kt | 58 --------- .../pin/lockscreen/di/LockScreenModule.kt | 5 + .../pin/lockscreen/ui/LockScreenFragment.kt | 24 ++-- .../pin/lockscreen/ui/LockScreenViewEvent.kt | 1 + .../pin/lockscreen/ui/LockScreenViewModel.kt | 114 +++++++++--------- .../pin/lockscreen/ui/LockScreenViewState.kt | 6 +- .../settings/VectorSettingsPinFragment.kt | 10 +- .../fragment/LockScreenViewModelTests.kt | 69 ++++++----- 13 files changed, 183 insertions(+), 215 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguratorProvider.kt diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt index 53c154ae30..2ec69cf0b1 100644 --- a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt @@ -31,7 +31,6 @@ import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import im.vector.app.TestBuildVersionSdkIntProvider import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository @@ -54,8 +53,10 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest +import org.amshove.kluent.coInvoking import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldThrow import org.junit.Before import org.junit.Ignore import org.junit.Test @@ -239,36 +240,35 @@ class BiometricHelperTests { @Test @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) // Due to some issues with mockk and CryptoObject initialization - fun authenticateCreatesSystemKeyIfNeededOnSuccessOnAndroidM() = runTest { + fun enableAuthenticationDeletesSystemKeyOnFailure() = runTest { buildVersionSdkIntProvider.value = Build.VERSION_CODES.M - every { lockScreenKeyRepository.isSystemKeyValid() } returns true val mockAuthChannel = Channel(capacity = 1) val biometricUtils = spyk(createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = true))) { every { createAuthChannel() } returns mockAuthChannel every { authenticateWithPromptInternal(any(), any(), any()) } returns mockk() } + every { lockScreenKeyRepository.deleteSystemKey() } returns Unit val latch = CountDownLatch(1) val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java) ActivityScenario.launch(intent).onActivity { activity -> activity.lifecycleScope.launch { + val exception = IllegalStateException("Some error") launch { - mockAuthChannel.send(true) - mockAuthChannel.close() + mockAuthChannel.close(exception) } - biometricUtils.authenticate(activity).collect() + coInvoking { biometricUtils.enableAuthentication(activity).collect() } shouldThrow exception latch.countDown() } } latch.await(1, TimeUnit.SECONDS) - verify { lockScreenKeyRepository.ensureSystemKey() } + verify { lockScreenKeyRepository.deleteSystemKey() } } private fun createBiometricHelper(configuration: LockScreenConfiguration): BiometricHelper { val context = InstrumentationRegistry.getInstrumentation().targetContext - val configProvider = LockScreenConfiguratorProvider(configuration) - return BiometricHelper(context, lockScreenKeyRepository, configProvider, biometricManager, buildVersionSdkIntProvider) + return BiometricHelper(configuration, context, lockScreenKeyRepository, biometricManager, buildVersionSdkIntProvider) } private fun createDefaultConfiguration( diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt index 924dbfee9e..8d14ca9153 100644 --- a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt @@ -17,8 +17,6 @@ package im.vector.app.features.pin.lockscreen.crypto import androidx.test.platform.app.InstrumentationRegistry -import im.vector.app.features.pin.lockscreen.crypto.migrations.LegacyPinCodeMigrator -import im.vector.app.features.settings.VectorPreferences import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk @@ -44,8 +42,6 @@ class LockScreenKeyRepositoryTests { } private lateinit var lockScreenKeyRepository: LockScreenKeyRepository - private val legacyPinCodeMigrator: LegacyPinCodeMigrator = mockk(relaxed = true) - private val vectorPreferences: VectorPreferences = mockk(relaxed = true) private val keyStore: KeyStore by lazy { KeyStore.getInstance(LockScreenCryptoConstants.ANDROID_KEY_STORE).also { it.load(null) } diff --git a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt index 69548f24f0..1688452167 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt @@ -24,6 +24,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import com.airbnb.mvrx.args +import com.airbnb.mvrx.asMavericksArgs import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.replaceFragment @@ -33,7 +34,7 @@ import im.vector.app.databinding.FragmentPinBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs import im.vector.app.features.pin.lockscreen.biometrics.BiometricAuthError -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider +import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.ui.AuthMethod import im.vector.app.features.pin.lockscreen.ui.LockScreenFragment @@ -51,7 +52,7 @@ data class PinArgs( class PinFragment @Inject constructor( private val pinCodeStore: PinCodeStore, private val vectorPreferences: VectorPreferences, - private val configuratorProvider: LockScreenConfiguratorProvider, + private val defaultConfiguration: LockScreenConfiguration, ) : VectorBaseFragment() { private val fragmentArgs: PinArgs by args() @@ -81,21 +82,17 @@ class PinFragment @Inject constructor( vectorBaseActivity.finish() } } - - configuratorProvider.updateDefaultConfiguration { - copy( - mode = LockScreenMode.CREATE, - title = getString(R.string.create_pin_title), - needsNewCodeValidation = true, - newCodeConfirmationTitle = getString(R.string.create_pin_confirm_title), - ) - } + createFragment.arguments = defaultConfiguration.copy( + mode = LockScreenMode.CREATE, + title = getString(R.string.create_pin_title), + needsNewCodeValidation = true, + newCodeConfirmationTitle = getString(R.string.create_pin_confirm_title), + ).asMavericksArgs() replaceFragment(R.id.pinFragmentContainer, createFragment) } private fun showAuthFragment() { val authFragment = LockScreenFragment() - val canUseBiometrics = vectorPreferences.useBiometricsToUnlock() authFragment.onLeftButtonClickedListener = View.OnClickListener { displayForgotPinWarningDialog() } authFragment.lockScreenListener = object : LockScreenListener { override fun onAuthenticationFailure(authMethod: AuthMethod) { @@ -133,18 +130,12 @@ class PinFragment @Inject constructor( .show() } } - configuratorProvider.updateDefaultConfiguration { - copy( - mode = LockScreenMode.VERIFY, - title = getString(R.string.auth_pin_title), - isStrongBiometricsEnabled = isStrongBiometricsEnabled && canUseBiometrics, - isWeakBiometricsEnabled = isWeakBiometricsEnabled && canUseBiometrics, - isDeviceCredentialUnlockEnabled = isDeviceCredentialUnlockEnabled && canUseBiometrics, - autoStartBiometric = canUseBiometrics, - leftButtonTitle = getString(R.string.auth_pin_forgot), - clearCodeOnError = true, - ) - } + authFragment.arguments = defaultConfiguration.copy( + mode = LockScreenMode.VERIFY, + title = getString(R.string.auth_pin_title), + leftButtonTitle = getString(R.string.auth_pin_forgot), + clearCodeOnError = true, + ).asMavericksArgs() replaceFragment(R.id.pinFragmentContainer, authFragment) } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt index ae4fa637b4..9bcf6e4264 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt @@ -31,10 +31,12 @@ import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext import im.vector.app.R import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository import im.vector.app.features.pin.lockscreen.ui.fallbackprompt.FallbackBiometricDialogFragment import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck @@ -54,22 +56,24 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import java.security.KeyStore import javax.crypto.Cipher -import javax.inject.Inject import kotlin.coroutines.CoroutineContext /** * This is a helper to manage system authentication (biometric and other types) and the system key. */ -class BiometricHelper @Inject constructor( +class BiometricHelper @AssistedInject constructor( + @Assisted private val configuration: LockScreenConfiguration, @ApplicationContext private val context: Context, private val lockScreenKeyRepository: LockScreenKeyRepository, - private val configurationProvider: LockScreenConfiguratorProvider, private val biometricManager: BiometricManager, private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, ) { private var prompt: BiometricPrompt? = null - private val configuration: LockScreenConfiguration get() = configurationProvider.currentConfiguration + @AssistedFactory + interface BiometricHelperFactory { + fun create(configuration: LockScreenConfiguration): BiometricHelper + } /** * Returns true if a weak biometric method (i.e.: some face or iris unlock implementations) can be used. @@ -174,16 +178,18 @@ class BiometricHelper @Inject constructor( when (val exception = result.exceptionOrNull()) { null -> result.getOrNull()?.let { emit(it) } else -> { - // Exception found, stop collecting, throw it and remove the prompt reference + // Exception found: + // 1. Stop collecting. + // 2. Remove the system key if we were creating it. + // 3. Throw the exception and remove the prompt reference + if (!checkSystemKeyExists) { + lockScreenKeyRepository.deleteSystemKey() + } prompt = null throw exception } } } - // Generates the system key on successful authentication - if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) { - lockScreenKeyRepository.ensureSystemKey() - } // Channel is closed, remove prompt reference prompt = null } @@ -213,11 +219,11 @@ class BiometricHelper @Inject constructor( .setAllowedAuthenticators(authenticators) .build() - return BiometricPrompt(activity, executor, callback).also { + return BiometricPrompt(activity, executor, callback).also { prompt -> showFallbackFragmentIfNeeded(activity, channel.receiveAsFlow(), executor.asCoroutineDispatcher()) { // For some reason this seems to be needed unless we want to receive a fragment transaction exception delay(1L) - it.authenticate(promptInfo, cryptoObject) + prompt.authenticate(promptInfo, cryptoObject) } } } @@ -253,11 +259,9 @@ class BiometricHelper @Inject constructor( ): BiometricPrompt.AuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() { private val scope = CoroutineScope(coroutineContext) override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - scope.launch { - // Error is a terminal event, should close both the Channel and the CoroutineScope to free resources. - channel.close(BiometricAuthError(errorCode, errString.toString())) - scope.cancel() - } + // Error is a terminal event, should close both the Channel and the CoroutineScope to free resources. + channel.close(BiometricAuthError(errorCode, errString.toString())) + scope.cancel() } override fun onAuthenticationFailed() { @@ -274,10 +278,8 @@ class BiometricHelper @Inject constructor( scope.cancel() } } else { - scope.launch { - channel.close(IllegalStateException("System key was not valid after authentication.")) - scope.cancel() - } + channel.close(IllegalStateException("System key was not valid after authentication.")) + scope.cancel() } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguration.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguration.kt index 8f3e67dfe5..12846c254c 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguration.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguration.kt @@ -16,9 +16,13 @@ package im.vector.app.features.pin.lockscreen.configuration +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + /** * Configuration to be used by the lockscreen feature. */ +@Parcelize data class LockScreenConfiguration( /** Which mode should the UI display, [LockScreenMode.VERIFY] or [LockScreenMode.CREATE]. */ val mode: LockScreenMode, @@ -56,4 +60,4 @@ data class LockScreenConfiguration( val biometricSubtitle: String? = null, /** Text for the cancel button of the Biometric prompt dialog. Optional. */ val biometricCancelButtonTitle: String? = null, -) +) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguratorProvider.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguratorProvider.kt deleted file mode 100644 index 338ac66125..0000000000 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguratorProvider.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.pin.lockscreen.configuration - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import javax.inject.Inject -import javax.inject.Singleton - -/** - * Class used to hold both the [defaultConfiguration] and an updated version in [currentConfiguration]. - */ -@Singleton -class LockScreenConfiguratorProvider @Inject constructor( - /** Default [LockScreenConfiguration], any derived configuration created using [updateDefaultConfiguration] will use this as a base. */ - val defaultConfiguration: LockScreenConfiguration, -) { - - private val mutableConfigurationFlow = MutableStateFlow(defaultConfiguration) - - /** - * A [Flow] that emits any changes in configuration. - */ - val configurationFlow: Flow = mutableConfigurationFlow - - /** - * The current configuration to be read and used. - */ - val currentConfiguration get() = mutableConfigurationFlow.value - - /** - * Applies the changes in [block] to the [defaultConfiguration] to generate a new [currentConfiguration]. - */ - fun updateDefaultConfiguration(block: LockScreenConfiguration.() -> LockScreenConfiguration) { - mutableConfigurationFlow.value = defaultConfiguration.block() - } - - /** - * Resets the [currentConfiguration] to the [defaultConfiguration]. - */ - fun reset() { - mutableConfigurationFlow.value = defaultConfiguration - } -} diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/di/LockScreenModule.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/di/LockScreenModule.kt index fb333b96bb..811a66f3af 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/di/LockScreenModule.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/di/LockScreenModule.kt @@ -16,8 +16,10 @@ package im.vector.app.features.pin.lockscreen.di +import android.app.KeyguardManager import android.content.Context import androidx.biometric.BiometricManager +import androidx.core.content.getSystemService import dagger.Binds import dagger.Module import dagger.Provides @@ -83,6 +85,9 @@ object LockScreenModule { SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider), buildVersionSdkIntProvider, ) + + @Provides + fun provideKeyguardManager(context: Context): KeyguardManager = context.getSystemService()!! } @Module diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt index 9eea61ac82..9a618ce939 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt @@ -34,6 +34,10 @@ import im.vector.app.databinding.FragmentLockScreenBinding import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.views.LockScreenCodeView +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class LockScreenFragment : VectorBaseFragment() { @@ -55,22 +59,12 @@ class LockScreenFragment : VectorBaseFragment() { handleEvent(it) } - withState(viewModel) { state -> - if (state.lockScreenConfiguration.mode == LockScreenMode.CREATE) return@withState - - viewLifecycleOwner.lifecycleScope.launchWhenResumed { - if (state.canUseBiometricAuth && state.isBiometricKeyInvalidated) { - lockScreenListener?.onBiometricKeyInvalidated() - } else if (state.showBiometricPromptAutomatically) { + viewModel.stateFlow.distinctUntilChangedBy { it.showBiometricPromptAutomatically } + .filter { it.showBiometricPromptAutomatically } + .onEach { showBiometricPrompt() } - } - } - } - - override fun onDestroy() { - super.onDestroy() - viewModel.reset() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun invalidate() = withState(viewModel) { state -> @@ -83,6 +77,7 @@ class LockScreenFragment : VectorBaseFragment() { setupTitleView(views.titleTextView, false, state.lockScreenConfiguration) } } + renderDeleteOrFingerprintButtons(views, views.codeView.enteredDigits) } @@ -123,6 +118,7 @@ class LockScreenFragment : VectorBaseFragment() { is LockScreenViewEvent.AuthSuccessful -> lockScreenListener?.onAuthenticationSuccess(viewEvent.method) is LockScreenViewEvent.AuthFailure -> onAuthFailure(viewEvent.method) is LockScreenViewEvent.AuthError -> onAuthError(viewEvent.method, viewEvent.throwable) + is LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage -> lockScreenListener?.onBiometricKeyInvalidated() } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt index cbde16876c..e340486bc0 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt @@ -24,4 +24,5 @@ sealed class LockScreenViewEvent : VectorViewEvents { data class AuthSuccessful(val method: AuthMethod) : LockScreenViewEvent() data class AuthFailure(val method: AuthMethod) : LockScreenViewEvent() data class AuthError(val method: AuthMethod, val throwable: Throwable) : LockScreenViewEvent() + object ShowBiometricKeyInvalidatedMessage : LockScreenViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt index 79c1967670..417ebdbd93 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt @@ -17,12 +17,12 @@ package im.vector.app.features.pin.lockscreen.ui import android.annotation.SuppressLint +import android.app.KeyguardManager import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException +import androidx.annotation.VisibleForTesting import androidx.fragment.app.FragmentActivity import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.ViewModelContext -import com.airbnb.mvrx.withState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -31,26 +31,28 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.pin.lockscreen.biometrics.BiometricAuthError import im.vector.app.features.pin.lockscreen.biometrics.BiometricHelper -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeysMigrator import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds class LockScreenViewModel @AssistedInject constructor( @Assisted val initialState: LockScreenViewState, private val pinCodeHelper: PinCodeHelper, - private val biometricHelper: BiometricHelper, + biometricHelperFactory: BiometricHelper.BiometricHelperFactory, private val lockScreenKeysMigrator: LockScreenKeysMigrator, - private val configuratorProvider: LockScreenConfiguratorProvider, - private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, + private val versionProvider: BuildVersionSdkIntProvider, + private val keyguardManager: KeyguardManager, ) : VectorViewModel(initialState) { @AssistedFactory @@ -58,27 +60,9 @@ class LockScreenViewModel @AssistedInject constructor( override fun create(initialState: LockScreenViewState): LockScreenViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - override fun initialState(viewModelContext: ViewModelContext): LockScreenViewState { - return LockScreenViewState( - lockScreenConfiguration = DUMMY_CONFIGURATION, - canUseBiometricAuth = false, - showBiometricPromptAutomatically = false, - pinCodeState = PinCodeState.Idle, - isBiometricKeyInvalidated = false, - ) - } - - private val DUMMY_CONFIGURATION = LockScreenConfiguration( - mode = LockScreenMode.VERIFY, - pinCodeLength = 4, - isStrongBiometricsEnabled = false, - isDeviceCredentialUnlockEnabled = false, - isWeakBiometricsEnabled = false, - needsNewCodeValidation = false, - ) - } + private val biometricHelper = biometricHelperFactory.create(initialState.lockScreenConfiguration) private var firstEnteredCode: String? = null @@ -86,12 +70,20 @@ class LockScreenViewModel @AssistedInject constructor( private var isSystemAuthTemporarilyDisabledByBiometricPrompt = false init { - // We need this to run synchronously before we start reading the configurations - runBlocking { lockScreenKeysMigrator.migrateIfNeeded() } + viewModelScope.launch { + // Wait until the keyguard is unlocked before performing migrations, it might cause crashes otherwise on Android 12 and 12L + waitUntilKeyguardIsUnlocked() + // Migrate pin code / system keys if needed + lockScreenKeysMigrator.migrateIfNeeded() + // Update initial state with biometric info + updateStateWithBiometricInfo() - configuratorProvider.configurationFlow - .onEach { updateConfiguration(it) } - .launchIn(viewModelScope) + val state = awaitState() + // If when initialized we detect a key invalidation, we should show an error message. + if (state.isBiometricKeyInvalidated) { + onBiometricKeyInvalidated() + } + } } override fun handle(action: LockScreenAction) { @@ -141,13 +133,18 @@ class LockScreenViewModel @AssistedInject constructor( private fun showBiometricPrompt(activity: FragmentActivity) = flow { emitAll(biometricHelper.authenticate(activity)) }.catch { error -> - if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && error is KeyPermanentlyInvalidatedException) { - removeBiometricAuthentication() - } else if (error is BiometricAuthError && error.isAuthDisabledError) { - isSystemAuthTemporarilyDisabledByBiometricPrompt = true - updateStateWithBiometricInfo() + when { + versionProvider.get() >= Build.VERSION_CODES.M && error is KeyPermanentlyInvalidatedException -> { + onBiometricKeyInvalidated() + } + else -> { + if (error is BiometricAuthError && error.isAuthDisabledError) { + isSystemAuthTemporarilyDisabledByBiometricPrompt = true + updateStateWithBiometricInfo() + } + _viewEvents.post(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, error)) + } } - _viewEvents.post(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, error)) }.onEach { success -> _viewEvents.post( if (success) LockScreenViewEvent.AuthSuccessful(AuthMethod.BIOMETRICS) @@ -155,24 +152,22 @@ class LockScreenViewModel @AssistedInject constructor( ) }.launchIn(viewModelScope) - fun reset() { - configuratorProvider.reset() - } - - private fun removeBiometricAuthentication() { + private suspend fun onBiometricKeyInvalidated() { biometricHelper.disableAuthentication() updateStateWithBiometricInfo() + _viewEvents.post(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) } - private fun updateStateWithBiometricInfo() { - val configuration = withState(this) { it.lockScreenConfiguration } - val canUseBiometricAuth = configuration.mode == LockScreenMode.VERIFY && + @SuppressLint("NewApi") + private suspend fun updateStateWithBiometricInfo() { + // This is a terrible hack, but I found no other way to ensure this would be called only after the device is considered unlocked on Android 12+ + waitUntilKeyguardIsUnlocked() + setState { + val isBiometricKeyInvalidated = biometricHelper.hasSystemKey && !biometricHelper.isSystemKeyValid + val canUseBiometricAuth = lockScreenConfiguration.mode == LockScreenMode.VERIFY && !isSystemAuthTemporarilyDisabledByBiometricPrompt && biometricHelper.isSystemAuthEnabledAndValid - val isBiometricKeyInvalidated = biometricHelper.hasSystemKey && !biometricHelper.isSystemKeyValid - val showBiometricPromptAutomatically = canUseBiometricAuth && - configuration.autoStartBiometric - setState { + val showBiometricPromptAutomatically = canUseBiometricAuth && lockScreenConfiguration.autoStartBiometric copy( canUseBiometricAuth = canUseBiometricAuth, showBiometricPromptAutomatically = showBiometricPromptAutomatically, @@ -181,8 +176,19 @@ class LockScreenViewModel @AssistedInject constructor( } } - private fun updateConfiguration(configuration: LockScreenConfiguration) { - setState { copy(lockScreenConfiguration = configuration) } - updateStateWithBiometricInfo() + /** + * Wait until the device is unlocked. There seems to be a behavior change on Android 12 that makes [KeyguardManager.isDeviceLocked] return `false` even + * after an Activity's `onResume` method. If we mix that with the system keys needing the device to be unlocked before they're used, we get crashes. + * See issue [#6768](https://github.com/vector-im/element-android/issues/6768). + */ + @SuppressLint("NewApi") + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal suspend fun waitUntilKeyguardIsUnlocked() { + if (versionProvider.get() < Build.VERSION_CODES.S) return + withTimeoutOrNull(5.seconds) { + while (keyguardManager.isDeviceLocked) { + delay(50.milliseconds) + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt index 8d2037953b..f689e1faf1 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt @@ -25,7 +25,11 @@ data class LockScreenViewState( val showBiometricPromptAutomatically: Boolean, val pinCodeState: PinCodeState, val isBiometricKeyInvalidated: Boolean, -) : MavericksState +) : MavericksState { + constructor(lockScreenConfiguration: LockScreenConfiguration) : this( + lockScreenConfiguration, false, false, PinCodeState.Idle, false + ) +} sealed class PinCodeState { object Idle : PinCodeState() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt index 4d95fc362b..db402758f1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt @@ -28,6 +28,8 @@ import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.PinMode import im.vector.app.features.pin.lockscreen.biometrics.BiometricHelper +import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration +import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse @@ -38,12 +40,15 @@ class VectorSettingsPinFragment @Inject constructor( private val pinCodeStore: PinCodeStore, private val navigator: Navigator, private val notificationDrawerManager: NotificationDrawerManager, - private val biometricHelper: BiometricHelper, + biometricHelperFactory: BiometricHelper.BiometricHelperFactory, + defaultLockScreenConfiguration: LockScreenConfiguration, ) : VectorSettingsBaseFragment() { override var titleRes = R.string.settings_security_application_protection_screen_title override val preferenceXmlRes = R.xml.vector_settings_pin + private val biometricHelper = biometricHelperFactory.create(defaultLockScreenConfiguration.copy(mode = LockScreenMode.CREATE)) + private val usePinCodePref by lazy { findPreference(VectorPreferences.SETTINGS_SECURITY_USE_PIN_CODE_FLAG)!! } @@ -102,9 +107,10 @@ class VectorSettingsPinFragment @Inject constructor( }.onFailure { showEnableBiometricErrorMessage() } + updateBiometricPrefState(isPinCodeChecked = usePinCodePref.isChecked) } - false + true } else { disableBiometricAuthentication() true diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt index 9e80bb490b..18dfdf9145 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt @@ -16,6 +16,7 @@ package im.vector.app.features.pin.lockscreen.fragment +import android.app.KeyguardManager import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException import androidx.fragment.app.FragmentActivity @@ -23,7 +24,6 @@ import com.airbnb.mvrx.test.MvRxTestRule import com.airbnb.mvrx.withState import im.vector.app.features.pin.lockscreen.biometrics.BiometricHelper import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeysMigrator import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper @@ -42,6 +42,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeFalse @@ -57,7 +58,15 @@ class LockScreenViewModelTests { private val pinCodeHelper = mockk(relaxed = true) private val biometricHelper = mockk(relaxed = true) + private val biometricHelperFactory = object : BiometricHelper.BiometricHelperFactory { + override fun create(configuration: LockScreenConfiguration): BiometricHelper { + return biometricHelper + } + } private val keysMigrator = mockk(relaxed = true) + private val keyguardManager = mockk(relaxed = true) { + every { isDeviceLocked } returns false + } private val versionProvider = TestBuildVersionSdkIntProvider() @Before @@ -68,19 +77,36 @@ class LockScreenViewModelTests { @Test fun `init migrates old keys to new ones if needed`() { val initialState = createViewState() - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coVerify { keysMigrator.migrateIfNeeded() } } + @Test + fun `init updates the initial state with biometric info`() = runTest { + every { biometricHelper.isSystemAuthEnabledAndValid } returns true + val initialState = createViewState() + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) + advanceUntilIdle() + val newState = viewModel.awaitState() + newState shouldNotBeEqualTo initialState + } + + @Test + fun `Updating the initial state with biometric info waits until device is unlocked on Android 12+`() = runTest { + val initialState = createViewState() + versionProvider.value = Build.VERSION_CODES.S + LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) + advanceUntilIdle() + verify { keyguardManager.isDeviceLocked } + } + @Test fun `when ViewModel is instantiated initialState is updated with biometric info`() { val initialState = createViewState() - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) // This should set canUseBiometricAuth to true every { biometricHelper.isSystemAuthEnabledAndValid } returns true - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val newState = withState(viewModel) { it } initialState shouldNotBeEqualTo newState } @@ -88,8 +114,7 @@ class LockScreenViewModelTests { @Test fun `when onPinCodeEntered is called in VERIFY mode, the code is verified and the result is emitted as a ViewEvent`() = runTest { val initialState = createViewState() - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coEvery { pinCodeHelper.verifyPinCode(any()) } returns true val events = viewModel.test().viewEvents @@ -113,8 +138,7 @@ class LockScreenViewModelTests { fun `when onPinCodeEntered is called in CREATE mode with no confirmation needed it creates the pin code`() = runTest { val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = false) val initialState = createViewState(lockScreenConfiguration = configuration) - val configProvider = LockScreenConfiguratorProvider(configuration) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val events = viewModel.test().viewEvents events.assertNoValues() @@ -128,9 +152,8 @@ class LockScreenViewModelTests { @Test fun `when onPinCodeEntered is called twice in CREATE mode with confirmation needed it verifies and creates the pin code`() = runTest { val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = true) - val configProvider = LockScreenConfiguratorProvider(configuration) val initialState = createViewState(lockScreenConfiguration = configuration) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val events = viewModel.test().viewEvents events.assertNoValues() @@ -149,8 +172,7 @@ class LockScreenViewModelTests { fun `when onPinCodeEntered is called in CREATE mode with incorrect confirmation it clears the pin code`() = runTest { val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = true) val initialState = createViewState(lockScreenConfiguration = configuration) - val configProvider = LockScreenConfiguratorProvider(configuration) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val events = viewModel.test().viewEvents events.assertNoValues() @@ -170,8 +192,7 @@ class LockScreenViewModelTests { @Test fun `onPinCodeEntered handles exceptions`() = runTest { val initialState = createViewState() - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val exception = IllegalStateException("Something went wrong") coEvery { pinCodeHelper.verifyPinCode(any()) } throws exception @@ -187,39 +208,34 @@ class LockScreenViewModelTests { fun `when showBiometricPrompt catches a KeyPermanentlyInvalidatedException it disables biometric authentication`() = runTest { versionProvider.value = Build.VERSION_CODES.M - every { biometricHelper.isSystemAuthEnabledAndValid } returns true - every { biometricHelper.isSystemKeyValid } returns true + every { biometricHelper.isSystemKeyValid } returns false val exception = KeyPermanentlyInvalidatedException() coEvery { biometricHelper.authenticate(any()) } throws exception - coEvery { biometricHelper.disableAuthentication() } coAnswers { - every { biometricHelper.isSystemAuthEnabledAndValid } returns false - } val configuration = createDefaultConfiguration(mode = LockScreenMode.VERIFY, needsNewCodeValidation = true, isBiometricsEnabled = true) - val configProvider = LockScreenConfiguratorProvider(configuration) val initialState = createViewState( canUseBiometricAuth = true, isBiometricKeyInvalidated = false, lockScreenConfiguration = configuration ) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val events = viewModel.test().viewEvents events.assertNoValues() viewModel.handle(LockScreenAction.ShowBiometricPrompt(mockk())) - events.assertValues(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, exception)) + events.assertValues(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) verify { biometricHelper.disableAuthentication() } // System key was deleted, biometric auth should be disabled + every { biometricHelper.isSystemAuthEnabledAndValid } returns false val newState = viewModel.awaitState() newState.canUseBiometricAuth.shouldBeFalse() } @Test fun `when showBiometricPrompt receives an event it propagates it as a ViewEvent`() = runTest { - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coEvery { biometricHelper.authenticate(any()) } returns flowOf(false, true) val events = viewModel.test().viewEvents @@ -232,8 +248,7 @@ class LockScreenViewModelTests { @Test fun `showBiometricPrompt handles exceptions`() = runTest { - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val exception = IllegalStateException("Something went wrong") coEvery { biometricHelper.authenticate(any()) } throws exception From cc59b9e6959c569ebd6c92305974d2debbff8cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 9 Aug 2022 12:57:59 +0200 Subject: [PATCH 043/114] Address review comments. --- .../features/pin/lockscreen/biometrics/BiometricHelperTests.kt | 3 ++- .../app/features/pin/lockscreen/ui/LockScreenViewModel.kt | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt index 2ec69cf0b1..30520dd44f 100644 --- a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt @@ -39,6 +39,7 @@ import im.vector.app.features.pin.lockscreen.ui.fallbackprompt.FallbackBiometric import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck import io.mockk.clearAllMocks import io.mockk.every +import io.mockk.justRun import io.mockk.mockk import io.mockk.mockkObject import io.mockk.mockkStatic @@ -247,7 +248,7 @@ class BiometricHelperTests { every { createAuthChannel() } returns mockAuthChannel every { authenticateWithPromptInternal(any(), any(), any()) } returns mockk() } - every { lockScreenKeyRepository.deleteSystemKey() } returns Unit + justRun { lockScreenKeyRepository.deleteSystemKey() } val latch = CountDownLatch(1) val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java) diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt index 417ebdbd93..2230215047 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt @@ -182,8 +182,7 @@ class LockScreenViewModel @AssistedInject constructor( * See issue [#6768](https://github.com/vector-im/element-android/issues/6768). */ @SuppressLint("NewApi") - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal suspend fun waitUntilKeyguardIsUnlocked() { + private suspend fun waitUntilKeyguardIsUnlocked() { if (versionProvider.get() < Build.VERSION_CODES.S) return withTimeoutOrNull(5.seconds) { while (keyguardManager.isDeviceLocked) { From 9888e15f2a4570b2376ec23a66809254b365d222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 9 Aug 2022 14:02:26 +0200 Subject: [PATCH 044/114] Improve logic to trigger LockScreenViewEvents --- .../pin/lockscreen/ui/LockScreenAction.kt | 1 + .../pin/lockscreen/ui/LockScreenFragment.kt | 13 ++----------- .../pin/lockscreen/ui/LockScreenViewEvent.kt | 1 + .../pin/lockscreen/ui/LockScreenViewModel.kt | 19 +++++++++++++++---- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenAction.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenAction.kt index 7f8d08dddd..729cf24aa5 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenAction.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenAction.kt @@ -22,4 +22,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class LockScreenAction : VectorViewModelAction { data class PinCodeEntered(val value: String) : LockScreenAction() data class ShowBiometricPrompt(val callingActivity: FragmentActivity) : LockScreenAction() + object OnUIReady : LockScreenAction() } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt index 9a618ce939..a7a228a105 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt @@ -23,7 +23,6 @@ import android.view.ViewGroup import android.view.animation.AnimationUtils import android.widget.TextView import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint @@ -34,10 +33,6 @@ import im.vector.app.databinding.FragmentLockScreenBinding import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.views.LockScreenCodeView -import kotlinx.coroutines.flow.distinctUntilChangedBy -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class LockScreenFragment : VectorBaseFragment() { @@ -59,12 +54,7 @@ class LockScreenFragment : VectorBaseFragment() { handleEvent(it) } - viewModel.stateFlow.distinctUntilChangedBy { it.showBiometricPromptAutomatically } - .filter { it.showBiometricPromptAutomatically } - .onEach { - showBiometricPrompt() - } - .launchIn(viewLifecycleOwner.lifecycleScope) + viewModel.handle(LockScreenAction.OnUIReady) } override fun invalidate() = withState(viewModel) { state -> @@ -119,6 +109,7 @@ class LockScreenFragment : VectorBaseFragment() { is LockScreenViewEvent.AuthFailure -> onAuthFailure(viewEvent.method) is LockScreenViewEvent.AuthError -> onAuthError(viewEvent.method, viewEvent.throwable) is LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage -> lockScreenListener?.onBiometricKeyInvalidated() + is LockScreenViewEvent.ShowBiometricPromptAutomatically -> showBiometricPrompt() } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt index e340486bc0..543ed58ffa 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt @@ -25,4 +25,5 @@ sealed class LockScreenViewEvent : VectorViewEvents { data class AuthFailure(val method: AuthMethod) : LockScreenViewEvent() data class AuthError(val method: AuthMethod, val throwable: Throwable) : LockScreenViewEvent() object ShowBiometricKeyInvalidatedMessage : LockScreenViewEvent() + object ShowBiometricPromptAutomatically : LockScreenViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt index 2230215047..d40f67ea35 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt @@ -20,7 +20,6 @@ import android.annotation.SuppressLint import android.app.KeyguardManager import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException -import androidx.annotation.VisibleForTesting import androidx.fragment.app.FragmentActivity import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -37,6 +36,7 @@ import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper import kotlinx.coroutines.delay import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -77,10 +77,20 @@ class LockScreenViewModel @AssistedInject constructor( lockScreenKeysMigrator.migrateIfNeeded() // Update initial state with biometric info updateStateWithBiometricInfo() + } + } - val state = awaitState() - // If when initialized we detect a key invalidation, we should show an error message. - if (state.isBiometricKeyInvalidated) { + private fun observeStateChanges() { + // The first time the state allows it, show the biometric prompt + viewModelScope.launch { + if (stateFlow.firstOrNull { it.showBiometricPromptAutomatically } != null) { + _viewEvents.post(LockScreenViewEvent.ShowBiometricPromptAutomatically) + } + } + + // The first time the state allows it, react to biometric key being invalidated + viewModelScope.launch { + if (stateFlow.firstOrNull { it.isBiometricKeyInvalidated } != null) { onBiometricKeyInvalidated() } } @@ -90,6 +100,7 @@ class LockScreenViewModel @AssistedInject constructor( when (action) { is LockScreenAction.PinCodeEntered -> onPinCodeEntered(action.value) is LockScreenAction.ShowBiometricPrompt -> showBiometricPrompt(action.callingActivity) + is LockScreenAction.OnUIReady -> observeStateChanges() } } From 6045eac87a4eb5ae95d6c811482a81f7a09982b0 Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Tue, 9 Aug 2022 14:31:26 +0200 Subject: [PATCH 045/114] recents carousel for new home screen layout (#6707) --- dependencies_groups.gradle | 1 + vector/build.gradle | 3 + .../java/im/vector/app/VectorApplication.kt | 19 +++- .../room/list/home/HomeRoomListFragment.kt | 17 +++- .../room/list/home/HomeRoomListViewModel.kt | 15 ++++ .../home/room/list/home/HomeRoomSection.kt | 4 + .../recent/RecentRoomCarouselController.kt | 86 +++++++++++++++++++ .../room/list/home/recent/RecentRoomItem.kt | 78 +++++++++++++++++ .../src/main/res/layout/item_recent_room.xml | 61 +++++++++++++ 9 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomItem.kt create mode 100644 vector/src/main/res/layout/item_recent_room.xml diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index f60a77a92d..d5972ed846 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -74,6 +74,7 @@ ext.groups = [ 'com.github.javaparser', 'com.github.piasy', 'com.github.shyiko.klob', + 'com.github.rubensousa', 'com.google', 'com.google.android', 'com.google.api.grpc', diff --git a/vector/build.gradle b/vector/build.gradle index 0edaf5424e..e5bd835a8f 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -427,6 +427,9 @@ dependencies { implementation libs.airbnb.epoxyPaging implementation libs.airbnb.mavericks + // Snap Helper https://github.com/rubensousa/GravitySnapHelper + implementation 'com.github.rubensousa:gravitysnaphelper:2.2.2' + // Nightly // API-only library gplayImplementation libs.google.appdistributionApi diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index b1bd0fc308..ca7f1b6c8e 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -25,17 +25,21 @@ import android.content.res.Configuration import android.os.Handler import android.os.HandlerThread import android.os.StrictMode +import android.view.Gravity import androidx.core.provider.FontRequest import androidx.core.provider.FontsContractCompat import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner import androidx.multidex.MultiDex +import androidx.recyclerview.widget.SnapHelper +import com.airbnb.epoxy.Carousel import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyController import com.airbnb.mvrx.Mavericks import com.facebook.stetho.Stetho import com.gabrielittner.threetenbp.LazyThreeTen +import com.github.rubensousa.gravitysnaphelper.GravitySnapHelper import com.mapbox.mapboxsdk.Mapbox import com.vanniktech.emoji.EmojiManager import com.vanniktech.emoji.google.GoogleEmojiProvider @@ -141,8 +145,9 @@ class VectorApplication : logInfo() LazyThreeTen.init(this) Mavericks.initialize(debugMode = false) - EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() - EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + + configureEpoxy() + registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager)) val fontRequest = FontRequest( "com.google.android.gms.fonts", @@ -198,6 +203,16 @@ class VectorApplication : Mapbox.getInstance(this) } + private fun configureEpoxy() { + EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + Carousel.setDefaultGlobalSnapHelperFactory(object : Carousel.SnapHelperFactory() { + override fun buildSnapHelper(context: Context?): SnapHelper { + return GravitySnapHelper(Gravity.START) + } + }) + } + private fun enableStrictModeIfNeeded() { if (Config.ENABLE_STRICT_MODE_LOGS) { StrictMode.setThreadPolicy( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index f0eb027785..02122a5ee1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -30,6 +30,7 @@ import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.epoxy.LayoutManagerStateRestorer +import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.UserPreferencesProvider @@ -43,6 +44,7 @@ import im.vector.app.features.home.room.list.RoomSummaryPagedController import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel +import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -53,7 +55,8 @@ import javax.inject.Inject class HomeRoomListFragment @Inject constructor( private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val userPreferencesProvider: UserPreferencesProvider + private val userPreferencesProvider: UserPreferencesProvider, + private val recentRoomCarouselController: RecentRoomCarouselController ) : VectorBaseFragment(), RoomListListener { @@ -180,6 +183,12 @@ class HomeRoomListFragment @Inject constructor( } }.adapter } + is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller -> + controller.listener = this + data.list.observe(viewLifecycleOwner) { list -> + controller.submitList(list) + } + }.adapter } } @@ -192,6 +201,12 @@ class HomeRoomListFragment @Inject constructor( ) } + override fun onDestroyView() { + views.roomListView.cleanup() + recentRoomCarouselController.listener = null + super.onDestroyView() + } + // region RoomListListener override fun onRoomClicked(room: RoomSummary) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index b95ec50ab0..479e22497f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.SpaceFilter import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter import org.matrix.android.sdk.api.query.toActiveSpaceOrOrphanRooms @@ -45,6 +46,7 @@ import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic class HomeRoomListViewModel @AssistedInject constructor( @@ -78,6 +80,7 @@ class HomeRoomListViewModel @AssistedInject constructor( private fun configureSections() { val newSections = mutableSetOf() + newSections.add(getRecentRoomsSection()) newSections.add(getAllRoomsSection()) viewModelScope.launch { @@ -89,6 +92,18 @@ class HomeRoomListViewModel @AssistedInject constructor( } } + private fun getRecentRoomsSection(): HomeRoomSection { + val liveList = session.roomService() + .getBreadcrumbsLive(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }) + + return HomeRoomSection.RecentRoomsData( + list = liveList + ) + } + private fun getAllRoomsSection(): HomeRoomSection.RoomSummaryData { val builder = RoomSummaryQueryParams.Builder().also { it.memberships = listOf(Membership.JOIN) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt index 7bfd0a769e..14c76b08bf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt @@ -24,4 +24,8 @@ sealed class HomeRoomSection { data class RoomSummaryData( val list: LiveData> ) : HomeRoomSection() + + data class RecentRoomsData( + val list: LiveData> + ) : HomeRoomSection() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt new file mode 100644 index 0000000000..53832bbc74 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.recent + +import android.content.res.Resources +import android.util.TypedValue +import com.airbnb.epoxy.Carousel +import com.airbnb.epoxy.CarouselModelBuilder +import com.airbnb.epoxy.EpoxyController +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.carousel +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.list.RoomListListener +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class RecentRoomCarouselController @Inject constructor( + private val avatarRenderer: AvatarRenderer, + private val resources: Resources, +) : EpoxyController() { + + private var data: List? = null + var listener: RoomListListener? = null + + private val hPadding = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 16f, + resources.displayMetrics + ).toInt() + + private val itemSpacing = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 24f, + resources.displayMetrics + ).toInt() + + fun submitList(recentList: List) { + this.data = recentList + requestModelBuild() + } + + override fun buildModels() { + val host = this + data?.let { data -> + carousel { + id("recents_carousel") + padding(Carousel.Padding(host.hPadding, host.itemSpacing)) + withModelsFrom(data) { roomSummary -> + val onClick = host.listener?.let { it::onRoomClicked } + val onLongClick = host.listener?.let { it::onRoomLongClicked } + + RecentRoomItem_() + .id(roomSummary.roomId) + .avatarRenderer(host.avatarRenderer) + .matrixItem(roomSummary.toMatrixItem()) + .unreadNotificationCount(roomSummary.notificationCount) + .showHighlighted(roomSummary.highlightCount > 0) + .itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false } + .itemClickListener { onClick?.invoke(roomSummary) } + } + } + } + } +} + +private inline fun CarouselModelBuilder.withModelsFrom( + items: List, + modelBuilder: (T) -> EpoxyModel<*> +) { + models(items.map { modelBuilder(it) }) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomItem.kt new file mode 100644 index 0000000000..6a575a2b6a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomItem.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.recent + +import android.view.HapticFeedbackConstants +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick +import im.vector.app.features.displayname.getBestName +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class RecentRoomItem : VectorEpoxyModel(R.layout.item_recent_room) { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var unreadNotificationCount: Int = 0 + @EpoxyAttribute var showHighlighted: Boolean = false + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var itemLongClickListener: View.OnLongClickListener? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var itemClickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + + holder.rootView.onClick(itemClickListener) + holder.rootView.setOnLongClickListener { + it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + itemLongClickListener?.onLongClick(it) ?: false + } + + avatarRenderer.render(matrixItem, holder.avatarImageView) + holder.avatarImageView.contentDescription = matrixItem.getBestName() + holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) + holder.title.text = matrixItem.getBestName() + } + + override fun unbind(holder: Holder) { + holder.rootView.setOnClickListener(null) + holder.rootView.setOnLongClickListener(null) + avatarRenderer.clear(holder.avatarImageView) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val unreadCounterBadgeView by bind(R.id.recentUnreadCounterBadgeView) + val avatarImageView by bind(R.id.recentImageView) + val title by bind(R.id.recentTitle) + val rootView by bind(R.id.recentRoot) + } +} diff --git a/vector/src/main/res/layout/item_recent_room.xml b/vector/src/main/res/layout/item_recent_room.xml new file mode 100644 index 0000000000..8e17707ff3 --- /dev/null +++ b/vector/src/main/res/layout/item_recent_room.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + From d7949307a41f5cceacdb2e1e6f8190a15d9ae1cd Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 9 Aug 2022 14:01:00 +0100 Subject: [PATCH 046/114] adding changelog entry --- changelog.d/6783.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6783.misc diff --git a/changelog.d/6783.misc b/changelog.d/6783.misc new file mode 100644 index 0000000000..d1095c1203 --- /dev/null +++ b/changelog.d/6783.misc @@ -0,0 +1 @@ +Decouples the variant logic from the vector module From a2768ccab7b99c3a44cdd745033d63b52fe91f12 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 9 Aug 2022 16:22:21 +0100 Subject: [PATCH 047/114] ignoring the gl locale from the play store upload step as it's unsupported --- tools/release/pushPlayStoreMetaData.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/release/pushPlayStoreMetaData.sh b/tools/release/pushPlayStoreMetaData.sh index 2d8fd9b36a..5c2f69cb7b 100755 --- a/tools/release/pushPlayStoreMetaData.sh +++ b/tools/release/pushPlayStoreMetaData.sh @@ -28,6 +28,7 @@ mv ./fastlane/metadata/android/fy ./fastlane_tmp mv ./fastlane/metadata/android/ga ./fastlane_tmp mv ./fastlane/metadata/android/kab ./fastlane_tmp mv ./fastlane/metadata/android/nb ./fastlane_tmp +mv ./fastlane/metadata/android/gl ./fastlane_tmp # Fastlane / PlayStore require longDescription and shortDescription file to be set, so copy the default # one for languages where they are missing From ddaf5397b1f2dc77be401290dd709739b1f72a35 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 10 Aug 2022 08:24:03 +0200 Subject: [PATCH 048/114] Removes params passed into HomeRoomListFragment and fixes lint error --- .../im/vector/app/features/home/NewHomeDetailFragment.kt | 5 +---- .../app/features/home/room/list/home/HomeRoomListFragment.kt | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index bfdf385356..c2b61d694a 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -31,7 +31,6 @@ import com.google.android.material.badge.BadgeDrawable import im.vector.app.R import im.vector.app.SpaceStateHandler import im.vector.app.core.extensions.commitTransaction -import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseFragment @@ -45,7 +44,6 @@ import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.dialpad.DialPadFragment import im.vector.app.features.call.webrtc.WebRtcCallManager -import im.vector.app.features.home.room.list.RoomListParams import im.vector.app.features.home.room.list.home.HomeRoomListFragment import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert @@ -339,8 +337,7 @@ class NewHomeDetailFragment @Inject constructor( if (fragmentToShow == null) { when (tab) { is HomeTab.RoomList -> { - val params = RoomListParams(tab.displayMode) - add(R.id.roomListContainer, HomeRoomListFragment::class.java, params.toMvRxBundle(), fragmentTag) + add(R.id.roomListContainer, HomeRoomListFragment::class.java, null, fragmentTag) } is HomeTab.DialPad -> { add(R.id.roomListContainer, createDialPadFragment(), fragmentTag) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index bb8fee632c..d0ef6fc0c2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -205,7 +205,6 @@ class HomeRoomListFragment @Inject constructor( } } - private fun promptLeaveRoom(roomId: String) { val isPublicRoom = roomListViewModel.isPublicRoom(roomId) val message = buildString { From 358ec5a722e5fe0b6889a65e9f9d6d9bf918301e Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 10 Aug 2022 08:25:51 +0200 Subject: [PATCH 049/114] Fixes post merge errors --- .../app/features/home/room/list/home/HomeRoomListFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index b29e151350..3c0c8f6b7e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -47,8 +47,8 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListSharedAction import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel -import im.vector.app.features.spaces.SpaceListBottomSheet import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController +import im.vector.app.features.spaces.SpaceListBottomSheet import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary From e81da5d8b44f21ed7843757205fdccf2542014f2 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 10 Aug 2022 09:02:04 +0100 Subject: [PATCH 050/114] generating 1.4.32 changelog --- CHANGES.md | 35 +++++++++++++++++++ changelog.d/6406.misc | 1 - changelog.d/6598.misc | 1 - changelog.d/6637.misc | 1 - changelog.d/6642.misc | 1 - changelog.d/6647.misc | 1 - changelog.d/6653.misc | 1 - changelog.d/6655.feature | 1 - changelog.d/6674.misc | 1 - changelog.d/6678.misc | 1 - changelog.d/6680.misc | 1 - changelog.d/6687.bugfix | 1 - changelog.d/6706.bugfix | 1 - changelog.d/6711.feature | 1 - changelog.d/6713.bugfix | 1 - changelog.d/6735.bugfix | 1 - changelog.d/6737.bugfix | 1 - changelog.d/6739.misc | 1 - changelog.d/6768.bugfix | 1 - changelog.d/6769.bugfix | 1 - .../android/en-US/changelogs/40104320.txt | 2 ++ 21 files changed, 37 insertions(+), 19 deletions(-) delete mode 100644 changelog.d/6406.misc delete mode 100644 changelog.d/6598.misc delete mode 100644 changelog.d/6637.misc delete mode 100644 changelog.d/6642.misc delete mode 100644 changelog.d/6647.misc delete mode 100644 changelog.d/6653.misc delete mode 100644 changelog.d/6655.feature delete mode 100644 changelog.d/6674.misc delete mode 100644 changelog.d/6678.misc delete mode 100644 changelog.d/6680.misc delete mode 100644 changelog.d/6687.bugfix delete mode 100644 changelog.d/6706.bugfix delete mode 100644 changelog.d/6711.feature delete mode 100644 changelog.d/6713.bugfix delete mode 100644 changelog.d/6735.bugfix delete mode 100644 changelog.d/6737.bugfix delete mode 100644 changelog.d/6739.misc delete mode 100644 changelog.d/6768.bugfix delete mode 100644 changelog.d/6769.bugfix create mode 100644 fastlane/metadata/android/en-US/changelogs/40104320.txt diff --git a/CHANGES.md b/CHANGES.md index 2bddbef133..829b1a0caa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,38 @@ +Changes in Element v1.4.32 (2022-08-10) +======================================= + +Features ✨ +---------- + - [Location Share] Render fallback UI when map fails to load ([#6711](https://github.com/vector-im/element-android/issues/6711)) + +Bugfixes 🐛 +---------- + - Fix message content sometimes appearing in the log ([#6706](https://github.com/vector-im/element-android/issues/6706)) + - Disable 'Enable biometrics' option if there are not biometric authenticators enrolled. ([#6713](https://github.com/vector-im/element-android/issues/6713)) + - Fix crash when biometric key is used when coming back to foreground and KeyStore reports that the device is still locked. ([#6768](https://github.com/vector-im/element-android/issues/6768)) + - Catch all exceptions on lockscreen system key migrations. ([#6769](https://github.com/vector-im/element-android/issues/6769)) + - Fixes crash when entering non ascii characters during account creation ([#6735](https://github.com/vector-im/element-android/issues/6735)) + - Fixes onboarding login/account creation errors showing after navigation ([#6737](https://github.com/vector-im/element-android/issues/6737)) + - [Location sharing] Invisible text on map symbol ([#6687](https://github.com/vector-im/element-android/issues/6687)) + +In development 🚧 +---------------- + - Adds new app layout toolbar ([#6655](https://github.com/vector-im/element-android/issues/6655)) + +Other changes +------------- + - [Modularization] Provides abstraction to avoid direct usages of BuildConfig ([#6406](https://github.com/vector-im/element-android/issues/6406)) + - Refactors SpaceStateHandler (previously AppStateHandler) and adds unit tests for it ([#6598](https://github.com/vector-im/element-android/issues/6598)) + - Setup Danger to the project ([#6637](https://github.com/vector-im/element-android/issues/6637)) + - [Location Share] Open maximized map on tapping on live sharing notification ([#6642](https://github.com/vector-im/element-android/issues/6642)) + - [Location sharing] Align naming of components for live location feature ([#6647](https://github.com/vector-im/element-android/issues/6647)) + - [Location share] Update minimum sending period to 5 seconds for a live ([#6653](https://github.com/vector-im/element-android/issues/6653)) + - [Location sharing] - Fix the memory leaks ([#6674](https://github.com/vector-im/element-android/issues/6674)) + - [Timeline] Memory leak in audio message playback tracker ([#6678](https://github.com/vector-im/element-android/issues/6678)) + - [FTUE] Memory leak on FtueAuthSplashCarouselFragment ([#6680](https://github.com/vector-im/element-android/issues/6680)) + - Link directly to DCO docs from danger message. ([#6739](https://github.com/vector-im/element-android/issues/6739)) + + Changes in Element v1.4.31 (2022-08-01) ======================================= diff --git a/changelog.d/6406.misc b/changelog.d/6406.misc deleted file mode 100644 index 27cf3c6493..0000000000 --- a/changelog.d/6406.misc +++ /dev/null @@ -1 +0,0 @@ -[Modularization] Provides abstraction to avoids direct usages of BuildConfig diff --git a/changelog.d/6598.misc b/changelog.d/6598.misc deleted file mode 100644 index db65a30bdc..0000000000 --- a/changelog.d/6598.misc +++ /dev/null @@ -1 +0,0 @@ -Refactors SpaceStateHandler (previously AppStateHandler) and adds unit tests for it diff --git a/changelog.d/6637.misc b/changelog.d/6637.misc deleted file mode 100644 index 7fc5ffad98..0000000000 --- a/changelog.d/6637.misc +++ /dev/null @@ -1 +0,0 @@ -Setup Danger to the project diff --git a/changelog.d/6642.misc b/changelog.d/6642.misc deleted file mode 100644 index a32b20716a..0000000000 --- a/changelog.d/6642.misc +++ /dev/null @@ -1 +0,0 @@ -[Location Share] Open maximized map on tapping on live sharing notification diff --git a/changelog.d/6647.misc b/changelog.d/6647.misc deleted file mode 100644 index e183cac57a..0000000000 --- a/changelog.d/6647.misc +++ /dev/null @@ -1 +0,0 @@ -[Location sharing] Align naming of components for live location feature diff --git a/changelog.d/6653.misc b/changelog.d/6653.misc deleted file mode 100644 index 1b5be1b83f..0000000000 --- a/changelog.d/6653.misc +++ /dev/null @@ -1 +0,0 @@ -[Location share] Update minimum sending period to 5 seconds for a live diff --git a/changelog.d/6655.feature b/changelog.d/6655.feature deleted file mode 100644 index 13a4c6d572..0000000000 --- a/changelog.d/6655.feature +++ /dev/null @@ -1 +0,0 @@ -Adds new app layout toolbar (feature flagged) diff --git a/changelog.d/6674.misc b/changelog.d/6674.misc deleted file mode 100644 index 830d528e27..0000000000 --- a/changelog.d/6674.misc +++ /dev/null @@ -1 +0,0 @@ -[Location sharing] - Fix the memory leaks diff --git a/changelog.d/6678.misc b/changelog.d/6678.misc deleted file mode 100644 index a7a53257d8..0000000000 --- a/changelog.d/6678.misc +++ /dev/null @@ -1 +0,0 @@ -[Timeline] Memory leak in audio message playback tracker diff --git a/changelog.d/6680.misc b/changelog.d/6680.misc deleted file mode 100644 index f42160fbba..0000000000 --- a/changelog.d/6680.misc +++ /dev/null @@ -1 +0,0 @@ -[FTUE] Memory leak on FtueAuthSplashCarouselFragment diff --git a/changelog.d/6687.bugfix b/changelog.d/6687.bugfix deleted file mode 100644 index 9b92efdffe..0000000000 --- a/changelog.d/6687.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Location sharing] Invisible text on map symbol diff --git a/changelog.d/6706.bugfix b/changelog.d/6706.bugfix deleted file mode 100644 index 51d5248374..0000000000 --- a/changelog.d/6706.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix message content sometimes appearing in the log diff --git a/changelog.d/6711.feature b/changelog.d/6711.feature deleted file mode 100644 index cff718affd..0000000000 --- a/changelog.d/6711.feature +++ /dev/null @@ -1 +0,0 @@ -[Location Share] Render fallback UI when map fails to load diff --git a/changelog.d/6713.bugfix b/changelog.d/6713.bugfix deleted file mode 100644 index 6a9926aa13..0000000000 --- a/changelog.d/6713.bugfix +++ /dev/null @@ -1 +0,0 @@ -Disable 'Enable biometrics' option if there are not biometric authenticators enrolled. diff --git a/changelog.d/6735.bugfix b/changelog.d/6735.bugfix deleted file mode 100644 index 814bf3f47c..0000000000 --- a/changelog.d/6735.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes crash when entering non ascii characters during account creation diff --git a/changelog.d/6737.bugfix b/changelog.d/6737.bugfix deleted file mode 100644 index 6568e9ff31..0000000000 --- a/changelog.d/6737.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes onboarding login/account creation errors showing after navigation diff --git a/changelog.d/6739.misc b/changelog.d/6739.misc deleted file mode 100644 index 5e5de00831..0000000000 --- a/changelog.d/6739.misc +++ /dev/null @@ -1 +0,0 @@ -Link directly to DCO docs from danger message. diff --git a/changelog.d/6768.bugfix b/changelog.d/6768.bugfix deleted file mode 100644 index 764386132b..0000000000 --- a/changelog.d/6768.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash when biometric key is used when coming back to foreground and KeyStore reports that the device is still locked. diff --git a/changelog.d/6769.bugfix b/changelog.d/6769.bugfix deleted file mode 100644 index 5d65bff449..0000000000 --- a/changelog.d/6769.bugfix +++ /dev/null @@ -1 +0,0 @@ -Catch all exceptions on lockscreen system key migrations. diff --git a/fastlane/metadata/android/en-US/changelogs/40104320.txt b/fastlane/metadata/android/en-US/changelogs/40104320.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases From fb0b38bb4936c19c45f5ed7fb67ce48ecfdb9894 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 9 Aug 2022 17:18:56 +0200 Subject: [PATCH 051/114] Adding new feature flag for new device management --- .../app/features/debug/features/DebugFeaturesStateFactory.kt | 5 +++++ .../app/features/debug/features/DebugVectorFeatures.kt | 4 ++++ .../src/main/java/im/vector/app/features/VectorFeatures.kt | 2 ++ 3 files changed, 11 insertions(+) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index d7e402c4dc..c127e3aed6 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -90,6 +90,11 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.newAppLayoutEnabled, factory = VectorFeatures::isNewAppLayoutEnabled ), + createBooleanFeature( + label = "Enable New Device Management", + key = DebugFeatureKeys.newDeviceManagementEnabled, + factory = VectorFeatures::isNewDeviceManagementEnabled + ), ) ) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 031ff11d59..003b9b8084 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -79,6 +79,9 @@ class DebugVectorFeatures( override fun isNewAppLayoutEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled) ?: vectorFeatures.isNewAppLayoutEnabled() + override fun isNewDeviceManagementEnabled(): Boolean = read(DebugFeatureKeys.newDeviceManagementEnabled) + ?: vectorFeatures.isNewDeviceManagementEnabled() + fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { it.remove(key) @@ -139,4 +142,5 @@ object DebugFeatureKeys { val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder") val startDmOnFirstMsg = booleanPreferencesKey("start-dm-on-first-msg") val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled") + val newDeviceManagementEnabled = booleanPreferencesKey("new-device-management-enabled") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 290d21879d..e60b225689 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -34,6 +34,7 @@ interface VectorFeatures { fun forceUsageOfOpusEncoder(): Boolean fun shouldStartDmOnFirstMessage(): Boolean fun isNewAppLayoutEnabled(): Boolean + fun isNewDeviceManagementEnabled(): Boolean } class DefaultVectorFeatures : VectorFeatures { @@ -50,4 +51,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun forceUsageOfOpusEncoder(): Boolean = false override fun shouldStartDmOnFirstMessage(): Boolean = false override fun isNewAppLayoutEnabled(): Boolean = false + override fun isNewDeviceManagementEnabled(): Boolean = false } From db1d0daec077a60f57c1a2670a7d632f6bb680a5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 10 Aug 2022 09:50:02 +0200 Subject: [PATCH 052/114] Adding a new setting entry behind feature flag --- .../vector/app/features/settings/VectorPreferences.kt | 1 + .../settings/VectorSettingsSecurityPrivacyFragment.kt | 10 ++++++++++ .../main/res/xml/vector_settings_security_privacy.xml | 7 ++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index ac14bfc3c7..0134fc581b 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -74,6 +74,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY" const val SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY" const val SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY = "SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY" + const val SETTINGS_SHOW_DEVICES_V2_LIST_PREFERENCE_KEY = "SETTINGS_SHOW_DEVICES_V2_LIST_PREFERENCE_KEY" const val SETTINGS_ALLOW_INTEGRATIONS_KEY = "SETTINGS_ALLOW_INTEGRATIONS_KEY" const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY" const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index ac8a47b81e..023bb9e03f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -50,6 +50,7 @@ import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.openFileSelection import im.vector.app.core.utils.toast import im.vector.app.databinding.DialogImportE2eKeysBinding +import im.vector.app.features.VectorFeatures import im.vector.app.features.analytics.AnalyticsConfig import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewActions @@ -86,6 +87,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( private val rawService: RawService, private val navigator: Navigator, private val analyticsConfig: AnalyticsConfig, + private val vectorFeatures: VectorFeatures, ) : VectorSettingsBaseFragment() { override var titleRes = R.string.settings_security_and_privacy @@ -135,6 +137,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( findPreference(VectorPreferences.SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY)!! } + private val showDevicesV2ListPref by lazy { + findPreference(VectorPreferences.SETTINGS_SHOW_DEVICES_V2_LIST_PREFERENCE_KEY)!! + } + // encrypt to unverified devices private val sendToUnverifiedDevicesPref by lazy { findPreference(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY)!! @@ -546,6 +552,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( showDeviceListPref.isEnabled = devices.isNotEmpty() showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size) + showDevicesV2ListPref.isVisible = vectorFeatures.isNewDeviceManagementEnabled() + showDevicesV2ListPref.title = showDeviceListPref.title.toString() + " (V2, WIP)" + showDevicesV2ListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size) + val userId = session.myUserId val deviceId = session.sessionParams.deviceId diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index d4dfed2fcf..d3493fd984 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -62,6 +62,11 @@ android:title="@string/settings_active_sessions_show_all" app:fragment="im.vector.app.features.settings.devices.VectorSettingsDevicesFragment" /> + + - \ No newline at end of file + From 1ebc2a9a8c0485a7d91a72a2e0d69097bc680165 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 10 Aug 2022 10:18:37 +0200 Subject: [PATCH 053/114] New empty devices list screen --- .../v2/VectorSettingsDevicesFragment.kt | 49 +++++++++++++++++++ .../res/layout/fragment_settings_devices.xml | 5 ++ vector/src/main/res/values/strings.xml | 1 + .../xml/vector_settings_security_privacy.xml | 2 +- 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt create mode 100644 vector/src/main/res/layout/fragment_settings_devices.xml diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt new file mode 100644 index 0000000000..0d5dedacc9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2 + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentSettingsDevicesBinding +import javax.inject.Inject + +/** + * Display the list of the user's devices and sessions. + */ +@AndroidEntryPoint +class VectorSettingsDevicesFragment @Inject constructor() : VectorBaseFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding { + return FragmentSettingsDevicesBinding.inflate(inflater, container, false) + } + + override fun onAttach(context: Context) { + super.onAttach(context) + initToolbar() + } + + private fun initToolbar() { + (activity as? AppCompatActivity) + ?.supportActionBar + ?.setTitle(R.string.settings_sessions_list) + } +} diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml new file mode 100644 index 0000000000..2a77219ad0 --- /dev/null +++ b/vector/src/main/res/layout/fragment_settings_devices.xml @@ -0,0 +1,5 @@ + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 9fc9756897..7c01eed715 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2336,6 +2336,7 @@ Show All Sessions Manage Sessions Sign out of this session + Sessions Server name Server version diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index d3493fd984..b5b21bc017 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -65,7 +65,7 @@ + app:fragment="im.vector.app.features.settings.devices.v2.VectorSettingsDevicesFragment" /> From ed00685514d734afd80b833cc0f0d89f4695b9ad Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 10 Aug 2022 10:39:27 +0200 Subject: [PATCH 054/114] Adding changelog entry --- changelog.d/6798.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6798.wip diff --git a/changelog.d/6798.wip b/changelog.d/6798.wip new file mode 100644 index 0000000000..a16270666b --- /dev/null +++ b/changelog.d/6798.wip @@ -0,0 +1 @@ +[Devices management] Add a feature flag and empty screen for future new layout From b9fa4ddfc8dbdfad7afb50cba4eb8d3e08fa60b6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 10 Aug 2022 10:53:34 +0200 Subject: [PATCH 055/114] Renaming v2 setting key --- .../vector/app/features/settings/VectorPreferences.kt | 2 +- .../settings/VectorSettingsSecurityPrivacyFragment.kt | 10 +++++----- .../main/res/xml/vector_settings_security_privacy.xml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 0134fc581b..857d2ce7fb 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -74,7 +74,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY" const val SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY" const val SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY = "SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY" - const val SETTINGS_SHOW_DEVICES_V2_LIST_PREFERENCE_KEY = "SETTINGS_SHOW_DEVICES_V2_LIST_PREFERENCE_KEY" + const val SETTINGS_SHOW_DEVICES_LIST_V2_PREFERENCE_KEY = "SETTINGS_SHOW_DEVICES_LIST_V2_PREFERENCE_KEY" const val SETTINGS_ALLOW_INTEGRATIONS_KEY = "SETTINGS_ALLOW_INTEGRATIONS_KEY" const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY" const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 023bb9e03f..24ad4e55ab 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -137,8 +137,8 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( findPreference(VectorPreferences.SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY)!! } - private val showDevicesV2ListPref by lazy { - findPreference(VectorPreferences.SETTINGS_SHOW_DEVICES_V2_LIST_PREFERENCE_KEY)!! + private val showDevicesListV2Pref by lazy { + findPreference(VectorPreferences.SETTINGS_SHOW_DEVICES_LIST_V2_PREFERENCE_KEY)!! } // encrypt to unverified devices @@ -552,9 +552,9 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( showDeviceListPref.isEnabled = devices.isNotEmpty() showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size) - showDevicesV2ListPref.isVisible = vectorFeatures.isNewDeviceManagementEnabled() - showDevicesV2ListPref.title = showDeviceListPref.title.toString() + " (V2, WIP)" - showDevicesV2ListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size) + showDevicesListV2Pref.isVisible = vectorFeatures.isNewDeviceManagementEnabled() + showDevicesListV2Pref.title = showDeviceListPref.title.toString() + " (V2, WIP)" + showDevicesListV2Pref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size) val userId = session.myUserId val deviceId = session.sessionParams.deviceId diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index b5b21bc017..c246a40f71 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -63,7 +63,7 @@ app:fragment="im.vector.app.features.settings.devices.VectorSettingsDevicesFragment" /> From fd37b31c4482868ee7d23f8f25c622258b79f03c Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 10 Aug 2022 11:28:14 +0200 Subject: [PATCH 056/114] Changes changelog file extension --- changelog.d/{6749.feature => 6749.wip} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{6749.feature => 6749.wip} (100%) diff --git a/changelog.d/6749.feature b/changelog.d/6749.wip similarity index 100% rename from changelog.d/6749.feature rename to changelog.d/6749.wip From defd848363c35b9394f3cf503bf17a01d173cd7d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 10 Aug 2022 12:30:38 +0100 Subject: [PATCH 057/114] updating version for next release cycle --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 45a962f12c..e6b526585b 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -60,7 +60,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.32\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.34\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector/build.gradle b/vector/build.gradle index e5bd835a8f..cb7ee67a6a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -37,7 +37,7 @@ ext.versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 32 +ext.versionPatch = 34 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From aecf460c96b3a56603c6410ccd6f522c521ae998 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 10 Aug 2022 14:00:36 +0200 Subject: [PATCH 058/114] Improve tests for lockscreen (#6796) * Improve tests * Address review comments. * Refactor pin code tests and code to improve testability. * Fix lint issues --- .../pin/lockscreen/ui/LockScreenViewModel.kt | 19 +- .../pin/lockscreen/ui/LockScreenViewState.kt | 4 +- .../fragment/LockScreenViewModelTests.kt | 218 +++++++++++------- .../java/im/vector/app/test/Extensions.kt | 5 + .../im/vector/app/test/FlowTestObserver.kt | 10 +- 5 files changed, 155 insertions(+), 101 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt index d40f67ea35..33ea590f1d 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt @@ -64,8 +64,6 @@ class LockScreenViewModel @AssistedInject constructor( private val biometricHelper = biometricHelperFactory.create(initialState.lockScreenConfiguration) - private var firstEnteredCode: String? = null - // BiometricPrompt will automatically disable system auth after too many failed auth attempts private var isSystemAuthTemporarilyDisabledByBiometricPrompt = false @@ -108,18 +106,17 @@ class LockScreenViewModel @AssistedInject constructor( val state = awaitState() when (state.lockScreenConfiguration.mode) { LockScreenMode.CREATE -> { - if (firstEnteredCode == null && state.lockScreenConfiguration.needsNewCodeValidation) { - firstEnteredCode = code - _viewEvents.post(LockScreenViewEvent.ClearPinCode(false)) - emit(PinCodeState.FirstCodeEntered) + val enteredPinCode = (state.pinCodeState as? PinCodeState.FirstCodeEntered)?.pinCode + if (enteredPinCode == null && state.lockScreenConfiguration.needsNewCodeValidation) { + _viewEvents.post(LockScreenViewEvent.ClearPinCode(confirmationFailed = false)) + emit(PinCodeState.FirstCodeEntered(code)) } else { - if (!state.lockScreenConfiguration.needsNewCodeValidation || code == firstEnteredCode) { + if (!state.lockScreenConfiguration.needsNewCodeValidation || code == enteredPinCode) { pinCodeHelper.createPinCode(code) _viewEvents.post(LockScreenViewEvent.CodeCreationComplete) emit(null) } else { - firstEnteredCode = null - _viewEvents.post(LockScreenViewEvent.ClearPinCode(true)) + _viewEvents.post(LockScreenViewEvent.ClearPinCode(confirmationFailed = true)) emit(PinCodeState.Idle) } } @@ -137,7 +134,9 @@ class LockScreenViewModel @AssistedInject constructor( }.catch { error -> _viewEvents.post(LockScreenViewEvent.AuthError(AuthMethod.PIN_CODE, error)) }.onEach { newPinState -> - newPinState?.let { setState { copy(pinCodeState = it) } } + if (newPinState != null) { + setState { copy(pinCodeState = newPinState) } + } }.launchIn(viewModelScope) @SuppressLint("NewApi") diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt index f689e1faf1..c6c6359f4f 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt @@ -27,11 +27,11 @@ data class LockScreenViewState( val isBiometricKeyInvalidated: Boolean, ) : MavericksState { constructor(lockScreenConfiguration: LockScreenConfiguration) : this( - lockScreenConfiguration, false, false, PinCodeState.Idle, false + lockScreenConfiguration, false, false, PinCodeState.Idle, false, ) } sealed class PinCodeState { object Idle : PinCodeState() - object FirstCodeEntered : PinCodeState() + data class FirstCodeEntered(val pinCode: String) : PinCodeState() } diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt index 18dfdf9145..6037d9933e 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt @@ -19,9 +19,11 @@ package im.vector.app.features.pin.lockscreen.fragment import android.app.KeyguardManager import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException +import androidx.biometric.BiometricPrompt import androidx.fragment.app.FragmentActivity import com.airbnb.mvrx.test.MvRxTestRule import com.airbnb.mvrx.withState +import im.vector.app.features.pin.lockscreen.biometrics.BiometricAuthError import im.vector.app.features.pin.lockscreen.biometrics.BiometricHelper import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode @@ -42,10 +44,8 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest -import org.amshove.kluent.shouldBeEqualTo -import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue import org.amshove.kluent.shouldNotBeEqualTo import org.junit.Before import org.junit.Rule @@ -76,138 +76,141 @@ class LockScreenViewModelTests { @Test fun `init migrates old keys to new ones if needed`() { + // given val initialState = createViewState() + // when LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) - + // then coVerify { keysMigrator.migrateIfNeeded() } } @Test fun `init updates the initial state with biometric info`() = runTest { + // given every { biometricHelper.isSystemAuthEnabledAndValid } returns true val initialState = createViewState() + // when val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) - advanceUntilIdle() - val newState = viewModel.awaitState() + // then + val newState = viewModel.awaitState() // Can't use viewModel.test() here since we want to record events emitted on init newState shouldNotBeEqualTo initialState } @Test fun `Updating the initial state with biometric info waits until device is unlocked on Android 12+`() = runTest { + // given val initialState = createViewState() versionProvider.value = Build.VERSION_CODES.S + // when LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) - advanceUntilIdle() + // then verify { keyguardManager.isDeviceLocked } } @Test fun `when ViewModel is instantiated initialState is updated with biometric info`() { + // given + givenShowBiometricPromptAutomatically() val initialState = createViewState() - // This should set canUseBiometricAuth to true - every { biometricHelper.isSystemAuthEnabledAndValid } returns true + // when val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) - val newState = withState(viewModel) { it } - initialState shouldNotBeEqualTo newState + // then + withState(viewModel) { newState -> + initialState shouldNotBeEqualTo newState + } } @Test - fun `when onPinCodeEntered is called in VERIFY mode, the code is verified and the result is emitted as a ViewEvent`() = runTest { + fun `when onPinCodeEntered is called in VERIFY mode and verification is successful, code is verified and result is emitted as a ViewEvent`() = runTest { + // given val initialState = createViewState() val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coEvery { pinCodeHelper.verifyPinCode(any()) } returns true - - val events = viewModel.test().viewEvents - events.assertNoValues() - - val stateBefore = viewModel.awaitState() - + val test = viewModel.test() + // when viewModel.handle(LockScreenAction.PinCodeEntered("1234")) + // then coVerify { pinCodeHelper.verifyPinCode(any()) } - events.assertValues(LockScreenViewEvent.AuthSuccessful(AuthMethod.PIN_CODE)) + test.assertEvents(LockScreenViewEvent.AuthSuccessful(AuthMethod.PIN_CODE)) + test.assertStates(initialState) + } + @Test + fun `when onPinCodeEntered is called in VERIFY mode and verification fails, the error result is emitted as a ViewEvent`() = runTest { + // given + val initialState = createViewState() + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coEvery { pinCodeHelper.verifyPinCode(any()) } returns false + val test = viewModel.test() + // when viewModel.handle(LockScreenAction.PinCodeEntered("1234")) - events.assertValues(LockScreenViewEvent.AuthSuccessful(AuthMethod.PIN_CODE), LockScreenViewEvent.AuthFailure(AuthMethod.PIN_CODE)) - - val stateAfter = viewModel.awaitState() - stateBefore shouldBeEqualTo stateAfter + // then + coVerify { pinCodeHelper.verifyPinCode(any()) } + test.assertEvents(LockScreenViewEvent.AuthFailure(AuthMethod.PIN_CODE)) + test.assertStates(initialState) } @Test fun `when onPinCodeEntered is called in CREATE mode with no confirmation needed it creates the pin code`() = runTest { + // given val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = false) val initialState = createViewState(lockScreenConfiguration = configuration) val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) - - val events = viewModel.test().viewEvents - events.assertNoValues() - + val test = viewModel.test() + // when viewModel.handle(LockScreenAction.PinCodeEntered("1234")) + // then coVerify { pinCodeHelper.createPinCode(any()) } - - events.assertValues(LockScreenViewEvent.CodeCreationComplete) + test.assertEvents(LockScreenViewEvent.CodeCreationComplete) } @Test fun `when onPinCodeEntered is called twice in CREATE mode with confirmation needed it verifies and creates the pin code`() = runTest { + // given + val pinCode = "1234" val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = true) - val initialState = createViewState(lockScreenConfiguration = configuration) + val initialState = createViewState(lockScreenConfiguration = configuration, pinCodeState = PinCodeState.FirstCodeEntered(pinCode)) val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) - - val events = viewModel.test().viewEvents - events.assertNoValues() - - viewModel.handle(LockScreenAction.PinCodeEntered("1234")) - - events.assertValues(LockScreenViewEvent.ClearPinCode(false)) - val pinCodeState = viewModel.awaitState().pinCodeState - pinCodeState shouldBeEqualTo PinCodeState.FirstCodeEntered - - viewModel.handle(LockScreenAction.PinCodeEntered("1234")) - events.assertValues(LockScreenViewEvent.ClearPinCode(false), LockScreenViewEvent.CodeCreationComplete) + val test = viewModel.test() + // when + viewModel.handle(LockScreenAction.PinCodeEntered(pinCode)) + // then + test.assertEvents(LockScreenViewEvent.CodeCreationComplete) + .assertLatestState { (it.pinCodeState as? PinCodeState.FirstCodeEntered)?.pinCode == pinCode } } @Test fun `when onPinCodeEntered is called in CREATE mode with incorrect confirmation it clears the pin code`() = runTest { + // given val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = true) - val initialState = createViewState(lockScreenConfiguration = configuration) + val initialState = createViewState(lockScreenConfiguration = configuration, pinCodeState = PinCodeState.FirstCodeEntered("1234")) val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) - - val events = viewModel.test().viewEvents - events.assertNoValues() - - viewModel.handle(LockScreenAction.PinCodeEntered("1234")) - - events.assertValues(LockScreenViewEvent.ClearPinCode(false)) - val pinCodeState = viewModel.awaitState().pinCodeState - pinCodeState shouldBeEqualTo PinCodeState.FirstCodeEntered - + val test = viewModel.test() + // when viewModel.handle(LockScreenAction.PinCodeEntered("4321")) - events.assertValues(LockScreenViewEvent.ClearPinCode(false), LockScreenViewEvent.ClearPinCode(true)) - val newPinCodeState = viewModel.awaitState().pinCodeState - newPinCodeState shouldBeEqualTo PinCodeState.Idle + // then + test.assertEvents(LockScreenViewEvent.ClearPinCode(true)) + .assertLatestState(initialState.copy(pinCodeState = PinCodeState.Idle)) } @Test fun `onPinCodeEntered handles exceptions`() = runTest { + // given val initialState = createViewState() val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val exception = IllegalStateException("Something went wrong") coEvery { pinCodeHelper.verifyPinCode(any()) } throws exception - - val events = viewModel.test().viewEvents - events.assertNoValues() - + val test = viewModel.test() + // when viewModel.handle(LockScreenAction.PinCodeEntered("1234")) - - events.assertValues(LockScreenViewEvent.AuthError(AuthMethod.PIN_CODE, exception)) + // then + test.assertEvents(LockScreenViewEvent.AuthError(AuthMethod.PIN_CODE, exception)) } @Test fun `when showBiometricPrompt catches a KeyPermanentlyInvalidatedException it disables biometric authentication`() = runTest { + // given versionProvider.value = Build.VERSION_CODES.M - every { biometricHelper.isSystemKeyValid } returns false val exception = KeyPermanentlyInvalidatedException() coEvery { biometricHelper.authenticate(any()) } throws exception @@ -218,49 +221,81 @@ class LockScreenViewModelTests { lockScreenConfiguration = configuration ) val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) - - val events = viewModel.test().viewEvents - events.assertNoValues() - + val test = viewModel.test() + // when viewModel.handle(LockScreenAction.ShowBiometricPrompt(mockk())) - - events.assertValues(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) + // then + test.assertEvents(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) + // Biometric key is invalidated so biometric auth is disabled + .assertLatestState { !it.canUseBiometricAuth } verify { biometricHelper.disableAuthentication() } - - // System key was deleted, biometric auth should be disabled - every { biometricHelper.isSystemAuthEnabledAndValid } returns false - val newState = viewModel.awaitState() - newState.canUseBiometricAuth.shouldBeFalse() } @Test fun `when showBiometricPrompt receives an event it propagates it as a ViewEvent`() = runTest { + // given val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coEvery { biometricHelper.authenticate(any()) } returns flowOf(false, true) - - val events = viewModel.test().viewEvents - events.assertNoValues() - + val test = viewModel.test() + // when viewModel.handle(LockScreenAction.ShowBiometricPrompt(mockk())) - - events.assertValues(LockScreenViewEvent.AuthFailure(AuthMethod.BIOMETRICS), LockScreenViewEvent.AuthSuccessful(AuthMethod.BIOMETRICS)) + // then + test.assertEvents(LockScreenViewEvent.AuthFailure(AuthMethod.BIOMETRICS), LockScreenViewEvent.AuthSuccessful(AuthMethod.BIOMETRICS)) } @Test fun `showBiometricPrompt handles exceptions`() = runTest { + // given val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val exception = IllegalStateException("Something went wrong") coEvery { biometricHelper.authenticate(any()) } throws exception - - val events = viewModel.test().viewEvents - events.assertNoValues() - + val test = viewModel.test() + // when viewModel.handle(LockScreenAction.ShowBiometricPrompt(mockk())) - - events.assertValues(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, exception)) + // then + test.assertEvents(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, exception)) } - private fun createViewState( + @Test + fun `when showBiometricPrompt handles isAuthDisabledError, canUseBiometricAuth becomes false`() = runTest { + // given + val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) + val exception = BiometricAuthError(BiometricPrompt.ERROR_LOCKOUT_PERMANENT, "Permanent lockout") + coEvery { biometricHelper.authenticate(any()) } throws exception + val test = viewModel.test() + // when + viewModel.handle(LockScreenAction.ShowBiometricPrompt(mockk())) + // then + exception.isAuthDisabledError.shouldBeTrue() + test.assertEvents(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, exception)) + .assertLatestState { !it.canUseBiometricAuth } + } + + @Test + fun `when OnUIReady action is received and showBiometricPromptAutomatically is true it shows prompt`() = runTest { + // given + givenShowBiometricPromptAutomatically() + val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) + val test = viewModel.test() + // when + viewModel.handle(LockScreenAction.OnUIReady) + // then + test.assertEvents(LockScreenViewEvent.ShowBiometricPromptAutomatically) + } + + @Test + fun `when OnUIReady action is received and isBiometricKeyInvalidated is true it shows prompt`() = runTest { + // given + givenBiometricKeyIsInvalidated() + val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) + val test = viewModel.test() + // when + viewModel.handle(LockScreenAction.OnUIReady) + // then + test.assertEvents(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) + } + + private fun createViewState( lockScreenConfiguration: LockScreenConfiguration = createDefaultConfiguration(), canUseBiometricAuth: Boolean = false, showBiometricPromptAutomatically: Boolean = false, @@ -286,4 +321,13 @@ class LockScreenViewModelTests { isDeviceCredentialUnlockEnabled, needsNewCodeValidation ).let(otherChanges) + + private fun givenBiometricKeyIsInvalidated() { + every { biometricHelper.hasSystemKey } returns true + every { biometricHelper.isSystemKeyValid } returns false + } + + private fun givenShowBiometricPromptAutomatically() { + every { biometricHelper.isSystemAuthEnabledAndValid } returns true + } } diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 5ac17cc5ff..2fbab3b71b 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -91,6 +91,11 @@ class ViewModelTest( return this } + fun assertLatestState(predicate: (S) -> Boolean): ViewModelTest { + states.assertLatestValue(predicate) + return this + } + fun finish() { states.finish() viewEvents.finish() diff --git a/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt b/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt index db828be232..ce8d27cd2a 100644 --- a/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt +++ b/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt @@ -47,8 +47,14 @@ class FlowTestObserver( return this } - fun assertLatestValue(value: T) { - assertTrue(values.last() == value) + fun assertLatestValue(predicate: (T) -> Boolean): FlowTestObserver { + assertTrue(predicate(values.last())) + return this + } + + fun assertLatestValue(value: T): FlowTestObserver { + assertEquals(value, values.last()) + return this } fun assertValues(values: List): FlowTestObserver { From 31a350eea70ede920002e310c2506a14e5648da4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 27 Jul 2022 16:36:16 +0200 Subject: [PATCH 059/114] Enabling leakcanary on debug builds --- dependencies_groups.gradle | 2 ++ vector/build.gradle | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index f60a77a92d..1c0135ae0f 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -106,7 +106,9 @@ ext.groups = [ 'com.pinterest.ktlint', 'com.posthog.android', 'com.squareup', + 'com.squareup.curtains', 'com.squareup.duktape', + 'com.squareup.leakcanary', 'com.squareup.moshi', 'com.squareup.okhttp3', 'com.squareup.okio', diff --git a/vector/build.gradle b/vector/build.gradle index 1244a84bfd..ee08ad7ae5 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -573,7 +573,7 @@ dependencies { debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0" // Activate when you want to check for leaks, from time to time. - //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testRunner From 36c69a46fdb9d3f9880b3890b779125d045eca04 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 8 Aug 2022 17:52:58 +0200 Subject: [PATCH 060/114] Adding way to enable leak canary at runtime --- .../vector/app/leakcanary/LeakCanaryProxy.kt | 26 ++++++++++++++++++ .../java/im/vector/app/VectorApplication.kt | 9 +++++++ .../im/vector/app/core/di/FragmentModule.kt | 6 +++++ .../features/settings/VectorPreferences.kt | 6 +++++ .../VectorSettingsAdvancedSettingsFragment.kt | 19 ++++++++++++- vector/src/main/res/values/strings.xml | 3 +++ .../xml/vector_settings_advanced_settings.xml | 9 ++++++- .../vector/app/leakcanary/LeakCanaryProxy.kt | 27 +++++++++++++++++++ 8 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 vector/src/debug/java/im/vector/app/leakcanary/LeakCanaryProxy.kt create mode 100644 vector/src/release/java/im/vector/app/leakcanary/LeakCanaryProxy.kt diff --git a/vector/src/debug/java/im/vector/app/leakcanary/LeakCanaryProxy.kt b/vector/src/debug/java/im/vector/app/leakcanary/LeakCanaryProxy.kt new file mode 100644 index 0000000000..3154c2c04b --- /dev/null +++ b/vector/src/debug/java/im/vector/app/leakcanary/LeakCanaryProxy.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.leakcanary + +import leakcanary.LeakCanary +import javax.inject.Inject + +class LeakCanaryProxy @Inject constructor() { + fun enable(enable: Boolean) { + LeakCanary.config = LeakCanary.config.copy(dumpHeap = enable) + } +} diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 53222ab962..bbff8f35c8 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -61,6 +61,8 @@ import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.version.VersionProvider +import im.vector.app.leakcanary.LeakCanaryProxy +import im.vector.app.push.fcm.FcmHelper import org.jitsi.meet.sdk.log.JitsiMeetDefaultLogHandler import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.auth.AuthenticationService @@ -102,6 +104,7 @@ class VectorApplication : @Inject lateinit var matrix: Matrix @Inject lateinit var fcmHelper: FcmHelper @Inject lateinit var buildMeta: BuildMeta + @Inject lateinit var leakCanaryProxy: LeakCanaryProxy // font thread handler private var fontThreadHandler: Handler? = null @@ -196,6 +199,8 @@ class VectorApplication : // Initialize Mapbox before inflating mapViews Mapbox.getInstance(this) + + initMemoryLeakAnalysis() } private fun enableStrictModeIfNeeded() { @@ -251,4 +256,8 @@ class VectorApplication : handlerThread.start() return Handler(handlerThread.looper) } + + private fun initMemoryLeakAnalysis() { + leakCanaryProxy.enable(vectorPreferences.isMemoryLeakAnalysisEnabled()) + } } diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index e86b350534..36b1d994c1 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -150,6 +150,7 @@ import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRul import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment +import im.vector.app.features.settings.VectorSettingsAdvancedSettingsFragment import im.vector.app.features.settings.VectorSettingsGeneralFragment import im.vector.app.features.settings.VectorSettingsHelpAboutFragment import im.vector.app.features.settings.VectorSettingsLabsFragment @@ -624,6 +625,11 @@ interface FragmentModule { @FragmentKey(VectorSettingsSecurityPrivacyFragment::class) fun bindVectorSettingsSecurityPrivacyFragment(fragment: VectorSettingsSecurityPrivacyFragment): Fragment + @Binds + @IntoMap + @FragmentKey(VectorSettingsAdvancedSettingsFragment::class) + fun bindVectorSettingsAdvancedSettingsFragment(fragment: VectorSettingsAdvancedSettingsFragment): Fragment + @Binds @IntoMap @FragmentKey(VectorSettingsHelpAboutFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index ac14bfc3c7..287b2c64ee 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -168,6 +168,7 @@ class VectorPreferences @Inject constructor( private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY" private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY" private const val SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY = "SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY" + const val SETTINGS_ENABLE_MEMORY_LEAK_ANALYSIS_KEY = "SETTINGS_ENABLE_MEMORY_LEAK_ANALYSIS_KEY" const val SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY = "SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY" @@ -268,6 +269,7 @@ class VectorPreferences @Inject constructor( SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, SETTINGS_LABS_ALLOW_EXTENDED_LOGS, SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, + SETTINGS_ENABLE_MEMORY_LEAK_ANALYSIS_KEY, SETTINGS_USE_RAGE_SHAKE_KEY, SETTINGS_SECURITY_USE_FLAG_SECURE, @@ -368,6 +370,10 @@ class VectorPreferences @Inject constructor( return buildMeta.isDebug || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false)) } + fun isMemoryLeakAnalysisEnabled(): Boolean { + return buildMeta.isDebug && developerMode() && defaultPrefs.getBoolean(SETTINGS_ENABLE_MEMORY_LEAK_ANALYSIS_KEY, false) + } + fun didAskUserToEnableSessionPush(): Boolean { return defaultPrefs.getBoolean(DID_ASK_TO_ENABLE_SESSION_PUSH, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt index b616ce1e1f..1bb7718390 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt @@ -19,14 +19,19 @@ package im.vector.app.features.settings import android.os.Bundle import androidx.preference.Preference import androidx.preference.SeekBarPreference +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.rageshake.RageShake +import im.vector.app.leakcanary.LeakCanaryProxy +import javax.inject.Inject -class VectorSettingsAdvancedSettingsFragment : VectorSettingsBaseFragment() { +class VectorSettingsAdvancedSettingsFragment @Inject constructor( + private val leakCanaryProxy: LeakCanaryProxy, +) : VectorSettingsBaseFragment() { override var titleRes = R.string.settings_advanced_settings override val preferenceXmlRes = R.xml.vector_settings_advanced_settings @@ -82,5 +87,17 @@ class VectorSettingsAdvancedSettingsFragment : VectorSettingsBaseFragment() { } else { findPreference("SETTINGS_RAGE_SHAKE_CATEGORY_KEY")!!.isVisible = false } + + bindMemoryLeakSetting() + } + + private fun bindMemoryLeakSetting() { + findPreference(VectorPreferences.SETTINGS_ENABLE_MEMORY_LEAK_ANALYSIS_KEY)?.let { pref -> + pref.isVisible = BuildConfig.DEBUG + pref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + leakCanaryProxy.enable(newValue as? Boolean ?: false) + true + } + } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d1b2d237d9..1b21642e04 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2185,6 +2185,9 @@ ${app_name} may crash more often when an unexpected error occurs + Enable memory leak analysis + App performances may be impacted when enabled. + Show debug info on screen Show some useful info to help debugging the application diff --git a/vector/src/main/res/xml/vector_settings_advanced_settings.xml b/vector/src/main/res/xml/vector_settings_advanced_settings.xml index 2fb3fae310..c076fc2238 100644 --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml @@ -44,6 +44,13 @@ android:summary="@string/settings_developer_mode_fail_fast_summary" android:title="@string/settings_developer_mode_fail_fast_title" /> + + - \ No newline at end of file + diff --git a/vector/src/release/java/im/vector/app/leakcanary/LeakCanaryProxy.kt b/vector/src/release/java/im/vector/app/leakcanary/LeakCanaryProxy.kt new file mode 100644 index 0000000000..28c9f1c9d5 --- /dev/null +++ b/vector/src/release/java/im/vector/app/leakcanary/LeakCanaryProxy.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.leakcanary + +import javax.inject.Inject + +/** + * No op version. + */ +@Suppress("UNUSED_PARAMETER") +class LeakCanaryProxy @Inject constructor() { + fun enable(enable: Boolean) {} +} From 12405527e14b010123aa98977be515410ce583c5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 9 Aug 2022 16:16:55 +0200 Subject: [PATCH 061/114] Adding changelog entry --- changelog.d/6786.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6786.misc diff --git a/changelog.d/6786.misc b/changelog.d/6786.misc new file mode 100644 index 0000000000..a916336ae4 --- /dev/null +++ b/changelog.d/6786.misc @@ -0,0 +1 @@ +Add a developer setting to enable LeakCanary at runtime From 250ee1faa1c4a7a9a0ff5aa866aa0f50570faac5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 10 Aug 2022 14:19:03 +0200 Subject: [PATCH 062/114] Moving the new setting inside debug screen --- vector/src/debug/AndroidManifest.xml | 1 + .../app/features/debug/DebugMenuActivity.kt | 6 ++ .../debug/di/MavericksViewModelDebugModule.kt | 7 ++ .../debug/leak/DebugMemoryLeaksActivity.kt | 37 ++++++++++ .../debug/leak/DebugMemoryLeaksFragment.kt | 54 +++++++++++++++ .../debug/leak/DebugMemoryLeaksViewActions.kt | 23 +++++++ .../debug/leak/DebugMemoryLeaksViewModel.kt | 67 +++++++++++++++++++ .../debug/leak/DebugMemoryLeaksViewState.kt | 23 +++++++ .../debug/res/layout/activity_debug_menu.xml | 12 +++- .../layout/fragment_debug_memory_leaks.xml | 32 +++++++++ .../im/vector/app/core/di/FragmentModule.kt | 6 -- .../features/settings/VectorPreferences.kt | 9 ++- .../VectorSettingsAdvancedSettingsFragment.kt | 18 +---- vector/src/main/res/values/strings.xml | 3 - .../xml/vector_settings_advanced_settings.xml | 7 -- 15 files changed, 270 insertions(+), 35 deletions(-) create mode 100644 vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksActivity.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewActions.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewModel.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewState.kt create mode 100644 vector/src/debug/res/layout/fragment_debug_memory_leaks.xml diff --git a/vector/src/debug/AndroidManifest.xml b/vector/src/debug/AndroidManifest.xml index 4ec47d4920..94fdb1b389 100644 --- a/vector/src/debug/AndroidManifest.xml +++ b/vector/src/debug/AndroidManifest.xml @@ -10,6 +10,7 @@ + () { views.debugAnalytics.setOnClickListener { startActivity(Intent(this, DebugAnalyticsActivity::class.java)) } + views.debugMemoryLeaks.setOnClickListener { openMemoryLeaksSettings() } views.debugTestTextViewLink.setOnClickListener { testTextViewLink() } views.debugOpenButtonStylesLight.setOnClickListener { startActivity(Intent(this, DebugVectorButtonStylesLightActivity::class.java)) @@ -130,6 +132,10 @@ class DebugMenuActivity : VectorBaseActivity() { startActivity(Intent(this, DebugPrivateSettingsActivity::class.java)) } + private fun openMemoryLeaksSettings() { + startActivity(Intent(this, DebugMemoryLeaksActivity::class.java)) + } + private fun renderQrCode(text: String) { views.debugQrCode.setData(text) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt b/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt index 6ef7fe441a..8512bb8c6d 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/di/MavericksViewModelDebugModule.kt @@ -24,6 +24,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksViewModelComponent import im.vector.app.core.di.MavericksViewModelKey import im.vector.app.features.debug.analytics.DebugAnalyticsViewModel +import im.vector.app.features.debug.leak.DebugMemoryLeaksViewModel import im.vector.app.features.debug.settings.DebugPrivateSettingsViewModel @InstallIn(MavericksViewModelComponent::class) @@ -39,4 +40,10 @@ interface MavericksViewModelDebugModule { @IntoMap @MavericksViewModelKey(DebugPrivateSettingsViewModel::class) fun debugPrivateSettingsViewModelFactory(factory: DebugPrivateSettingsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(DebugMemoryLeaksViewModel::class) + fun debugMemoryLeaksViewModelFactory(factory: DebugMemoryLeaksViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + } diff --git a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksActivity.kt new file mode 100644 index 0000000000..226c65e3ed --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksActivity.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.leak + +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivitySimpleBinding + +@AndroidEntryPoint +class DebugMemoryLeaksActivity : VectorBaseActivity() { + + override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + + override fun initUiAndData() { + if (isFirstCreation()) { + addFragment( + views.simpleFragmentContainer, + DebugMemoryLeaksFragment::class.java + ) + } + } +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt new file mode 100644 index 0000000000..d3e70e26e6 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.leak + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.epoxy.onClick +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentDebugMemoryLeaksBinding + +@AndroidEntryPoint +class DebugMemoryLeaksFragment : VectorBaseFragment() { + + private val viewModel: DebugMemoryLeaksViewModel by fragmentViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDebugMemoryLeaksBinding { + return FragmentDebugMemoryLeaksBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setViewListeners() + } + + private fun setViewListeners() { + views.enableMemoryLeakAnalysis.onClick { + viewModel.handle(DebugMemoryLeaksViewActions.EnableMemoryLeaksAnalysis(views.enableMemoryLeakAnalysis.isChecked)) + } + } + + override fun invalidate() = withState(viewModel) { viewState -> + views.enableMemoryLeakAnalysis.isChecked = viewState.isMemoryLeaksAnalysisEnabled + } +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewActions.kt b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewActions.kt new file mode 100644 index 0000000000..e1447ae345 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewActions.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.leak + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface DebugMemoryLeaksViewActions : VectorViewModelAction { + data class EnableMemoryLeaksAnalysis(val isEnabled: Boolean) : DebugMemoryLeaksViewActions +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewModel.kt new file mode 100644 index 0000000000..617a6b2ecb --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewModel.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.leak + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.settings.VectorPreferences +import im.vector.app.leakcanary.LeakCanaryProxy +import kotlinx.coroutines.launch + +class DebugMemoryLeaksViewModel @AssistedInject constructor( + @Assisted initialState: DebugMemoryLeaksViewState, + private val vectorPreferences: VectorPreferences, + private val leakCanaryProxy: LeakCanaryProxy, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: DebugMemoryLeaksViewState): DebugMemoryLeaksViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + init { + viewModelScope.launch { + refreshStateFromPreferences() + } + } + + override fun handle(action: DebugMemoryLeaksViewActions) { + when (action) { + is DebugMemoryLeaksViewActions.EnableMemoryLeaksAnalysis -> handleEnableMemoryLeaksAnalysis(action) + } + } + + private fun handleEnableMemoryLeaksAnalysis(action: DebugMemoryLeaksViewActions.EnableMemoryLeaksAnalysis) { + viewModelScope.launch { + vectorPreferences.enableMemoryLeakAnalysis(action.isEnabled) + leakCanaryProxy.enable(action.isEnabled) + refreshStateFromPreferences() + } + } + + private fun refreshStateFromPreferences() { + setState { copy(isMemoryLeaksAnalysisEnabled = vectorPreferences.isMemoryLeakAnalysisEnabled()) } + } +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewState.kt new file mode 100644 index 0000000000..4e9fe4b402 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksViewState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.leak + +import com.airbnb.mvrx.MavericksState + +data class DebugMemoryLeaksViewState( + val isMemoryLeaksAnalysisEnabled: Boolean = false +) : MavericksState diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml index 8b38c17b35..b5efe0302c 100644 --- a/vector/src/debug/res/layout/activity_debug_menu.xml +++ b/vector/src/debug/res/layout/activity_debug_menu.xml @@ -1,5 +1,6 @@ + android:layout_height="wrap_content" + app:layout_anchor="@+id/scrollView2" + app:layout_anchorGravity="center"> +